mango-v4-ui/components/rewards/Claim.tsx

333 lines
11 KiB
TypeScript
Raw Normal View History

import {
Claim,
Distribution,
MangoMintsRedemptionClient,
} from '@blockworks-foundation/mango-mints-redemption'
import dynamic from 'next/dynamic'
import { web3 } from '@project-serum/anchor'
import { useWallet } from '@solana/wallet-adapter-react'
import { PublicKey } from '@solana/web3.js'
import mangoStore from '@store/mangoStore'
import { useCurrentSeason, useDistribution } from 'hooks/useRewards'
import { chunk } from 'lodash'
2023-09-28 04:02:32 -07:00
import { useState, useEffect, useCallback, useMemo } from 'react'
import {
TransactionInstructionWithSigners,
SequenceType,
} from '@blockworks-foundation/mangolana/lib/globalTypes'
import {
TransactionInstructionWithType,
sendSignAndConfirmTransactions,
} from '@blockworks-foundation/mangolana/lib/transactions'
import useJupiterMints from 'hooks/useJupiterMints'
import { Token } from 'types/jupiter'
import {
Metaplex,
Nft,
NftWithToken,
Sft,
SftWithToken,
} from '@metaplex-foundation/js'
import Loading from '@components/shared/Loading'
2023-09-28 04:02:32 -07:00
import dayjs from 'dayjs'
2023-09-28 04:30:25 -07:00
import HolographicCard from './HolographicCard'
2023-09-27 22:20:26 -07:00
const CLAIM_BUTTON_CLASSES =
'raised-button font-rewards mx-auto mt-6 block rounded-lg px-6 py-3 text-xl focus:outline-none'
const WINNER_TITLES = [
'Congratulations',
'Happy Days',
'Chicken Dinner',
'Well Played',
'Nailed It',
'Bravo',
]
const RewardsComponent = dynamic(() => import('./RewardsComponents'), {
loading: () => (
<div className="flex h-full w-full items-center justify-center">
<Loading></Loading>
</div>
),
})
const ClaimPage = () => {
const [isClaiming, setIsClaiming] = useState(false)
const [claimProgress, setClaimProgress] = useState(0)
const [distribution, setDistribution] = useState<Distribution | undefined>(
undefined,
)
const { jupiterTokens } = useJupiterMints()
2023-09-27 22:20:26 -07:00
const [winnerTitle] = useState(
WINNER_TITLES[Math.floor(Math.random() * WINNER_TITLES.length)],
)
const [showRender, setShowRender] = useState(false)
const [rewardsWasShown, setRewardsWasShow] = useState(false)
const [claims, setClaims] = useState<Claim[] | undefined>([])
const [claimed, setClaimed] = useState<PublicKey[] | undefined>([])
const [loadingMetadata, setLoadingMetadata] = useState(false)
const [tokenRewardsInfo, setTokensRewardsInfo] = useState<Token[]>([])
const [nftsRewardsInfo, setNftsRewardsInfo] = useState<
(Sft | SftWithToken | Nft | NftWithToken)[]
>([])
const [rewardsClient, setRewardsClient] = useState<
MangoMintsRedemptionClient | undefined
>(undefined)
const { client } = mangoStore()
const { publicKey } = useWallet()
const { data: seasonData } = useCurrentSeason()
const currentSeason = seasonData?.season_id
const previousSeason = currentSeason ? currentSeason - 1 : null
//needed for tx sign
const wallet = useWallet()
const provider = client.program.provider
const connection = provider.connection
const { data: distributionDataAndClient, refetch } = useDistribution(
previousSeason!,
)
2023-09-28 04:02:32 -07:00
const claimEndsIn = useMemo(() => {
if (!distributionDataAndClient?.distribution) return
const start = distributionDataAndClient.distribution.start.getTime()
return dayjs().to(
start + distributionDataAndClient.distribution.duration * 1000,
)
}, [distributionDataAndClient])
useEffect(() => {
const handleSetDistribution = async () => {
setDistribution(distributionDataAndClient!.distribution)
setClaims(distributionDataAndClient!.distribution!.getClaims(publicKey!))
setClaimed(await distributionDataAndClient!.distribution!.getClaimed())
setRewardsClient(distributionDataAndClient!.client)
}
if (distributionDataAndClient && publicKey) {
handleSetDistribution()
} else {
setDistribution(undefined)
setClaims(undefined)
setClaimed(undefined)
setRewardsClient(undefined)
}
}, [distributionDataAndClient, publicKey])
const startShowRewards = () => {
setShowRender(true)
setRewardsWasShow(true)
}
const handleTokenMetadata = useCallback(async () => {
if (claims?.length && connection && jupiterTokens.length) {
setLoadingMetadata(true)
const metaplex = new Metaplex(connection)
const tokens = claims!
.filter((x) => x.mintProperties.type === 'token')
.map((t) => jupiterTokens.find((x) => x.address === t.mint.toBase58()))
.filter((x) => x)
.map((x) => x as Token)
const nfts = claims!.filter((x) => x.mintProperties.type === 'nft')
const nftsInfos: (Sft | SftWithToken | Nft | NftWithToken)[] = []
for (const nft of nfts) {
const metadataPDA = await metaplex
.nfts()
.pdas()
.metadata({ mint: nft.mint })
const tokenMetadata = await metaplex.nfts().findByMetadata({
metadata: metadataPDA,
})
nftsInfos.push(tokenMetadata)
}
setNftsRewardsInfo(nftsInfos)
setTokensRewardsInfo(tokens)
setLoadingMetadata(false)
}
}, [claims, connection, jupiterTokens])
useEffect(() => {
if (claims) {
handleTokenMetadata()
}
}, [claims, handleTokenMetadata])
const handleClaimRewards = useCallback(async () => {
if (!distribution || !publicKey || !claims || !rewardsClient) return
const transactionInstructions: TransactionInstructionWithType[] = []
// Create claim account if it doesn't exist
if (claimed === undefined) {
transactionInstructions.push({
instructionsSet: [
new TransactionInstructionWithSigners(
await rewardsClient.program.methods
.claimAccountCreate()
.accounts({
distribution: distribution.publicKey,
claimAccount: distribution.findClaimAccountAddress(publicKey!),
claimant: publicKey!,
payer: publicKey!,
systemProgram: web3.SystemProgram.programId,
rent: web3.SYSVAR_RENT_PUBKEY,
})
.instruction(),
),
],
sequenceType: SequenceType.Sequential,
})
}
try {
const claimIxes: TransactionInstructionWithSigners[] = []
for (const claim of claims) {
if (claimed !== undefined) {
const alreadyClaimed =
claimed.find((c) => c.equals(claim.mint)) !== undefined
if (alreadyClaimed) {
continue
}
}
const ixs = (
await distribution.makeClaimInstructions(publicKey!, claim.mint)
).map(
(ix: web3.TransactionInstruction) =>
new TransactionInstructionWithSigners(ix),
)
claimIxes.push(...ixs)
}
chunk(claimIxes, 4).map((x) =>
transactionInstructions.push({
instructionsSet: x,
sequenceType: SequenceType.Parallel,
}),
)
setIsClaiming(true)
setClaimProgress(10)
await sendSignAndConfirmTransactions({
connection,
wallet,
transactionInstructions,
callbacks: {
afterFirstBatchSign: (signedCount) => {
console.log('afterFirstBatchSign', signedCount)
},
afterBatchSign: (signedCount) => {
console.log('afterBatchSign', signedCount)
},
afterAllTxConfirmed: () => {
console.log('afterAllTxConfirmed')
setClaimProgress(100)
refetch()
},
afterEveryTxConfirmation: () => {
console.log('afterEveryTxConfirmation')
setClaimProgress(
claimProgress + 90 / transactionInstructions.length,
)
},
onError: (e, notProcessedTransactions, originalProps) => {
console.log('error', e, notProcessedTransactions, originalProps)
},
},
config: {
maxTxesInBatch: 10,
autoRetry: false,
logFlowInfo: true,
},
})
} catch (e) {
console.error(e)
} finally {
setIsClaiming(false)
}
}, [distribution, wallet, claims, rewardsClient, connection])
return claims === undefined ? (
2023-09-27 21:54:13 -07:00
<div className="flex min-h-[calc(100vh-94px)] items-center justify-center">
<Loading />
</div>
) : showRender ? (
<div className="fixed bottom-0 left-0 right-0 top-0 z-[1000]">
<RewardsComponent
tokensInfo={tokenRewardsInfo}
nftsRewardsInfo={nftsRewardsInfo}
claims={claims}
setShowRender={setShowRender}
2023-09-28 05:10:05 -07:00
/>
</div>
) : (
2023-09-27 21:54:13 -07:00
<div className="min-h-[calc(100vh-94px)]">
2023-09-28 04:30:25 -07:00
<div className="flex items-center justify-center pt-8">
2023-09-28 05:10:05 -07:00
<div className="flex items-center justify-center rounded-full bg-gradient-to-br from-yellow-400 to-red-400 px-4 py-1">
<p className="font-rewards text-lg text-black">
2023-09-28 04:02:32 -07:00
Season {previousSeason} claim ends <span>{claimEndsIn}</span>
</p>
</div>
</div>
2023-09-28 05:10:05 -07:00
<div className="flex h-[calc(100vh-164px)] flex-col justify-center">
2023-09-28 04:30:25 -07:00
<div className="mx-auto flex max-w-[1140px] flex-col items-center justify-center px-8 lg:px-10">
<div className="-mt-16">
<HolographicCard />
</div>
<div className="-mt-8 mb-6 text-center">
2023-09-28 04:02:32 -07:00
<h2 className="mb-1 font-rewards text-4xl tracking-wide sm:text-6xl">
2023-09-27 22:20:26 -07:00
{winnerTitle}!
</h2>
<p className="text-lg font-bold text-th-fgd-1">
You&apos;re a winner in Season {previousSeason}
</p>
</div>
{isClaiming ? (
2023-09-27 21:54:13 -07:00
<div className="pt-1">
<div className="flex h-4 w-full flex-grow rounded-full bg-th-bkg-4">
<div
style={{
width: `${claimProgress}%`,
}}
className={`flex rounded-full bg-gradient-to-r from-green-600 to-green-400`}
></div>
</div>
<div className="mx-auto mt-3 text-center text-base">
{`Loading prizes: ${claimProgress.toFixed(0)}%`}
</div>
</div>
) : rewardsWasShown ? (
2023-09-27 21:54:13 -07:00
<button
className={CLAIM_BUTTON_CLASSES}
onClick={() => handleClaimRewards()}
>
<span className="mt-1">{`Claim ${claims.length} Prize${
claims.length > 1 ? 's' : ''
}`}</span>
2023-09-27 21:54:13 -07:00
</button>
) : (
2023-09-27 21:54:13 -07:00
<button
disabled={loadingMetadata}
className={CLAIM_BUTTON_CLASSES}
onClick={() => startShowRewards()}
>
<span className="mt-1">
{' '}
{loadingMetadata ? (
<Loading className="w-3"></Loading>
) : (
'Reveal Prizes'
)}
</span>
2023-09-27 21:54:13 -07:00
</button>
)}
</div>
</div>
2023-09-27 21:54:13 -07:00
</div>
)
}
export default ClaimPage