diff --git a/components/SerumGov/DepositCard.tsx b/components/SerumGov/DepositCard.tsx deleted file mode 100644 index 4f5070925e..0000000000 --- a/components/SerumGov/DepositCard.tsx +++ /dev/null @@ -1,256 +0,0 @@ -import * as yup from 'yup' -import * as anchor from '@coral-xyz/anchor' -import { yupResolver } from '@hookform/resolvers/yup' -import useTokenAccountBalance from '@hooks/useTokenAccountBalance' -import useWalletDeprecated from '@hooks/useWalletDeprecated' -import useSerumGovStore, { - MSRM_DECIMALS, - SRM_DECIMALS, -} from 'stores/useSerumGovStore' -import { SubmitHandler, useForm } from 'react-hook-form' -import { notify } from '@utils/notifications' -import { - fmtBnMintDecimals, - parseMintNaturalAmountFromDecimalAsBN, -} from '@tools/sdk/units' -import { useMemo, useState } from 'react' -import Loading from '@components/Loading' -import { - getInstructionDataFromBase64, - Governance, - ProgramAccount, - serializeInstructionToBase64, -} from '@solana/spl-governance' -import { PublicKey, Transaction, TransactionInstruction } from '@solana/web3.js' -import useCreateProposal from '@hooks/useCreateProposal' -import useQueryContext from '@hooks/useQueryContext' -import useRealm from '@hooks/useRealm' -import { useRouter } from 'next/router' -import { getAssociatedTokenAddress } from '@blockworks-foundation/mango-v4' -import { InstructionDataWithHoldUpTime } from 'actions/createProposal' -import useLegacyConnectionContext from '@hooks/useLegacyConnectionContext' - -type DepositCardProps = { - mint: 'SRM' | 'MSRM' - callback?: () => Promise - createProposal?: { - governance?: ProgramAccount - owner: PublicKey - } -} - -const DepositLockedSRMSchema = { - amount: yup.string().required(), -} - -type DepositLockedSRMFormValues = { - amount: number -} - -const DepositCard = ({ mint, callback, createProposal }: DepositCardProps) => { - const router = useRouter() - const { symbol } = useRealm() - const { fmtUrlWithCluster } = useQueryContext() - - const { wallet, anchorProvider } = useWalletDeprecated() - - const connection = useLegacyConnectionContext() - const actions = useSerumGovStore((s) => s.actions) - const { srmMint, msrmMint } = useSerumGovStore((s) => ({ - srmMint: s.srmMint, - msrmMint: s.msrmMint, - })) - - const MINT_MAP = useMemo( - () => ({ - SRM: { pubkey: srmMint, decimals: SRM_DECIMALS }, - MSRM: { pubkey: msrmMint, decimals: MSRM_DECIMALS }, - }), - [srmMint, msrmMint] - ) - - const { - balance, - loading: isLoading, - mutate: mutateBalance, - } = useTokenAccountBalance( - createProposal ? createProposal.owner : wallet?.publicKey, - MINT_MAP[mint].pubkey - ) - - const [isDepositing, setIsDepositing] = useState(false) - - const { handleCreateProposal } = useCreateProposal() - - const schema = yup.object(DepositLockedSRMSchema).required() - const { register, handleSubmit } = useForm({ - mode: 'all', - resolver: yupResolver(schema), - defaultValues: { - amount: 0, - }, - }) - - const handleDeposit: SubmitHandler = async ({ - amount, - }) => { - if (!balance || !wallet || !wallet.publicKey || isLoading) { - notify({ - type: 'error', - message: 'Something went wrong. Please try refreshing.', - }) - return - } - - setIsDepositing(true) - - const amountAsBN = parseMintNaturalAmountFromDecimalAsBN( - amount, - MINT_MAP[mint].decimals - ) - if (amountAsBN.gt(new anchor.BN(balance.amount))) { - notify({ - type: 'error', - message: `You do not have enough ${mint} to lock`, - }) - setIsDepositing(false) - return - } - - if (!createProposal) { - await actions.depositLocked( - connection.current, - anchorProvider, - amountAsBN, - mint === 'MSRM', - wallet - ) - - await mutateBalance() - if (callback) await callback() - } else { - try { - const instructions: TransactionInstruction[] = [] - const { owner } = createProposal - - const userAccount = await actions.getUserAccount(anchorProvider, owner) - - if (!userAccount) { - const initIx = await actions.getInitUserInstruction( - owner, - owner, - anchorProvider - ) - instructions.push(initIx) - } - - const ownerAta = await getAssociatedTokenAddress( - MINT_MAP[mint].pubkey, - owner, - true - ) - - const depositIx = await actions.getGrantLockedInstruction( - owner, - owner, - ownerAta, - anchorProvider, - amountAsBN, - MINT_MAP[mint].pubkey.toBase58() === - MINT_MAP['MSRM'].pubkey.toBase58() - ) - instructions.push(depositIx) - - const instructionsData: InstructionDataWithHoldUpTime[] = [] - - instructions.forEach(async (ix) => { - const serializedIx = serializeInstructionToBase64(ix) - - const ixData = { - data: getInstructionDataFromBase64(serializedIx), - holdUpTime: - createProposal.governance?.account.config - .minInstructionHoldUpTime, - prerequisiteInstructions: [], - } - - instructionsData.push(ixData) - }) - - const tx = new Transaction({ feePayer: createProposal.owner }).add( - ...instructions.map((i) => i) - ) - const simulationResult = await connection.current.simulateTransaction( - tx - ) - - if (simulationResult.value.err) { - notify({ - type: 'error', - message: 'Transaction simulation failed.', - }) - // setIsBurning(false) - return - } - const proposalAddress = await handleCreateProposal({ - title: `Serum DAO: Lock ${fmtBnMintDecimals( - amountAsBN, - MINT_MAP[mint].decimals - )} ${mint}`, - description: `Locking ${fmtBnMintDecimals( - amountAsBN, - MINT_MAP[mint].decimals - )} ${mint}.`, - instructionsData, - governance: createProposal.governance!, - }) - const url = fmtUrlWithCluster( - `/dao/${symbol}/proposal/${proposalAddress}` - ) - await router.push(url) - } catch (e) { - console.error('Failed to add Lock Proposal', e) - notify({ - type: 'error', - message: `Something went wrong. Please check console.`, - }) - } - } - - setIsDepositing(false) - } - - if (!wallet || !balance || !balance.uiAmount || isLoading) return null - - return ( -
-
-

Lock SRM

-

Balance: {balance.uiAmount}

-
-
- - -
-
- ) -} - -export default DepositCard diff --git a/components/SerumGov/LockedAccount.tsx b/components/SerumGov/LockedAccount.tsx deleted file mode 100644 index 10eb3cacfb..0000000000 --- a/components/SerumGov/LockedAccount.tsx +++ /dev/null @@ -1,265 +0,0 @@ -import * as yup from 'yup' -import * as anchor from '@coral-xyz/anchor' -import { yupResolver } from '@hookform/resolvers/yup' -import { - fmtBnMintDecimals, - parseMintNaturalAmountFromDecimalAsBN, -} from '@tools/sdk/units' -import classNames from 'classnames' -import { FC, useState } from 'react' -import { SubmitHandler, useForm } from 'react-hook-form' -import useSerumGovStore, { - LockedAccountType, - SRM_DECIMALS, -} from 'stores/useSerumGovStore' -import { notify } from '@utils/notifications' -import useWalletDeprecated from '@hooks/useWalletDeprecated' -import { useRouter } from 'next/router' -import useRealm from '@hooks/useRealm' -import useQueryContext from '@hooks/useQueryContext' -import useCreateProposal from '@hooks/useCreateProposal' -import { - getInstructionDataFromBase64, - Governance, - ProgramAccount, - serializeInstructionToBase64, -} from '@solana/spl-governance' -import { PublicKey, TokenAmount } from '@solana/web3.js' -import Loading from '@components/Loading' -import { dryRunInstruction } from 'actions/dryRunInstruction' -import Link from 'next/link' -import { getExplorerUrl } from '@components/explorer/tools' -import { ExternalLinkIcon } from '@heroicons/react/outline' -import { useConnection } from '@solana/wallet-adapter-react' - -const BurnLockedAccountSchema = { - amount: yup.string().required(), -} - -type BurnLockedAccountFormValues = { - amount: number -} - -type Props = { - account: LockedAccountType - gsrmBalance?: TokenAmount | null - callback?: () => Promise - createProposal?: { - governance?: ProgramAccount - owner: PublicKey - } -} -const LockedAccount: FC = ({ - account, - createProposal, - gsrmBalance, - callback, -}) => { - const router = useRouter() - const { cluster } = router.query - - const { symbol } = useRealm() - const { fmtUrlWithCluster } = useQueryContext() - - const actions = useSerumGovStore((s) => s.actions) - - const { anchorProvider, wallet } = useWalletDeprecated() - const { connection } = useConnection() - - const [isBurning, setIsBurning] = useState(false) - - const { handleCreateProposal } = useCreateProposal() - - const schema = yup.object(BurnLockedAccountSchema).required() - const { - register, - handleSubmit, - formState: { errors }, - } = useForm({ - mode: 'all', - resolver: yupResolver(schema), - defaultValues: { - amount: 0, - }, - }) - - const handleBurn: SubmitHandler = async ({ - amount, - }) => { - if ( - !gsrmBalance || - isNaN(parseFloat(amount.toString())) || - !wallet || - !wallet.publicKey - ) { - notify({ - type: 'error', - message: 'Something went wrong. Please try refreshing.', - }) - return - } - setIsBurning(true) - const amountAsBN = parseMintNaturalAmountFromDecimalAsBN( - amount, - SRM_DECIMALS - ) - // Check if amount > balance - if (amountAsBN.gt(new anchor.BN(gsrmBalance.amount))) { - notify({ - type: 'error', - message: 'You do not have enough gSRM to redeem', - }) - setIsBurning(false) - return - } - // Check if amount > (total - burned) - if (amountAsBN.gt(account.totalGsrmAmount.sub(account.gsrmBurned))) { - notify({ - type: 'error', - message: `Only ${fmtBnMintDecimals( - account.totalGsrmAmount.sub(account.gsrmBurned), - SRM_DECIMALS - )} gSRM can be redeemed`, - }) - setIsBurning(false) - return - } - if (!createProposal) { - await actions.burnLockedGsrm( - connection, - anchorProvider, - account, - amountAsBN, - wallet - ) - if (callback) await callback() - } else { - const ix = await actions.getBurnLockedGsrmInstruction( - anchorProvider, - account, - amountAsBN, - createProposal.owner - ) - - const serializedIx = serializeInstructionToBase64(ix) - - const instructionData = { - data: getInstructionDataFromBase64(serializedIx), - holdUpTime: - createProposal.governance?.account.config.minInstructionHoldUpTime, - prerequisiteInstructions: [], - } - - const { response: dryRunResponse } = await dryRunInstruction( - connection, - wallet!, - instructionData.data - ) - if (dryRunResponse.err) { - notify({ type: 'error', message: 'Transaction Simulation Failed' }) - setIsBurning(false) - return - } - - const proposalAddress = await handleCreateProposal({ - title: `Serum DAO: Redeeming ${amount} gSRM`, - description: `Redeeming ${amount} gSRM to redeem vested ${ - account.isMsrm ? 'MSRM' : 'SRM' - }.`, - instructionsData: [instructionData], - governance: createProposal.governance!, - }) - const url = fmtUrlWithCluster( - `/dao/${symbol}/proposal/${proposalAddress}` - ) - await router.push(url) - } - setIsBurning(false) - } - - return ( -
-
-
-

Locked

- - - - - -
-
- {account.isMsrm ? 'MSRM' : 'SRM'} -
-
-
-

Redeemable gSRM

-

- <> - {fmtBnMintDecimals( - account.totalGsrmAmount.sub(account.gsrmBurned), - SRM_DECIMALS - )} - /{fmtBnMintDecimals(account.totalGsrmAmount, SRM_DECIMALS)} - -

-
-
- - - -
- {errors.amount ?

{errors.amount.message}

: null} -
- ) -} - -export default LockedAccount diff --git a/components/SerumGov/Ticket.tsx b/components/SerumGov/Ticket.tsx deleted file mode 100644 index 51da1cfba5..0000000000 --- a/components/SerumGov/Ticket.tsx +++ /dev/null @@ -1,315 +0,0 @@ -import { getAssociatedTokenAddress } from '@blockworks-foundation/mango-v4' -import { getExplorerUrl } from '@components/explorer/tools' -import Loading from '@components/Loading' -import { ExternalLinkIcon } from '@heroicons/react/outline' -import useCreateProposal from '@hooks/useCreateProposal' -import useQueryContext from '@hooks/useQueryContext' -import useRealm from '@hooks/useRealm' -import useWalletDeprecated from '@hooks/useWalletDeprecated' -import { - getInstructionDataFromBase64, - Governance, - ProgramAccount, - serializeInstructionToBase64, -} from '@solana/spl-governance' -import { useConnection } from '@solana/wallet-adapter-react' -import { PublicKey, Transaction, TransactionInstruction } from '@solana/web3.js' -import { fmtBnMintDecimals } from '@tools/sdk/units' -import { createAssociatedTokenAccount } from '@utils/associated' -import { fmtSecsToTime, fmtTimeToString } from '@utils/formatting' -import { notify } from '@utils/notifications' -import { InstructionDataWithHoldUpTime } from 'actions/createProposal' -import { BigNumber } from 'bignumber.js' -import classNames from 'classnames' -import Link from 'next/link' -import { useRouter } from 'next/router' -import { FC, useEffect, useState } from 'react' -import useSerumGovStore, { - ClaimTicketType, - MSRM_DECIMALS, - RedeemTicketType, - SRM_DECIMALS, -} from 'stores/useSerumGovStore' - -type TicketType = ClaimTicketType | RedeemTicketType - -function isClaimTicket( - toBeDetermined: TicketType -): toBeDetermined is ClaimTicketType { - if ((toBeDetermined as ClaimTicketType).claimDelay) { - return true - } - return false -} - -type Props = { - ticket: TicketType - callback?: () => Promise - createProposal?: { - governance?: ProgramAccount - owner: PublicKey - } -} -const Ticket: FC = ({ ticket, createProposal, callback }) => { - const router = useRouter() - const { cluster } = router.query - - const { symbol } = useRealm() - const { fmtUrlWithCluster } = useQueryContext() - - const { connection } = useConnection() - const { anchorProvider, wallet } = useWalletDeprecated() - - const actions = useSerumGovStore((s) => s.actions) - const gsrmMint = useSerumGovStore((s) => s.gsrmMint) - - const [isClaiming, setIsClaiming] = useState(false) - - const [timeRemaining, setTimeRemaining] = useState(null) // NOTE: set to high number to disable initially - - const { handleCreateProposal } = useCreateProposal() - - useEffect(() => { - const timeRemainingInterval = setInterval(() => { - const currentTimestamp = Math.floor(Date.now() / 1000) - const endTimestamp = - ticket.createdAt + - (isClaimTicket(ticket) ? ticket.claimDelay : ticket.redeemDelay) - - setTimeRemaining(endTimestamp - currentTimestamp) - }, 1000) - - return () => clearInterval(timeRemainingInterval) - }, [ticket]) - - const handleButton = async (ticket: TicketType) => { - if (wallet && wallet.publicKey) { - setIsClaiming(true) - // If claim ticket - if (isClaimTicket(ticket)) { - if (!createProposal) { - // If sendTransaction (for user wallets) - await actions.claim(connection, anchorProvider, ticket, wallet) - if (callback) await callback() - } else { - // else create proposal (for DAO wallets); - try { - const instructions: TransactionInstruction[] = [] - const { owner } = createProposal - const ownerAta = await getAssociatedTokenAddress( - gsrmMint, - owner, - true - ) - // Adding createATA if not already exists - try { - await connection.getTokenAccountBalance(ownerAta, 'confirmed') - } catch (e) { - const [ix] = await createAssociatedTokenAccount( - owner, - owner, - gsrmMint - ) - instructions.push(ix) - } - const ix = await actions.getClaimInstruction( - anchorProvider, - ticket, - createProposal.owner - ) - instructions.push(ix) - - const instructionsData: InstructionDataWithHoldUpTime[] = [] - - instructions.forEach(async (ix) => { - const serializedIx = serializeInstructionToBase64(ix) - - const ixData = { - data: getInstructionDataFromBase64(serializedIx), - holdUpTime: - createProposal.governance?.account.config - .minInstructionHoldUpTime, - prerequisiteInstructions: [], - } - - instructionsData.push(ixData) - }) - - const tx = new Transaction({ feePayer: owner }).add( - ...instructions.map((i) => i) - ) - const simulationResult = await connection.simulateTransaction(tx) - - if (simulationResult.value.err) { - notify({ - type: 'error', - message: 'Transaction simulation failed.', - }) - // setIsBurning(false) - return - } - const proposalAddress = await handleCreateProposal({ - title: `Serum DAO: Claim ${fmtBnMintDecimals( - ticket.gsrmAmount, - SRM_DECIMALS - )} gSRM`, - description: `Claiming ticketId: ${ticket.address.toBase58()}`, - instructionsData, - governance: createProposal.governance!, - }) - const url = fmtUrlWithCluster( - `/dao/${symbol}/proposal/${proposalAddress}` - ) - await router.push(url) - } catch (ex) { - console.error('Failed to add Claim Proposal', ex) - notify({ - type: 'error', - message: `Something went wrong. Please check console.`, - }) - } - } - } else { - if (!createProposal) { - await actions.redeem(connection, anchorProvider, ticket, wallet) - if (callback) await callback() - } else { - try { - const instructions: TransactionInstruction[] = [] - const { owner } = createProposal - const ownerAta = await getAssociatedTokenAddress( - gsrmMint, - owner, - true - ) - // Adding createATA if not already exists - try { - await connection.getTokenAccountBalance(ownerAta, 'confirmed') - } catch (e) { - const [ix] = await createAssociatedTokenAccount( - owner, - owner, - gsrmMint - ) - instructions.push(ix) - } - - const ix = await actions.getRedeemInstruction( - anchorProvider, - ticket, - createProposal.owner - ) - instructions.push(ix) - - const instructionsData: InstructionDataWithHoldUpTime[] = [] - - instructions.forEach(async (ix) => { - const serializedIx = serializeInstructionToBase64(ix) - - const ixData = { - data: getInstructionDataFromBase64(serializedIx), - holdUpTime: - createProposal.governance?.account.config - .minInstructionHoldUpTime, - prerequisiteInstructions: [], - } - - instructionsData.push(ixData) - }) - - const tx = new Transaction().add(...instructions.map((i) => i)) - const simulationResult = await connection.simulateTransaction(tx) - - if (simulationResult.value.err) { - notify({ - type: 'error', - message: 'Transaction simulation failed.', - }) - // setIsBurning(false) - return - } - - const proposalAddress = await handleCreateProposal({ - title: `Serum DAO: Redeem ${new BigNumber( - ticket.amount.toString() - ) - .shiftedBy(-1 * (ticket.isMsrm ? MSRM_DECIMALS : SRM_DECIMALS)) - .toFormat()} ${ticket.isMsrm ? 'MSRM' : 'SRM'}`, - description: `Redeeming ticketId: ${ticket.address.toBase58()}`, - instructionsData, - governance: createProposal.governance!, - }) - const url = fmtUrlWithCluster( - `/dao/${symbol}/proposal/${proposalAddress}` - ) - await router.push(url) - } catch (ex) { - console.error('Failed to add Claim Proposal', ex) - notify({ - type: 'error', - message: `Something went wrong. Please check console.`, - }) - } - } - } - setIsClaiming(false) - } else { - notify({ type: 'error', message: 'Wallet not connected.' }) - } - } - - return ( -
-
-

- {isClaimTicket(ticket) ? 'Claim Ticket' : 'Redeem Ticket'} -

- - - - - -
-
- {gsrmMint && ( -

- {isClaimTicket(ticket) - ? `${fmtBnMintDecimals(ticket.gsrmAmount, SRM_DECIMALS)}` - : `${new BigNumber(ticket.amount.toString()) - .shiftedBy( - -1 * (ticket.isMsrm ? MSRM_DECIMALS : SRM_DECIMALS) - ) - .toFormat()}`}{' '} - {isClaimTicket(ticket) ? 'gSRM' : ticket.isMsrm ? 'MSRM' : 'SRM'} -

- )} - {timeRemaining !== null && ( - - )} -
-
- ) -} - -export default Ticket diff --git a/components/SerumGov/VestAccount.tsx b/components/SerumGov/VestAccount.tsx deleted file mode 100644 index 3f7a3c01b4..0000000000 --- a/components/SerumGov/VestAccount.tsx +++ /dev/null @@ -1,301 +0,0 @@ -import * as yup from 'yup' -import * as anchor from '@coral-xyz/anchor' -import { yupResolver } from '@hookform/resolvers/yup' -import useWalletDeprecated from '@hooks/useWalletDeprecated' -import { - fmtBnMintDecimals, - parseMintNaturalAmountFromDecimalAsBN, -} from '@tools/sdk/units' -import classNames from 'classnames' -import { FC, useEffect, useMemo, useState } from 'react' -import useSerumGovStore, { - MSRM_MULTIPLIER, - SRM_DECIMALS, - VestAccountType, -} from 'stores/useSerumGovStore' -import { SubmitHandler, useForm } from 'react-hook-form' -import { notify } from '@utils/notifications' -import { MSRM_DECIMALS } from '@project-serum/serum/lib/token-instructions' -import { BigNumber } from 'bignumber.js' -import { - getInstructionDataFromBase64, - Governance, - ProgramAccount, - serializeInstructionToBase64, -} from '@solana/spl-governance' -import { PublicKey, TokenAmount } from '@solana/web3.js' -import useCreateProposal from '@hooks/useCreateProposal' -import { useRouter } from 'next/router' -import useRealm from '@hooks/useRealm' -import useQueryContext from '@hooks/useQueryContext' -import Loading from '@components/Loading' -import { dryRunInstruction } from 'actions/dryRunInstruction' -import { ExternalLinkIcon } from '@heroicons/react/outline' -import Link from 'next/link' -import { getExplorerUrl } from '@components/explorer/tools' -import { useConnection } from '@solana/wallet-adapter-react' - -const BurnVestAccountSchema = { - amount: yup.string().required(), -} - -type BurnVestAccountFormValues = { - amount: number -} - -type Props = { - account: VestAccountType - gsrmBalance?: TokenAmount | null - callback?: () => Promise - createProposal?: { - governance?: ProgramAccount - owner: PublicKey - } -} -const VestAccount: FC = ({ - account, - gsrmBalance, - callback, - createProposal, -}) => { - const router = useRouter() - const { cluster } = router.query - - const { symbol } = useRealm() - const { fmtUrlWithCluster } = useQueryContext() - - const gsrmMint = useSerumGovStore((s) => s.gsrmMint) - const actions = useSerumGovStore((s) => s.actions) - - const { anchorProvider, wallet } = useWalletDeprecated() - const { connection } = useConnection() - - const [isBurning, setIsBurning] = useState(false) - const [currentTimestamp, setCurrentTimestamp] = useState(0) - - const { handleCreateProposal } = useCreateProposal() - - useEffect(() => { - const timestampInterval = setInterval(() => { - setCurrentTimestamp(Math.floor(Date.now() / 1000)) - }, 1000) - return () => clearInterval(timestampInterval) - }) - - const schema = yup.object(BurnVestAccountSchema).required() - const { - register, - handleSubmit, - formState: { errors }, - } = useForm({ - mode: 'all', - resolver: yupResolver(schema), - defaultValues: { - amount: 0, - }, - }) - - const redeemableAmount = useMemo(() => { - const cliffEnd = account.createdAt + account.cliffPeriod - const timeVested = currentTimestamp - cliffEnd - - if (timeVested <= 0) return - - let vestedAmount = account.totalGsrmAmount - .mul(new anchor.BN(timeVested)) - .div(new anchor.BN(account.linearVestingPeriod)) - - vestedAmount = vestedAmount.lte(account.totalGsrmAmount) - ? vestedAmount - : account.totalGsrmAmount - - const redeemable = vestedAmount - .sub(account.gsrmBurned) - .div(account.isMsrm ? new anchor.BN(1_000_000_000_000) : new anchor.BN(1)) - - return new BigNumber(redeemable.toString()) - .shiftedBy(-1 * (account.isMsrm ? MSRM_DECIMALS : SRM_DECIMALS)) - .toFormat() - }, [account, currentTimestamp]) - - const handleBurn: SubmitHandler = async ({ - amount, - }) => { - if ( - !gsrmMint || - !gsrmBalance || - isNaN(parseFloat(amount.toString())) || - !wallet || - !wallet.publicKey - ) { - notify({ - type: 'error', - message: 'Something went wrong. Please try refreshing.', - }) - return - } - - setIsBurning(true) - - let amountAsBN = parseMintNaturalAmountFromDecimalAsBN( - amount, - account.isMsrm ? MSRM_DECIMALS : SRM_DECIMALS - ) - if (account.isMsrm) - amountAsBN = amountAsBN.mul(new anchor.BN(MSRM_MULTIPLIER)) - - // Check if amount > balance - if (amountAsBN.gt(new anchor.BN(gsrmBalance.amount))) { - notify({ - type: 'error', - message: 'You do not have enough gSRM to redeem', - }) - setIsBurning(false) - return - } - // Check if amount > (total - burned) - if (amountAsBN.gt(account.totalGsrmAmount.sub(account.gsrmBurned))) { - notify({ - type: 'error', - message: `Only ${fmtBnMintDecimals( - account.totalGsrmAmount.sub(account.gsrmBurned), - SRM_DECIMALS - )} gSRM can be redeemed`, - }) - setIsBurning(false) - return - } - if (!createProposal) { - await actions.burnVestGsrm( - connection, - anchorProvider, - account, - amountAsBN, - wallet - ) - if (callback) await callback() - } else { - const ix = await actions.getBurnVestGsrmInstruction( - anchorProvider, - account, - amountAsBN, - createProposal.owner - ) - - const serializedIx = serializeInstructionToBase64(ix) - - const instructionData = { - data: getInstructionDataFromBase64(serializedIx), - holdUpTime: - createProposal.governance?.account.config.minInstructionHoldUpTime, - prerequisiteInstructions: [], - } - - const { response: dryRunResponse } = await dryRunInstruction( - connection, - wallet!, - instructionData.data - ) - if (dryRunResponse.err) { - notify({ type: 'error', message: 'Transaction Simulation Failed' }) - setIsBurning(false) - return - } - - const proposalAddress = await handleCreateProposal({ - title: `Serum DAO: Redeeming ${amount} gSRM`, - description: `Redeeming ${amount} gSRM to redeem vested ${ - account.isMsrm ? 'MSRM' : 'SRM' - }.`, - instructionsData: [instructionData], - governance: createProposal.governance!, - }) - const url = fmtUrlWithCluster( - `/dao/${symbol}/proposal/${proposalAddress}` - ) - await router.push(url) - } - setIsBurning(false) - } - return ( -
-
-
-

Vested

- - - - - -
-
- {account.isMsrm ? 'MSRM' : 'SRM'} -
-
-
-
-

Redeemable gSRM

-

- {redeemableAmount || 0}/ - {fmtBnMintDecimals(account.totalGsrmAmount, SRM_DECIMALS)} -

-
-
-
-
- - - -
-
- {errors.amount ?

{errors.amount.message}

: null} -
- ) -} - -export default VestAccount diff --git a/components/TokenBalance/SerumGovernanceTokenWrapper.tsx b/components/TokenBalance/SerumGovernanceTokenWrapper.tsx deleted file mode 100644 index 728d4cf198..0000000000 --- a/components/TokenBalance/SerumGovernanceTokenWrapper.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { FC } from 'react' - -import Ticket from '@components/SerumGov/Ticket' -import useWalletDeprecated from '@hooks/useWalletDeprecated' -import LockedAccount from '@components/SerumGov/LockedAccount' -import VestAccount from '@components/SerumGov/VestAccount' -import useSerumGov from '@hooks/useSerumGov' -import DepositCard from '@components/SerumGov/DepositCard' - -const SerumGovernanceTokenWrapper: FC = () => { - const { wallet } = useWalletDeprecated() - - const { - claimTickets, - redeemTickets, - lockedAccounts, - vestAccounts, - gsrmBalance, - refreshRedeemTickets, - refreshVestAccounts, - refreshLockedAccounts, - refreshClaimTickets, - refreshGsrmBalance, - } = useSerumGov(wallet?.publicKey) - - return ( -
-
-
- { - await Promise.all([ - refreshClaimTickets(), - refreshLockedAccounts(), - ]) - }} - /> - { - await Promise.all([ - refreshClaimTickets(), - refreshLockedAccounts(), - ]) - }} - /> -
-
- {vestAccounts && - vestAccounts.map((account) => ( - { - await Promise.all([ - refreshGsrmBalance(), - refreshVestAccounts(), - refreshRedeemTickets(), - ]) - }} - /> - ))} - {lockedAccounts && - lockedAccounts.map((account) => ( - { - await Promise.all([ - refreshGsrmBalance(), - refreshLockedAccounts(), - refreshRedeemTickets(), - ]) - }} - /> - ))} -
- - {(claimTickets && claimTickets.length > 0) || - (redeemTickets && redeemTickets.length > 0) ? ( -
-

Tickets

- {claimTickets && - claimTickets.map((ticket) => ( - { - await Promise.all([ - refreshGsrmBalance(), - refreshClaimTickets(), - ]) - }} - /> - ))} - {redeemTickets && - redeemTickets.map((ticket) => ( - { - await refreshRedeemTickets() - }} - /> - ))} -
- ) : null} -
-
- ) -} - -export default SerumGovernanceTokenWrapper diff --git a/components/treasuryV2/WalletList/WalletListItem/SerumGovWallet/index.tsx b/components/treasuryV2/WalletList/WalletListItem/SerumGovWallet/index.tsx deleted file mode 100644 index 4f3c1b4b03..0000000000 --- a/components/treasuryV2/WalletList/WalletListItem/SerumGovWallet/index.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import DepositCard from '@components/SerumGov/DepositCard' -import LockedAccount from '@components/SerumGov/LockedAccount' -import Ticket from '@components/SerumGov/Ticket' -import VestAccount from '@components/SerumGov/VestAccount' -import useSerumGov from '@hooks/useSerumGov' -import { Wallet } from '@models/treasury/Wallet' -import { PublicKey } from '@solana/web3.js' - -export default function SerumGovWallet({ wallet }: { wallet: Wallet }) { - const { - claimTickets, - redeemTickets, - vestAccounts, - lockedAccounts, - gsrmBalance, - } = useSerumGov(wallet.address) - - if ( - !claimTickets?.length && - !redeemTickets?.length && - !vestAccounts?.length && - !lockedAccounts?.length && - !gsrmBalance - ) - return null - - return ( -
-
- Serum Governance Assets -
-
- - -
-
- {vestAccounts && - vestAccounts.map((account) => ( - - ))} - {lockedAccounts && - lockedAccounts.map((account) => ( - - ))} -
-
- {(claimTickets && claimTickets.length > 0) || - (redeemTickets && redeemTickets.length > 0) ? ( -
- {claimTickets && - claimTickets.map((ticket) => ( - - ))} - {redeemTickets && - redeemTickets.map((ticket) => ( - - ))} -
- ) : null} -
-
- ) -} diff --git a/components/treasuryV2/WalletList/WalletListItem/index.tsx b/components/treasuryV2/WalletList/WalletListItem/index.tsx index c1c95dd3c2..f830919eb7 100644 --- a/components/treasuryV2/WalletList/WalletListItem/index.tsx +++ b/components/treasuryV2/WalletList/WalletListItem/index.tsx @@ -6,7 +6,6 @@ import { Wallet } from '@models/treasury/Wallet' import AssetList, { Section } from './AssetList' import SummaryButton from './SummaryButton' -import SerumGovWallet from './SerumGovWallet' import { PublicKey } from '@metaplex-foundation/js' interface Props { @@ -93,7 +92,6 @@ export default function WalletListItem(props: Props) { }) } /> - )} diff --git a/hooks/useSerumGov.ts b/hooks/useSerumGov.ts deleted file mode 100644 index 8c60f06954..0000000000 --- a/hooks/useSerumGov.ts +++ /dev/null @@ -1,412 +0,0 @@ -import useWalletDeprecated from './useWalletDeprecated' -import useSWR from 'swr' -import { Connection, PublicKey } from '@solana/web3.js' -import useSerumGovUser from './useSerumGovUser' -import { AnchorProvider, Idl, Program } from '@coral-xyz/anchor' -import IDL from '../idls/serum_gov.json' -import useSerumGovStore, { - ClaimTicketType, - LockedAccountType, - RedeemTicketType, - VestAccountType, -} from 'stores/useSerumGovStore' -import { getAssociatedTokenAddress } from '@blockworks-foundation/mango-v4' -import { useConnection } from '@solana/wallet-adapter-react' - -export default function useSerumGov(ownerAddress?: PublicKey | string | null) { - const { connection } = useConnection() - const { anchorProvider } = useWalletDeprecated() - const serumGovProgramId = useSerumGovStore((s) => s.programId) - const gsrmMint = useSerumGovStore((s) => s.gsrmMint) - - const { userAccount } = useSerumGovUser(ownerAddress) - - const { data: gsrmBalance, mutate: refreshGsrmBalance } = useSWR( - () => - userAccount && - ownerAddress && [ - ownerAddress.toString(), - gsrmMint.toBase58(), - 'gsrm_balance', - ], - () => fetchGsrmBalance(connection, gsrmMint, ownerAddress), - { - revalidateOnFocus: false, - revalidateIfStale: false, - errorRetryCount: 0, - } - ) - - const { - data: claimTickets, - mutate: refreshClaimTickets, - error: claimTicketsError, - isValidating: claimTicketsIsValidating, - } = useSWR( - () => - userAccount && - ownerAddress && [ - ownerAddress.toString(), - serumGovProgramId.toBase58(), - 'claim_tickets', - ], - () => fetchClaimTickets(anchorProvider, serumGovProgramId, ownerAddress), - { - revalidateOnFocus: false, - revalidateIfStale: false, - errorRetryCount: 0, - } - ) - const claimTicketsLoading = !claimTickets && !claimTicketsError - - const { - data: redeemTickets, - mutate: refreshRedeemTickets, - error: redeemTicketsError, - isValidating: redeemTicketsIsValidating, - } = useSWR( - () => - userAccount && - ownerAddress && [ - ownerAddress.toString(), - serumGovProgramId.toBase58(), - 'redeem_tickets', - ], - () => fetchRedeemTickets(anchorProvider, serumGovProgramId, ownerAddress), - { - revalidateOnFocus: false, - revalidateIfStale: false, - errorRetryCount: 0, - } - ) - const redeemTicketsLoading = !redeemTickets && !redeemTicketsError - - const { - data: lockedAccounts, - mutate: refreshLockedAccounts, - error: lockedAccountsError, - isValidating: lockedAccountsIsValidating, - } = useSWR( - () => - userAccount && - ownerAddress && [ - ownerAddress.toString(), - serumGovProgramId.toBase58(), - 'locked_accounts', - ], - () => fetchLockedAccounts(anchorProvider, serumGovProgramId, ownerAddress), - { - revalidateOnFocus: false, - revalidateIfStale: false, - errorRetryCount: 0, - } - ) - const lockedAccountsLoading = !lockedAccounts && !lockedAccountsError - - const { - data: vestAccounts, - mutate: refreshVestAccounts, - error: vestAccountsError, - isValidating: vestAccountsIsValidating, - } = useSWR( - () => - userAccount && - ownerAddress && [ - ownerAddress.toString(), - serumGovProgramId.toBase58(), - 'vest_accounts', - ], - () => fetchVestAccounts(anchorProvider, serumGovProgramId, ownerAddress), - { - revalidateOnFocus: false, - revalidateIfStale: false, - errorRetryCount: 0, - } - ) - const vestAccountsLoading = !vestAccounts && !vestAccountsError - - return { - gsrmBalance, - refreshGsrmBalance, - claimTickets, - claimTicketsLoading, - claimTicketsError, - claimTicketsIsValidating, - refreshClaimTickets, - redeemTickets, - redeemTicketsLoading, - redeemTicketsError, - redeemTicketsIsValidating, - refreshRedeemTickets, - lockedAccounts, - lockedAccountsLoading, - lockedAccountsError, - lockedAccountsIsValidating, - refreshLockedAccounts, - vestAccounts, - vestAccountsLoading, - vestAccountsError, - vestAccountsIsValidating, - refreshVestAccounts, - } -} - -const fetchGsrmBalance = async ( - connection: Connection, - gsrmMint: PublicKey, - ownerAddress?: PublicKey | string | null -) => { - if (!ownerAddress) throw new Error('No ownerAddress provided') - - const owner = - typeof ownerAddress === 'string' - ? new PublicKey(ownerAddress) - : ownerAddress - - const ata = await getAssociatedTokenAddress(gsrmMint, owner, true) - const tokenBalance = await connection.getTokenAccountBalance(ata, 'confirmed') - return tokenBalance.value -} - -const fetchClaimTickets = async ( - anchorProvider: AnchorProvider, - serumGovProgramId: PublicKey, - ownerAddress?: PublicKey | string | null -): Promise => { - if (!ownerAddress) throw new Error('No ownerAddress provided') - - const owner = - typeof ownerAddress === 'string' - ? new PublicKey(ownerAddress) - : ownerAddress - - const program = new Program(IDL as Idl, serumGovProgramId, anchorProvider) - - const tickets = await program.account.claimTicket.all([ - { - memcmp: { - offset: 8, - bytes: owner.toBase58(), - }, - }, - ]) - return tickets.map((t) => ({ - address: t.publicKey, - owner: (t.account as any).owner, - depositAccount: (t.account as any).depositAccount, - gsrmAmount: (t.account as any).gsrmAmount, - claimDelay: (t.account as any).claimDelay.toNumber(), - createdAt: (t.account as any).createdAt.toNumber(), - })) -} - -const fetchRedeemTickets = async ( - anchorProvider: AnchorProvider, - serumGovProgramId: PublicKey, - ownerAddress?: PublicKey | string | null -): Promise => { - if (!ownerAddress) throw new Error('No ownerAddress provided') - - const owner = - typeof ownerAddress === 'string' - ? new PublicKey(ownerAddress) - : ownerAddress - - const program = new Program(IDL as Idl, serumGovProgramId, anchorProvider) - - const tickets = await program.account.redeemTicket.all([ - { - memcmp: { - offset: 8, - bytes: owner.toBase58(), - }, - }, - ]) - return tickets.map((t) => ({ - address: t.publicKey, - owner: (t.account as any).owner, - depositAccount: (t.account as any).depositAccount, - redeemIndex: (t.account as any).redeemIndex.toNumber(), - isMsrm: (t.account as any).isMsrm, - amount: (t.account as any).amount, - redeemDelay: (t.account as any).redeemDelay.toNumber(), - createdAt: (t.account as any).createdAt.toNumber(), - })) -} - -const fetchLockedAccounts = async ( - anchorProvider: AnchorProvider, - serumGovProgramId: PublicKey, - ownerAddress?: PublicKey | string | null -): Promise => { - if (!ownerAddress) throw new Error('No ownerAddress provided') - - const owner = - typeof ownerAddress === 'string' - ? new PublicKey(ownerAddress) - : ownerAddress - - const program = new Program(IDL as Idl, serumGovProgramId, anchorProvider) - - const accounts = await program.account.lockedAccount.all([ - { - memcmp: { - offset: 8, - bytes: owner.toBase58(), - }, - }, - ]) - return accounts.map((a) => ({ - address: a.publicKey, - owner: (a.account as any).owner, - lockIndex: (a.account as any).lockIndex.toNumber(), - redeemIndex: (a.account as any).redeemIndex.toNumber(), - createdAt: (a.account as any).createdAt.toNumber(), - isMsrm: (a.account as any).isMsrm, - totalGsrmAmount: (a.account as any).totalGsrmAmount, - gsrmBurned: (a.account as any).gsrmBurned, - })) -} - -const fetchVestAccounts = async ( - anchorProvider: AnchorProvider, - serumGovProgramId: PublicKey, - ownerAddress?: PublicKey | string | null -): Promise => { - if (!ownerAddress) throw new Error('No ownerAddress provided') - - const owner = - typeof ownerAddress === 'string' - ? new PublicKey(ownerAddress) - : ownerAddress - - const program = new Program(IDL as Idl, serumGovProgramId, anchorProvider) - - const accounts = await program.account.vestAccount.all([ - { - memcmp: { - offset: 8, - bytes: owner.toBase58(), - }, - }, - ]) - return accounts.map((a) => ({ - address: a.publicKey, - owner: (a.account as any).owner, - isMsrm: (a.account as any).isMsrm, - vestIndex: (a.account as any).vestIndex.toNumber(), - redeemIndex: (a.account as any).redeemIndex.toNumber(), - cliffPeriod: (a.account as any).cliffPeriod.toNumber(), - linearVestingPeriod: (a.account as any).linearVestingPeriod.toNumber(), - createdAt: (a.account as any).createdAt.toNumber(), - totalGsrmAmount: (a.account as any).totalGsrmAmount, - gsrmBurned: (a.account as any).gsrmBurned, - })) -} - -// export default function useSerumGov(ownerAddress?: PublicKey | string | null) { -// const router = useRouter() -// const routeHasClusterInPath = router.asPath.includes('cluster') -// const { cluster } = router.query - -// const { connection } = useConnection() -// const { anchorProvider } = useWallet() - -// const actions = useSerumGovStore((s) => s.actions) - -// const [gsrmBalance, setGsrmBalance] = useState(null) -// const [userAccount, setUserAccount] = useState(null) -// const [claimTickets, setClaimTickets] = useState([]) -// const [redeemTickets, setRedeemTickets] = useState([]) -// const [lockedAccounts, setLockedAccounts] = useState([]) -// const [vestAccounts, setVestAccounts] = useState([]) - -// async function refreshClaimTickets() { -// const tickets = ownerAddress -// ? await actions.getClaimTickets( -// anchorProvider, -// new PublicKey(ownerAddress) -// ) -// : [] -// setClaimTickets(tickets) -// } -// async function refreshRedeemTickets() { -// const tickets = ownerAddress -// ? await actions.getRedeemTickets( -// anchorProvider, -// new PublicKey(ownerAddress) -// ) -// : [] -// setRedeemTickets(tickets) -// } -// async function refreshLockedAccounts() { -// const accounts = ownerAddress -// ? await actions.getLockedAccounts( -// anchorProvider, -// new PublicKey(ownerAddress) -// ) -// : [] -// setLockedAccounts(accounts) -// } -// async function refreshVestAccounts() { -// const accounts = ownerAddress -// ? await actions.getVestAccounts( -// anchorProvider, -// new PublicKey(ownerAddress) -// ) -// : [] -// setVestAccounts(accounts) -// } -// async function refreshUserAccount() { -// const account = ownerAddress -// ? await actions.getUserAccount( -// anchorProvider, -// new PublicKey(ownerAddress) -// ) -// : null -// setUserAccount(account) -// } -// async function refreshGsrmBalance() { -// const balance = ownerAddress -// ? await actions.getGsrmBalance(connection, new PublicKey(ownerAddress)) -// : null -// setGsrmBalance(balance) -// } - -// useEffect(() => { -// async function getAllAccounts() { -// await Promise.all([ -// refreshClaimTickets(), -// refreshRedeemTickets(), -// refreshLockedAccounts(), -// refreshVestAccounts(), -// refreshGsrmBalance(), -// refreshUserAccount(), -// ]) -// } - -// //Small hack to prevent race conditions with cluster change until we remove connection from store and move it to global dep. -// if ( -// connection && -// ((routeHasClusterInPath && cluster) || !routeHasClusterInPath) -// ) { -// console.log('[serum_gov]: Loading Serum Gov data..') -// getAllAccounts() -// } -// }, [ownerAddress?.toString(), connection.rpcEndpoint]) - -// return { -// gsrmBalance, -// userAccount, -// claimTickets, -// redeemTickets, -// lockedAccounts, -// vestAccounts, -// refreshClaimTickets, -// refreshRedeemTickets, -// refreshLockedAccounts, -// refreshVestAccounts, -// refreshUserAccount, -// refreshGsrmBalance, -// } -// } diff --git a/hooks/useSerumGovUser.ts b/hooks/useSerumGovUser.ts deleted file mode 100644 index 3600d9a2e5..0000000000 --- a/hooks/useSerumGovUser.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { AnchorProvider, BN, Idl, Program } from '@coral-xyz/anchor' -import { PublicKey } from '@solana/web3.js' -import useSerumGovStore, { UserAccountType } from 'stores/useSerumGovStore' -import useSWR from 'swr' -import useWalletDeprecated from './useWalletDeprecated' -import IDL from '../idls/serum_gov.json' - -const fetchUserAccount = async ( - anchorProvider: AnchorProvider, - serumGovProgramId: PublicKey, - ownerAddress?: string | PublicKey | null -): Promise => { - if (!ownerAddress) throw new Error('No ownerAddress provided') - - const owner = - typeof ownerAddress === 'string' - ? new PublicKey(ownerAddress) - : ownerAddress - - const program = new Program(IDL as Idl, serumGovProgramId, anchorProvider) - - const [account] = PublicKey.findProgramAddressSync( - [Buffer.from('user'), owner.toBuffer()], - serumGovProgramId - ) - - const userAccount = await program.account.user.fetch(account) - return { - address: account, - owner: owner, - lockIndex: (userAccount.lockIndex as BN).toNumber(), - vestIndex: (userAccount.vestIndex as BN).toNumber(), - } -} - -export default function useSerumGovUser(owner?: PublicKey | string | null) { - const { anchorProvider } = useWalletDeprecated() - const serumGovProgramId = useSerumGovStore((s) => s.programId) - - const { data, error, mutate, isValidating } = useSWR( - () => - owner && [ - owner.toString(), - serumGovProgramId.toBase58(), - anchorProvider.connection.rpcEndpoint, - 'user_account', - ], - () => fetchUserAccount(anchorProvider, serumGovProgramId, owner) - ) - - const loading = !data && !error - - return { - userAccount: data, - loading, - error, - mutate, - isValidating, - } -}