Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VSR: unlock deposit integration #1822

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
44 changes: 44 additions & 0 deletions VoteStakeRegistry/actions/getUnlockDepositInstruction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { PublicKey } from '@solana/web3.js'

import {
Registrar,
getRegistrarPDA,
getVoterPDA,
} from 'VoteStakeRegistry/sdk/accounts'
import { VsrClient } from 'VoteStakeRegistry/sdk/client'

export const getUnlockDepositInstruction = async ({
communityMintPk,
voterAuthorityPk,
realmPk,
depositEntryIndex,
client,
voteStakeRegistryRegistrar,
}: {
communityMintPk: PublicKey
depositEntryIndex: number
voterAuthorityPk: PublicKey
realmPk: PublicKey
client?: VsrClient
voteStakeRegistryRegistrar: Registrar
}) => {
const clientProgramId = client!.program.programId

const { registrar } = getRegistrarPDA(
realmPk,
communityMintPk,
clientProgramId
)
const { voter } = getVoterPDA(registrar, voterAuthorityPk, clientProgramId)

const unlockDepositIx = await client?.program.methods
.unlockDeposit(depositEntryIndex)
.accounts({
registrar,
voter,
voterAuthority: voterAuthorityPk,
grantAuthority: voteStakeRegistryRegistrar.realmAuthority,
})
.instruction()
return unlockDepositIx
}
213 changes: 213 additions & 0 deletions VoteStakeRegistry/components/instructions/UnlockDeposit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
import React, {
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from 'react'
import Input from '@components/inputs/Input'
import { PublicKey, TransactionInstruction } from '@solana/web3.js'
import {
UiInstruction,
UnlockDepositForm,
} from '@utils/uiTypes/proposalCreationTypes'
import { getValidatedPublickKey } from '@utils/validations'
import useGovernanceAssets from '@hooks/useGovernanceAssets'
import {
Governance,
serializeInstructionToBase64,
} from '@solana/spl-governance'
import { ProgramAccount } from '@solana/spl-governance'
import { validateInstruction } from '@utils/instructionTools'
import * as yup from 'yup'
import useVotePluginsClientStore from 'stores/useVotePluginsClientStore'
import { useRealmQuery } from '@hooks/queries/realm'
import { getUnlockDepositInstruction } from 'VoteStakeRegistry/actions/getUnlockDepositInstruction'
import { NewProposalContext } from 'pages/dao/[symbol]/proposal/new'
import { Voter, getVoterPDA } from 'VoteStakeRegistry/sdk/accounts'
import { VsrClient } from 'VoteStakeRegistry/sdk/client'
import { tryGetVoter } from 'VoteStakeRegistry/sdk/api'
import Select from '@components/inputs/Select'
import { getDepositType } from 'VoteStakeRegistry/tools/deposits'
import dayjs from 'dayjs'

const UnlockDeposit = ({
index,
}: {
index: number
governance: ProgramAccount<Governance> | null
}) => {
const {
client,
voteStakeRegistryRegistrar,
voteStakeRegistryRegistrarPk,
} = useVotePluginsClientStore((s) => ({
client: s.state.vsrClient,
voteStakeRegistryRegistrar: s.state.voteStakeRegistryRegistrar,
voteStakeRegistryRegistrarPk: s.state.voteStakeRegistryRegistrarPk,
}))
const realm = useRealmQuery().data?.result
const { handleSetInstructions } = useContext(NewProposalContext)

const { governancesArray } = useGovernanceAssets()
const [form, setForm] = useState<UnlockDepositForm>({
depositEntryIndex: undefined,
voterAuthorityPk: '',
})
const schema = useMemo(
() =>
yup.object().shape({
voterAuthorityPk: yup
.string()
.required('voterAuthority required')
.test(
'voterAuthorityPk',
'voterAuthorityPk must be valid PublicKey',
function (voterAuthorityPk: string) {
try {
getValidatedPublickKey(voterAuthorityPk)
} catch (err) {
return false
}
return true
}
),
depositEntryIndex: yup.number().required('depositEntryIndex required'),
}),
[]
)

const [voter, setVoter] = useState<Voter | null>(null)

const [governedAccount, setGovernedAccount] = useState<
ProgramAccount<Governance> | undefined
>(undefined)
const [formErrors, setFormErrors] = useState({})

const handleSetForm = ({ propertyName, value }) => {
setFormErrors({})
setForm((prevForm) => ({ ...prevForm, [propertyName]: value }))
}

const getInstruction = useCallback(async () => {
if (!realm) throw new Error('No realm')
if (!voteStakeRegistryRegistrar)
throw new Error('No voteStakeRegistryRegistrar')

console.log('form', form)
const isValid = await validateInstruction({ schema, form, setFormErrors })
let serializedInstruction = ''
const prerequisiteInstructions: TransactionInstruction[] = []
if (isValid && typeof form.depositEntryIndex === 'number') {
const voterAuthorityPk = new PublicKey(form.voterAuthorityPk)

const unlockDepositIx = await getUnlockDepositInstruction({
depositEntryIndex: form.depositEntryIndex,
communityMintPk: realm!.account.communityMint,
voterAuthorityPk,
realmPk: realm!.pubkey,
client: client!,
voteStakeRegistryRegistrar,
})
serializedInstruction = serializeInstructionToBase64(unlockDepositIx!)
}

const obj: UiInstruction = {
serializedInstruction,
isValid,
governance: governedAccount,
prerequisiteInstructions: prerequisiteInstructions,
chunkBy: 1,
}
return obj
}, [client, form, governedAccount, realm, schema, voteStakeRegistryRegistrar])

useEffect(() => {
if (!voteStakeRegistryRegistrar) return

const governance = governancesArray.find((governance) =>
governance.pubkey.equals(voteStakeRegistryRegistrar.realmAuthority)
)
setGovernedAccount(governance)
}, [governancesArray, voteStakeRegistryRegistrar])

useEffect(() => {
handleSetInstructions(
{ governedAccount: governedAccount, getInstruction },
index
)
console.log('Set instruction')
}, [form, governedAccount, handleSetInstructions, index, getInstruction])

useEffect(() => {
const loadVoter = async (client: VsrClient, registrarPk: PublicKey) => {
const { voter: voterPda } = getVoterPDA(
registrarPk,
new PublicKey(form.voterAuthorityPk),
client.program.programId
)
const _voter = await tryGetVoter(voterPda, client)
setVoter(_voter)
}
if (form.voterAuthorityPk && client && voteStakeRegistryRegistrarPk) {
loadVoter(client, voteStakeRegistryRegistrarPk)
}
}, [client, form.voterAuthorityPk, voteStakeRegistryRegistrarPk])

return (
<>
<Input
label="Voter Wallet address"
value={form.voterAuthorityPk}
type="text"
onChange={(evt) =>
handleSetForm({
value: evt.target.value,
propertyName: 'voterAuthorityPk',
})
}
error={formErrors['voterAuthorityPk']}
/>
{voter ? (
<Select
label={'Deposit Entry'}
onChange={(value) => {
handleSetForm({ value, propertyName: 'depositEntryIndex' })
}}
placeholder="Please select..."
value={
typeof form.depositEntryIndex === 'number'
? `Entry # ${form.depositEntryIndex}`
: undefined
}
error={formErrors['depositEntryIndex']}
>
{voter.deposits.map((deposit, idx) => {
const unixLockupEnd = deposit.lockup.endTs.toNumber() * 1000
const finalUnlockDate = dayjs(unixLockupEnd)
const lockupType = getDepositType(deposit)
if (lockupType == 'none') return null
return (
<Select.Option key={idx} value={idx}>
<div>Lockup Type: {lockupType}</div>
<div>
<div>
Lockup end date: {finalUnlockDate.format('YYYY-MM-DD')}
</div>
{/* TODO: Translate to human readable value */}
Amount still locked:{' '}
{deposit.amountDepositedNative.toString()}
</div>
</Select.Option>
)
})}
</Select>
) : (
<div>No VSR deposits found for voter: {form.voterAuthorityPk}</div>
)}
</>
)
}

export default UnlockDeposit
8 changes: 4 additions & 4 deletions VoteStakeRegistry/sdk/accounts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ export interface DepositWithMintAccount extends Deposit {

export const emptyPk = '11111111111111111111111111111111'

export const getRegistrarPDA = async (
export const getRegistrarPDA = (
realmPk: PublicKey,
mint: PublicKey,
clientProgramId: PublicKey
) => {
const [registrar, registrarBump] = await PublicKey.findProgramAddress(
const [registrar, registrarBump] = PublicKey.findProgramAddressSync(
[realmPk.toBuffer(), Buffer.from('registrar'), mint.toBuffer()],
clientProgramId
)
Expand All @@ -78,12 +78,12 @@ export const getRegistrarPDA = async (
}
}

export const getVoterPDA = async (
export const getVoterPDA = (
registrar: PublicKey,
walletPk: PublicKey,
clientProgramId: PublicKey
) => {
const [voter, voterBump] = await PublicKey.findProgramAddress(
const [voter, voterBump] = PublicKey.findProgramAddressSync(
[registrar.toBuffer(), Buffer.from('voter'), walletPk.toBuffer()],
clientProgramId
)
Expand Down
Loading
Loading