feat: Implementing claims logic, adding error state and minor copy change (#5357)
* Implementing claims logic
This commit is contained in:
parent
9dd8ad1db6
commit
5dce68a62f
218
src/abis/uniswap-nft-airdrop-claim.json
Normal file
218
src/abis/uniswap-nft-airdrop-claim.json
Normal file
@ -0,0 +1,218 @@
|
||||
[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "token_",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "merkleRoot_",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "endTime_",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "AlreadyClaimed",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "ClaimWindowFinished",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "EndTimeInPast",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "InvalidProof",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "NoWithdrawDuringClaim",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Claimed",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "previousOwner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnershipTransferred",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32[]",
|
||||
"name": "merkleProof",
|
||||
"type": "bytes32[]"
|
||||
}
|
||||
],
|
||||
"name": "claim",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "endTime",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "isClaimed",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "merkleRoot",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "owner",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "renounceOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "token",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "transferOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "withdraw",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
@ -1,233 +0,0 @@
|
||||
import airdropBackgroundv2 from 'assets/images/airdopBackground.png'
|
||||
import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
|
||||
import { OpacityHoverState } from 'components/Common'
|
||||
import Loader from 'components/Loader'
|
||||
import { ChevronRightIcon } from 'nft/components/icons'
|
||||
import { useState } from 'react'
|
||||
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
import styled from 'styled-components/macro'
|
||||
import { CloseIcon, ThemedText } from 'theme'
|
||||
|
||||
import Modal from '../Modal'
|
||||
|
||||
const ModalWrap = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
const Body = styled.div`
|
||||
padding: 28px 20px 20px 20px;
|
||||
`
|
||||
|
||||
const ClaimButton = styled(ThemeButton)`
|
||||
width: 100%;
|
||||
background-color: ${({ theme }) => theme.accentAction};
|
||||
margin-top: 40px;
|
||||
border-radius: 12px;
|
||||
color: ${({ theme }) => theme.white};
|
||||
`
|
||||
|
||||
const Line = styled.div`
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: ${({ theme }) => theme.white};
|
||||
opacity: 0.24;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
`
|
||||
|
||||
const LinkWrap = styled.a`
|
||||
text-decoration: none;
|
||||
${OpacityHoverState}
|
||||
`
|
||||
|
||||
const ImageContainer = styled.div`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const StyledImage = styled.img`
|
||||
width: 100%;
|
||||
height: 170px;
|
||||
`
|
||||
|
||||
const USDCLabel = styled.div`
|
||||
font-weight: 700;
|
||||
font-size: 36px;
|
||||
line-height: 44px;
|
||||
margin-top: 8px;
|
||||
color: white;
|
||||
`
|
||||
|
||||
const TextContainer = styled.div`
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
`
|
||||
|
||||
const RewardsDetailsContainer = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
`
|
||||
|
||||
const CurrencyText = styled.span`
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
line-height: 14.5px;
|
||||
`
|
||||
|
||||
const ClaimContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
height: 380px;
|
||||
padding: 60px 28px;
|
||||
padding-bottom: 20px;
|
||||
`
|
||||
|
||||
const SuccessText = styled.div`
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
|
||||
const EtherscanLink = styled.a`
|
||||
text-decoration: none;
|
||||
|
||||
${OpacityHoverState}
|
||||
`
|
||||
|
||||
const CloseButton = styled(ThemeButton)`
|
||||
max-width: 68px;
|
||||
margin-top: auto;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
`
|
||||
|
||||
const SyledCloseIcon = styled(CloseIcon)`
|
||||
float: right;
|
||||
height: 24px;
|
||||
|
||||
${OpacityHoverState}
|
||||
`
|
||||
|
||||
const RewardsText = styled.span`
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: ${({ theme }) => theme.white};
|
||||
|
||||
&:first-child {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`
|
||||
|
||||
const RewardsInformationText = styled.span`
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
margin-bottom: 28px;
|
||||
`
|
||||
|
||||
const MainHeader = styled.span`
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
color: ${({ theme }) => theme.white};
|
||||
`
|
||||
|
||||
const AirdropModal = () => {
|
||||
const [isClaimed, setClaimed] = useState(false)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [totalAmount] = useState(300)
|
||||
const isOpen = useModalIsOpen(ApplicationModal.UNISWAP_NFT_AIRDROP_CLAIM)
|
||||
const usdcAirdropToggle = useToggleModal(ApplicationModal.UNISWAP_NFT_AIRDROP_CLAIM)
|
||||
const dismiss = () => {
|
||||
usdcAirdropToggle()
|
||||
|
||||
setTimeout(() => {
|
||||
setClaimed(false)
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
setIsSubmitting(true)
|
||||
|
||||
setTimeout(() => {
|
||||
setIsSubmitting(false)
|
||||
setClaimed(true)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal hideBorder isOpen={isOpen} onDismiss={dismiss} maxHeight={90} maxWidth={400}>
|
||||
<ModalWrap>
|
||||
{isClaimed ? (
|
||||
<ClaimContainer>
|
||||
<ThemedText.HeadlineSmall>Congratulations!</ThemedText.HeadlineSmall>
|
||||
<SuccessText>
|
||||
You have successfully claimed {totalAmount} USDC. Thank you for supporting Genie.xyz.
|
||||
</SuccessText>
|
||||
<EtherscanLink href="https://etherscan.io/" target="_blank">
|
||||
<ThemedText.Link>
|
||||
<div style={{ display: 'flex', alignItems: 'center', textAlign: 'center', justifyContent: 'center' }}>
|
||||
<span style={{ marginRight: 8 }}>Etherscan</span>
|
||||
<ChevronRightIcon />
|
||||
</div>
|
||||
</ThemedText.Link>
|
||||
</EtherscanLink>
|
||||
|
||||
<CloseButton size={ButtonSize.medium} emphasis={ButtonEmphasis.medium} onClick={dismiss}>
|
||||
Close
|
||||
</CloseButton>
|
||||
</ClaimContainer>
|
||||
) : (
|
||||
<>
|
||||
<ImageContainer>
|
||||
<TextContainer>
|
||||
<SyledCloseIcon onClick={dismiss} stroke="white" />
|
||||
<MainHeader>Uniswap NFT Airdrop</MainHeader>
|
||||
<USDCLabel>{totalAmount} USDC</USDCLabel>
|
||||
<Line />
|
||||
<RewardsDetailsContainer>
|
||||
<RewardsText>Trading rewards</RewardsText> <CurrencyText>300 USDC</CurrencyText>
|
||||
</RewardsDetailsContainer>
|
||||
<RewardsDetailsContainer>
|
||||
<RewardsText>Genie NFT holder rewards</RewardsText> <CurrencyText>0</CurrencyText>
|
||||
</RewardsDetailsContainer>
|
||||
</TextContainer>
|
||||
<StyledImage src={airdropBackgroundv2} />
|
||||
</ImageContainer>
|
||||
<Body>
|
||||
<RewardsInformationText>
|
||||
As a long time supporter of Genie you’ve been awarded {totalAmount} USDC tokens. Read more about Uniswap
|
||||
NFT.
|
||||
</RewardsInformationText>
|
||||
<LinkWrap href="https://uniswap.org/blog/uniswap-nft-aggregator-announcement" target="_blank">
|
||||
<ThemedText.Link>Read more about Uniswap NFT.</ThemedText.Link>
|
||||
</LinkWrap>
|
||||
|
||||
<ClaimButton
|
||||
onClick={submit}
|
||||
size={ButtonSize.medium}
|
||||
emphasis={ButtonEmphasis.medium}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting && <Loader stroke="white" />}
|
||||
<span>Claim{isSubmitting && 'ing'} USDC</span>
|
||||
</ClaimButton>
|
||||
</Body>
|
||||
</>
|
||||
)}
|
||||
</ModalWrap>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default AirdropModal
|
333
src/components/AirdropModal/index.tsx
Normal file
333
src/components/AirdropModal/index.tsx
Normal file
@ -0,0 +1,333 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import type { TransactionResponse } from '@ethersproject/providers'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import uniswapNftAirdropClaim from 'abis/uniswap-nft-airdrop-claim.json'
|
||||
import airdropBackgroundv2 from 'assets/images/airdopBackground.png'
|
||||
import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
|
||||
import { OpacityHoverState } from 'components/Common'
|
||||
import Loader from 'components/Loader'
|
||||
import { UNISWAP_NFT_AIRDROP_CLAIM_ADDRESS } from 'constants/addresses'
|
||||
import { useContract } from 'hooks/useContract'
|
||||
import { ChevronRightIcon } from 'nft/components/icons'
|
||||
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
|
||||
import { CollectionRewardsFetcher } from 'nft/queries/genie/GetAirdorpMerkle'
|
||||
import { Airdrop, Rewards } from 'nft/types/airdrop'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
import styled from 'styled-components/macro'
|
||||
import { CloseIcon, ThemedText } from 'theme'
|
||||
|
||||
import Modal from '../Modal'
|
||||
|
||||
const ModalWrap = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
const Body = styled.div`
|
||||
padding: 28px 20px 20px 20px;
|
||||
`
|
||||
|
||||
const ClaimButton = styled(ThemeButton)`
|
||||
width: 100%;
|
||||
background-color: ${({ theme }) => theme.accentAction};
|
||||
border-radius: 12px;
|
||||
color: ${({ theme }) => theme.white};
|
||||
`
|
||||
|
||||
const Line = styled.div`
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: ${({ theme }) => theme.white};
|
||||
opacity: 0.24;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
`
|
||||
|
||||
const LinkWrap = styled.a`
|
||||
text-decoration: none;
|
||||
${OpacityHoverState}
|
||||
`
|
||||
|
||||
const ImageContainer = styled.div`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const StyledImage = styled.img`
|
||||
width: 100%;
|
||||
height: 170px;
|
||||
`
|
||||
|
||||
const USDCLabel = styled.div`
|
||||
font-weight: 700;
|
||||
font-size: 36px;
|
||||
line-height: 44px;
|
||||
margin-top: 8px;
|
||||
color: white;
|
||||
`
|
||||
|
||||
const TextContainer = styled.div`
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
`
|
||||
|
||||
const RewardsDetailsContainer = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
`
|
||||
|
||||
const CurrencyText = styled.span`
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
line-height: 14.5px;
|
||||
`
|
||||
|
||||
const ClaimContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
height: 380px;
|
||||
padding: 60px 28px;
|
||||
padding-bottom: 20px;
|
||||
`
|
||||
|
||||
const SuccessText = styled.div`
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
|
||||
const EtherscanLink = styled.a`
|
||||
text-decoration: none;
|
||||
|
||||
${OpacityHoverState}
|
||||
`
|
||||
|
||||
const CloseButton = styled(ThemeButton)`
|
||||
max-width: 68px;
|
||||
margin-top: auto;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
`
|
||||
|
||||
const SyledCloseIcon = styled(CloseIcon)`
|
||||
float: right;
|
||||
height: 24px;
|
||||
|
||||
${OpacityHoverState}
|
||||
`
|
||||
|
||||
const Error = styled.div`
|
||||
display: flex;
|
||||
color: ${({ theme }) => theme.accentCritical};
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
border-radius: 16px;
|
||||
padding: 12px 20px;
|
||||
font-size: 14px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
`
|
||||
|
||||
const ReactLinkWrap = styled.div`
|
||||
margin-bottom: 40px;
|
||||
`
|
||||
|
||||
const RewardsText = styled.span`
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: ${({ theme }) => theme.white};
|
||||
|
||||
&:first-child {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`
|
||||
|
||||
const RewardsInformationText = styled.span`
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
margin-bottom: 28px;
|
||||
`
|
||||
|
||||
const MainHeader = styled.span`
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
color: ${({ theme }) => theme.white};
|
||||
`
|
||||
|
||||
const EtherscanLinkWrap = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
`
|
||||
|
||||
enum RewardAmounts {
|
||||
tradingRewardAmount = 300,
|
||||
holderRewardAmount = 1000,
|
||||
combinedAmount = 1300,
|
||||
}
|
||||
|
||||
const AirdropModal = () => {
|
||||
const { account, provider } = useWeb3React()
|
||||
const [claim, setClaim] = useState<Rewards>()
|
||||
const [isClaimed, setIsClaimed] = useState(false)
|
||||
const [hash, setHash] = useState('')
|
||||
const [error, setError] = useState(false)
|
||||
const setIsClaimAvailable = useIsNftClaimAvailable((state) => state.setIsClaimAvailable)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [totalAmount, setTotalAmount] = useState(0)
|
||||
const isOpen = useModalIsOpen(ApplicationModal.UNISWAP_NFT_AIRDROP_CLAIM)
|
||||
const usdcAirdropToggle = useToggleModal(ApplicationModal.UNISWAP_NFT_AIRDROP_CLAIM)
|
||||
const contract = useContract(UNISWAP_NFT_AIRDROP_CLAIM_ADDRESS, uniswapNftAirdropClaim)
|
||||
|
||||
const displayError = () => {
|
||||
setIsSubmitting(false)
|
||||
setError(true)
|
||||
setTimeout(() => {
|
||||
setError(false)
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (account && provider && contract) {
|
||||
;(async () => {
|
||||
try {
|
||||
const { data } = await CollectionRewardsFetcher(account)
|
||||
const claim = data.find((claim) => claim?.rewardType === Airdrop.GENIE_UNISWAP_USDC_AIRDROP)
|
||||
|
||||
if (!claim) return
|
||||
|
||||
const [isClaimed] = await contract.connect(provider).functions.isClaimed(claim?.index)
|
||||
|
||||
if (claim && isClaimed === false) {
|
||||
const usdAmount = BigNumber.from(claim.amount).div(10 ** 6)
|
||||
setClaim(claim)
|
||||
setTotalAmount(usdAmount.toNumber())
|
||||
setIsClaimAvailable(true)
|
||||
}
|
||||
} catch (err) {
|
||||
displayError()
|
||||
}
|
||||
})()
|
||||
}
|
||||
}, [account, contract, provider, setIsClaimAvailable])
|
||||
|
||||
const makeClaim = async () => {
|
||||
try {
|
||||
if (contract && claim && claim.amount && claim.merkleProof && provider) {
|
||||
setIsSubmitting(true)
|
||||
|
||||
const response: TransactionResponse = await contract
|
||||
.connect(provider?.getSigner())
|
||||
.functions.claim(claim.index, account, claim?.amount, claim?.merkleProof)
|
||||
|
||||
setHash(response.hash)
|
||||
setIsSubmitting(false)
|
||||
setIsClaimed(true)
|
||||
setIsClaimAvailable(false)
|
||||
}
|
||||
} catch (err) {
|
||||
setIsSubmitting(false)
|
||||
displayError()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal hideBorder isOpen={isOpen} onDismiss={usdcAirdropToggle} maxHeight={90} maxWidth={400}>
|
||||
<ModalWrap>
|
||||
{isClaimed ? (
|
||||
<ClaimContainer>
|
||||
<ThemedText.HeadlineSmall>Congratulations!</ThemedText.HeadlineSmall>
|
||||
<SuccessText>
|
||||
You have successfully claimed {totalAmount} USDC. Thank you for supporting Genie.xyz.
|
||||
</SuccessText>
|
||||
<EtherscanLink href={`https://etherscan.io/tx/${hash}`} target="_blank">
|
||||
<ThemedText.Link>
|
||||
<EtherscanLinkWrap>
|
||||
<span>Etherscan</span>
|
||||
<ChevronRightIcon />
|
||||
</EtherscanLinkWrap>
|
||||
</ThemedText.Link>
|
||||
</EtherscanLink>
|
||||
|
||||
<CloseButton size={ButtonSize.medium} emphasis={ButtonEmphasis.medium} onClick={usdcAirdropToggle}>
|
||||
Close
|
||||
</CloseButton>
|
||||
</ClaimContainer>
|
||||
) : (
|
||||
<>
|
||||
<ImageContainer>
|
||||
<TextContainer>
|
||||
<SyledCloseIcon onClick={usdcAirdropToggle} stroke="white" />
|
||||
<MainHeader>Uniswap NFT Airdrop</MainHeader>
|
||||
<USDCLabel>{totalAmount} USDC</USDCLabel>
|
||||
<Line />
|
||||
<RewardsDetailsContainer>
|
||||
<RewardsText>Trading rewards</RewardsText>{' '}
|
||||
<CurrencyText>
|
||||
{totalAmount === RewardAmounts.tradingRewardAmount || totalAmount === RewardAmounts.combinedAmount
|
||||
? `${RewardAmounts.tradingRewardAmount} USDC`
|
||||
: '0'}
|
||||
</CurrencyText>
|
||||
</RewardsDetailsContainer>
|
||||
<RewardsDetailsContainer>
|
||||
<RewardsText>Genie NFT holder rewards</RewardsText>{' '}
|
||||
<CurrencyText>
|
||||
{totalAmount !== RewardAmounts.tradingRewardAmount
|
||||
? `${RewardAmounts.holderRewardAmount} USDC`
|
||||
: '0'}
|
||||
</CurrencyText>
|
||||
</RewardsDetailsContainer>
|
||||
</TextContainer>
|
||||
<StyledImage src={airdropBackgroundv2} />
|
||||
</ImageContainer>
|
||||
<Body>
|
||||
<RewardsInformationText>
|
||||
As a long time supporter of Genie, you’ve been awarded {totalAmount} USDC tokens.
|
||||
</RewardsInformationText>
|
||||
<ReactLinkWrap>
|
||||
<LinkWrap href="https://uniswap.org/blog/uniswap-nft-aggregator-announcement" target="_blank">
|
||||
<ThemedText.Link>Read more about Uniswap NFT.</ThemedText.Link>
|
||||
</LinkWrap>
|
||||
</ReactLinkWrap>
|
||||
|
||||
{error && (
|
||||
<Error>
|
||||
<AlertTriangle />
|
||||
Claim USDC failed. Please try again later
|
||||
</Error>
|
||||
)}
|
||||
|
||||
<ClaimButton
|
||||
onClick={makeClaim}
|
||||
size={ButtonSize.medium}
|
||||
emphasis={ButtonEmphasis.medium}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting && <Loader stroke="white" />}
|
||||
<span>Claim{isSubmitting && 'ing'} USDC</span>
|
||||
</ClaimButton>
|
||||
</Body>
|
||||
</>
|
||||
)}
|
||||
</ModalWrap>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default AirdropModal
|
@ -11,7 +11,7 @@ import { ApplicationModal } from 'state/application/reducer'
|
||||
|
||||
const Bag = lazy(() => import('nft/components/bag/Bag'))
|
||||
const TransactionCompleteModal = lazy(() => import('nft/components/collection/TransactionCompleteModal'))
|
||||
const AirdropModal = lazy(() => import('components/AidropModal'))
|
||||
const AirdropModal = lazy(() => import('components/AirdropModal'))
|
||||
|
||||
export default function TopLevelModals() {
|
||||
const addressClaimOpen = useModalIsOpen(ApplicationModal.ADDRESS_CLAIM)
|
||||
|
@ -9,6 +9,7 @@ import useCopyClipboard from 'hooks/useCopyClipboard'
|
||||
import useStablecoinPrice from 'hooks/useStablecoinPrice'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks'
|
||||
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
|
||||
import { ProfilePageStateType } from 'nft/types'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { Copy, ExternalLink, Power } from 'react-feather'
|
||||
@ -117,6 +118,7 @@ const AuthenticatedHeader = () => {
|
||||
const setSellPageState = useProfilePageState((state) => state.setProfilePageState)
|
||||
const resetSellAssets = useSellAsset((state) => state.reset)
|
||||
const clearCollectionFilters = useWalletCollections((state) => state.clearCollectionFilters)
|
||||
const isClaimAvailable = useIsNftClaimAvailable((state) => state.isClaimAvailable)
|
||||
|
||||
const unclaimedAmount: CurrencyAmount<Token> | undefined = useUserUnclaimedAmount(account)
|
||||
const isUnclaimed = useUserHasAvailableClaim(account)
|
||||
@ -187,7 +189,7 @@ const AuthenticatedHeader = () => {
|
||||
<Trans>Claim</Trans> {unclaimedAmount?.toFixed(0, { groupSeparator: ',' } ?? '-')} <Trans>reward</Trans>
|
||||
</UNIButton>
|
||||
)}
|
||||
{nftFlag === NftVariant.Enabled && (
|
||||
{nftFlag === NftVariant.Enabled && isClaimAvailable && (
|
||||
<UNIButton size={ButtonSize.medium} emphasis={ButtonEmphasis.medium} onClick={openNftModal}>
|
||||
<Trans>Claim Uniswap NFT Airdrop</Trans>
|
||||
</UNIButton>
|
||||
|
@ -7,6 +7,7 @@ import WalletDropdown from 'components/WalletDropdown'
|
||||
import { getConnection } from 'connection/utils'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import { Portal } from 'nft/components/common/Portal'
|
||||
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
|
||||
import { getIsValidSwapQuote } from 'pages/Swap'
|
||||
import { darken } from 'polished'
|
||||
import { useMemo, useRef } from 'react'
|
||||
@ -14,6 +15,7 @@ import { AlertTriangle, ChevronDown, ChevronUp } from 'react-feather'
|
||||
import { useAppSelector } from 'state/hooks'
|
||||
import { useDerivedSwapInfo } from 'state/swap/hooks'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { colors } from 'theme/colors'
|
||||
import { flexRowNoWrap } from 'theme/styles'
|
||||
|
||||
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
|
||||
@ -95,11 +97,16 @@ const Web3StatusConnectWrapper = styled.div<{ faded?: boolean }>`
|
||||
}
|
||||
`
|
||||
|
||||
const Web3StatusConnected = styled(Web3StatusGeneric)<{ pending?: boolean; isNftActive?: boolean }>`
|
||||
const Web3StatusConnected = styled(Web3StatusGeneric)<{
|
||||
pending?: boolean
|
||||
isNftActive?: boolean
|
||||
isClaimAvailable?: boolean
|
||||
}>`
|
||||
background-color: ${({ pending, theme }) => (pending ? theme.deprecated_primary1 : theme.deprecated_bg1)};
|
||||
border: 1px solid ${({ pending, theme }) => (pending ? theme.deprecated_primary1 : theme.deprecated_bg1)};
|
||||
color: ${({ pending, theme }) => (pending ? theme.deprecated_white : theme.deprecated_text1)};
|
||||
font-weight: 500;
|
||||
border: ${({ isClaimAvailable }) => isClaimAvailable && `1px solid ${colors.purple300}`};
|
||||
:hover,
|
||||
:focus {
|
||||
border: 1px solid ${({ theme }) => darken(0.05, theme.deprecated_bg3)};
|
||||
@ -202,6 +209,7 @@ function Web3StatusInner() {
|
||||
const toggleWalletDropdown = useToggleWalletDropdown()
|
||||
const toggleWalletModal = useToggleWalletModal()
|
||||
const walletIsOpen = useModalIsOpen(ApplicationModal.WALLET_DROPDOWN)
|
||||
const isClaimAvailable = useIsNftClaimAvailable((state) => state.isClaimAvailable)
|
||||
|
||||
const error = useAppSelector((state) => state.connection.errorByConnectionType[getConnection(connector).type])
|
||||
const isNftActive = useNftFlag() === NftVariant.Enabled
|
||||
@ -241,6 +249,7 @@ function Web3StatusInner() {
|
||||
isNftActive={isNftActive}
|
||||
onClick={toggleWallet}
|
||||
pending={hasPendingTransactions}
|
||||
isClaimAvailable={isClaimAvailable}
|
||||
>
|
||||
{!hasPendingTransactions && <StatusIcon size={24} connectionType={connectionType} />}
|
||||
{hasPendingTransactions ? (
|
||||
|
@ -8,6 +8,8 @@ type AddressMap = { [chainId: number]: string }
|
||||
|
||||
export const UNI_ADDRESS: AddressMap = constructSameAddressMap('0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984')
|
||||
|
||||
export const UNISWAP_NFT_AIRDROP_CLAIM_ADDRESS = '0x8B799381ac40b838BBA4131ffB26197C432AFe78'
|
||||
|
||||
export const V2_FACTORY_ADDRESSES: AddressMap = constructSameAddressMap(V2_FACTORY_ADDRESS)
|
||||
export const V2_ROUTER_ADDRESS: AddressMap = constructSameAddressMap('0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D')
|
||||
|
||||
|
19
src/nft/hooks/useIsNftClaimAvailable.ts
Normal file
19
src/nft/hooks/useIsNftClaimAvailable.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import create from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
interface NFTClaim {
|
||||
isClaimAvailable: boolean
|
||||
setIsClaimAvailable: (isClaimAvailable: boolean) => void
|
||||
}
|
||||
|
||||
export const useIsNftClaimAvailable = create<NFTClaim>()(
|
||||
devtools(
|
||||
(set) => ({
|
||||
isClaimAvailable: false,
|
||||
setIsClaimAvailable: (isClaimAvailable: boolean) => {
|
||||
set(() => ({ isClaimAvailable }))
|
||||
},
|
||||
}),
|
||||
{ name: 'useIsNftClaimAvailable' }
|
||||
)
|
||||
)
|
24
src/nft/queries/genie/GetAirdorpMerkle.ts
Normal file
24
src/nft/queries/genie/GetAirdorpMerkle.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Rewards } from 'nft/types/airdrop'
|
||||
|
||||
interface CollectionrRewardsResponse {
|
||||
data: Array<Rewards>
|
||||
}
|
||||
|
||||
export const CollectionRewardsFetcher = async (address: string): Promise<CollectionrRewardsResponse> => {
|
||||
const url = `${process.env.REACT_APP_TEMP_API_URL}/nft/rewards/${address}?category=GENIE_UNISWAP_USDC_AIRDROP`
|
||||
|
||||
const controller = new AbortController()
|
||||
|
||||
const timeoutId = setTimeout(() => controller.abort(), 3000)
|
||||
|
||||
const r = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
clearInterval(timeoutId)
|
||||
const data = await r.json()
|
||||
|
||||
return data
|
||||
}
|
14
src/nft/types/airdrop/index.ts
Normal file
14
src/nft/types/airdrop/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export enum Airdrop {
|
||||
LOOKS_RARE_NFT_COMMERCE_REWARDS = 'LOOKS_RARE_NFT_COMMERCE_REWARDS',
|
||||
GENIE_UNISWAP_USDC_AIRDROP = 'GENIE_UNISWAP_USDC_AIRDROP',
|
||||
}
|
||||
|
||||
export interface Rewards {
|
||||
amount: string
|
||||
walletAddress: string
|
||||
tokenAddress: string
|
||||
merkleProof: Array<string>
|
||||
rewardType: Airdrop
|
||||
chainId: number
|
||||
index: number
|
||||
}
|
@ -83,6 +83,7 @@ export const colors = {
|
||||
blueVibrant: '#587BFF',
|
||||
// TODO: add magenta 50-900
|
||||
magentaVibrant: '#FC72FF',
|
||||
purple300: '#8440F2',
|
||||
purple900: '#1C0337',
|
||||
// TODO: add all other vibrant variations
|
||||
networkEthereum: '#627EEA',
|
||||
|
Loading…
Reference in New Issue
Block a user