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' 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' import dayjs from 'dayjs' import { onClick, unmute } from 'lib/render' import { usePlausible } from 'next-plausible' import { TelemetryEvents } from 'utils/telemetry' import { Prize, getClaimsAsPrizes } from './RewardsComponents' import { notify } from 'utils/notifications' import { sleep } from 'utils' const CLAIM_BUTTON_CLASSES = 'raised-button group mx-auto block h-12 px-6 pt-1 font-rewards text-xl after:rounded-lg focus:outline-none lg:h-14' const WINNER_TITLES = [ 'Congratulations', 'Happy Days', 'Chicken Dinner', 'Well Played', 'Nailed It', 'Bravo', ] const RewardsComponent = dynamic(() => import('./RewardsComponents'), { loading: () => (
), }) const ClaimPage = () => { const [isClaiming, setIsClaiming] = useState(false) const [claimProgress, setClaimProgress] = useState(0) const [distribution, setDistribution] = useState( undefined, ) const { jupiterTokens } = useJupiterMints() 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([]) const [loadingClaims, setLoadingClaims] = useState(false) const [claimed, setClaimed] = useState([]) const [loadingMetadata, setLoadingMetadata] = useState(false) const [tokenRewardsInfo, setTokensRewardsInfo] = useState([]) const [nftsRewardsInfo, setNftsRewardsInfo] = useState< (Sft | SftWithToken | Nft | NftWithToken)[] >([]) const [rewardsClient, setRewardsClient] = useState< MangoMintsRedemptionClient | undefined >(undefined) const [prizes, setPrizes] = useState([]) 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 telemetry = usePlausible() const { data: distributionDataAndClient, refetch } = useDistribution( previousSeason!, ) 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 () => { setLoadingClaims(true) setDistribution(distributionDataAndClient?.distribution) setClaims(distributionDataAndClient?.distribution?.getClaims(publicKey!)) setClaimed(await distributionDataAndClient?.distribution?.getClaimed()) setRewardsClient(distributionDataAndClient?.client) setLoadingClaims(false) } if (distributionDataAndClient && publicKey) { handleSetDistribution() } else { setDistribution(undefined) setClaims(undefined) setClaimed(undefined) setRewardsClient(undefined) } }, [distributionDataAndClient, publicKey]) const startShowRewards = () => { telemetry('rewardsOpenRender') setShowRender(true) setRewardsWasShow(true) onClick() unmute() } 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.toLowerCase() === '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.toLowerCase() === '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) { const claimAccountPk = distribution.findClaimAccountAddress(publicKey!) const isCreated = (await connection.getBalance(claimAccountPk)) > 1 if (!isCreated) { 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, 2).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: async () => { console.log('afterAllTxConfirmed') notify({ type: 'success', title: 'All rewards successfully claimed', }) setClaimProgress(100) await sleep(1000) refetch() }, afterEveryTxConfirmation: () => { console.log('afterEveryTxConfirmation') setClaimProgress( claimProgress + 90 / transactionInstructions.length, ) }, onError: (e, notProcessedTransactions, originalProps) => { console.log('error', e, notProcessedTransactions, originalProps) notify({ title: 'Transaction failed', description: e.message, txid: e?.txid, type: 'error', }) }, }, config: { maxTxesInBatch: 10, autoRetry: false, logFlowInfo: true, }, }) telemetry('rewardsClaim', { props: { rewards: claims.length } }) } catch (e) { telemetry('rewardsClaimError', { props: { message: (e as Error).toString() }, }) console.error(e) } finally { setIsClaiming(false) } }, [ distribution, publicKey, claims, rewardsClient, claimed, connection, wallet, telemetry, refetch, claimProgress, ]) useEffect(() => { if (tokenRewardsInfo.length && claims?.length) { const claimsAsPrizes = getClaimsAsPrizes( claims, tokenRewardsInfo, nftsRewardsInfo, ) setPrizes(claimsAsPrizes) } }, [claims, nftsRewardsInfo, tokenRewardsInfo]) return claims === undefined && !loadingClaims ? (
Something went wrong. Try refreshing the page
) : loadingClaims ? (
) : (

Season {previousSeason} claim ends {claimEndsIn}

{prizes.length && rewardsWasShown ? (
{prizes.map((prize, i) => { const { info, item, itemUrl, rarity, stencilUrl } = prize return (

{item}

{rarity}

{info}

) })}
) : ( <>

{winnerTitle}!

You're a winner in Season {previousSeason}

)}
{isClaiming ? (

{`Claiming prizes: ${claimProgress.toFixed(0)}%`}

) : rewardsWasShown ? ( ) : ( )}
) } export default ClaimPage