Skip to content

Commit

Permalink
fill vaults - mango inst (#1864)
Browse files Browse the repository at this point in the history
  • Loading branch information
abrzezinski94 authored Oct 9, 2023
1 parent 6eaaa1e commit 1a07037
Show file tree
Hide file tree
Showing 4 changed files with 306 additions and 0 deletions.
5 changes: 5 additions & 0 deletions hooks/useGovernanceAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,11 @@ export default function useGovernanceAssets() {
isVisible: canUseAuthorityInstruction,
packageId: PackageEnum.Distribution,
},
[Instructions.DistributionFillVaults]: {
name: 'Fill vaults',
isVisible: canUseAuthorityInstruction,
packageId: PackageEnum.Distribution,
},
}

const availablePackages: PackageType[] = Object.entries(packages)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { useContext, useEffect, useState } from 'react'
import * as yup from 'yup'
import { isFormValid } from '@utils/formValidation'
import { UiInstruction } from '@utils/uiTypes/proposalCreationTypes'
import useGovernanceAssets from '@hooks/useGovernanceAssets'
import {
Governance,
serializeInstructionToBase64,
} from '@solana/spl-governance'
import { ProgramAccount } from '@solana/spl-governance'
import { AccountType, AssetAccount } from '@utils/uiTypes/assets'
import useWalletOnePointOh from '@hooks/useWalletOnePointOh'
import { NewProposalContext } from '../../../new'
import InstructionForm, { InstructionInput } from '../FormCreator'
import { InstructionInputType } from '../inputInstructionType'
import {
Distribution,
MangoMintsRedemptionClient,
} from '@blockworks-foundation/mango-mints-redemption'
import { AnchorProvider } from '@coral-xyz/anchor'
import useLegacyConnectionContext from '@hooks/useLegacyConnectionContext'
import EmptyWallet from '@utils/Mango/listingTools'
import { Keypair, PublicKey, TransactionInstruction } from '@solana/web3.js'
import { tryGetTokenAccount } from '@utils/tokens'
import Button from '@components/Button'
import { TOKEN_PROGRAM_ID, Token, u64 } from '@solana/spl-token'
import Input from '@components/inputs/Input'
import { parseMintNaturalAmountFromDecimal } from '@tools/sdk/units'

interface FillVaultsForm {
governedAccount: AssetAccount | null
distributionNumber: number
}

type Vault = {
publicKey: PublicKey
amount: bigint
mintIndex: number
mint: PublicKey
}

type Transfer = {
from: PublicKey
to: PublicKey
amount: string
decimals: number
mintIndex: number
}

const FillVaults = ({
index,
governance,
}: {
index: number
governance: ProgramAccount<Governance> | null
}) => {
const wallet = useWalletOnePointOh()
const { assetAccounts } = useGovernanceAssets()
const solAccounts = assetAccounts.filter((x) => x.type === AccountType.SOL)
const connection = useLegacyConnectionContext()
const shouldBeGoverned = !!(index !== 0 && governance)
const [form, setForm] = useState<FillVaultsForm>({
governedAccount: null,
distributionNumber: 0,
})
const [transfers, setTransfers] = useState<Transfer[]>([])
const [client, setClient] = useState<MangoMintsRedemptionClient>()
const [distribution, setDistribution] = useState<Distribution>()
const [vaults, setVaults] = useState<{ [pubkey: string]: Vault }>()
const [formErrors, setFormErrors] = useState({})
const { handleSetInstructions } = useContext(NewProposalContext)
const validateInstruction = async (): Promise<boolean> => {
const { isValid, validationErrors } = await isFormValid(schema, form)
setFormErrors(validationErrors)
return isValid
}
async function getInstruction(): Promise<UiInstruction> {
const isValid = await validateInstruction()
let serializedInstruction = ''
const additionalSerializedInstructions: string[] = []
const prerequisiteInstructions: TransactionInstruction[] = []
if (
isValid &&
form.governedAccount?.governance?.account &&
wallet?.publicKey &&
vaults
) {
for (const t of transfers) {
const mintAmount = parseMintNaturalAmountFromDecimal(
t.amount,
t.decimals
)
const transferIx = Token.createTransferInstruction(
TOKEN_PROGRAM_ID,
t.from,
t.to,
form.governedAccount.extensions.transferAddress!,
[],
new u64(mintAmount.toString())
)
additionalSerializedInstructions.push(
serializeInstructionToBase64(transferIx!)
)
}
serializedInstruction = ''
}
const obj: UiInstruction = {
additionalSerializedInstructions,
prerequisiteInstructions,
serializedInstruction: serializedInstruction,
isValid,
governance: form.governedAccount?.governance,
customHoldUpTime: form.distributionNumber,
}
return obj
}
const handleSelectDistribution = async (number: number) => {
const distribution = await client?.loadDistribution(number)
setDistribution(distribution)
}
const fetchVaults = async () => {
if (!client || !distribution) return
const v: any = {}
for (let i = 0; i < distribution.metadata!.mints.length; i++) {
const mint = distribution.metadata!.mints[i]
const vaultAddress = distribution.findVaultAddress(
new PublicKey(mint.address)
)
try {
const tokenAccount = await tryGetTokenAccount(
connection.current,
vaultAddress
)

v[vaultAddress.toString()] = {
publicKey: vaultAddress,
amount: tokenAccount?.account.amount,
mint: tokenAccount?.account.mint,
mintIndex: i,
}
} catch {
v[vaultAddress.toString()] = { amount: -1, mintIndex: i }
}
}
setVaults(v)
}
useEffect(() => {
if (distribution) {
fetchVaults()
}
}, [distribution])
useEffect(() => {
const client = new MangoMintsRedemptionClient(
new AnchorProvider(
connection.current,
new EmptyWallet(Keypair.generate()),
{ skipPreflight: true }
)
)
setClient(client)
}, [])
useEffect(() => {
if (vaults && form.governedAccount) {
const trans = Object.values(vaults).map((v) => {
const from = assetAccounts.find(
(assetAccount) =>
assetAccount.isToken &&
assetAccount.extensions.mint?.publicKey.equals(v.mint) &&
assetAccount.extensions.token?.account.owner.equals(
form.governedAccount!.extensions.transferAddress!
)
)
if (!from) {
return undefined
}
return {
from: from!.pubkey,
to: v.publicKey,
amount: '',
decimals: from!.extensions.mint!.account.decimals,
mintIndex: v.mintIndex,
}
})
setTransfers(trans.filter((x) => x) as Transfer[])
} else {
setTransfers([])
}
}, [vaults])

useEffect(() => {
handleSetInstructions(
{ governedAccount: form.governedAccount?.governance, getInstruction },
index
)
// eslint-disable-next-line react-hooks/exhaustive-deps -- TODO please fix, it can cause difficult bugs. You might wanna check out https://bobbyhadz.com/blog/react-hooks-exhaustive-deps for info. -@asktree
}, [form])
const schema = yup.object().shape({
governedAccount: yup
.object()
.nullable()
.required('Program governed account is required'),
})
const inputs: InstructionInput[] = [
{
label: 'Governance',
initialValue: form.governedAccount,
name: 'governedAccount',
type: InstructionInputType.GOVERNED_ACCOUNT,
shouldBeGoverned: shouldBeGoverned as any,
governance: governance,
options: solAccounts,
},
{
label: 'Distribution Number',
initialValue: form.distributionNumber,
type: InstructionInputType.INPUT,
additionalComponent: (
<div>
<Button
onClick={() => handleSelectDistribution(form.distributionNumber)}
>
Load
</Button>
</div>
),
inputType: 'number',
name: 'distributionNumber',
},
]

return (
<>
{form && (
<>
<InstructionForm
outerForm={form}
setForm={setForm}
inputs={inputs}
setFormErrors={setFormErrors}
formErrors={formErrors}
></InstructionForm>
{distribution && vaults && (
<div className="border-t border-th-bkg-2 px-6 py-3">
<span className="mr-4 mb-3 flex flex-col whitespace-nowrap text-th-fgd-3">
Vaults to fill
</span>
<span className="flex flex-col font-mono text-th-fgd-2">
<div>
{transfers
? transfers.map((t, idx) => {
return (
<div
key={t.to.toBase58()}
className="flex justify-between"
>
<p>{t.to.toBase58()}</p>{' '}
<p>
{
distribution.metadata!.mints[t.mintIndex]
.properties?.name
}
</p>{' '}
<span>
<Input
value={t.amount}
onChange={(e) => {
const newTrans = transfers.map(
(x, innerIdex) => {
if (innerIdex === idx) {
return {
...x,
amount: e.target.value,
}
}
return x
}
)
setTransfers(newTrans)
}}
type="text"
></Input>
</span>
</div>
)
})
: 'Loading...'}
</div>
</span>
</div>
)}
</>
)}
</>
)
}

export default FillVaults
2 changes: 2 additions & 0 deletions pages/dao/[symbol]/proposal/new.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ import DualGso from './components/instructions/Dual/DualGso'
import DualGsoWithdraw from './components/instructions/Dual/DualGsoWithdraw'
import MultiChoiceForm from '../../../../components/MultiChoiceForm'
import CloseVaults from './components/instructions/DistrubtionProgram/CloseVaults'
import FillVaults from './components/instructions/DistrubtionProgram/FillVaults'

const TITLE_LENGTH_LIMIT = 130
// the true length limit is either at the tx size level, and maybe also the total account size level (I can't remember)
Expand Down Expand Up @@ -494,6 +495,7 @@ const New = () => {
[Instructions.DualFinanceVoteDeposit]: DualVoteDeposit,
[Instructions.DualFinanceVote]: DualVote,
[Instructions.DistributionCloseVaults]: CloseVaults,
[Instructions.DistributionFillVaults]: FillVaults,
[Instructions.MeanCreateAccount]: MeanCreateAccount,
[Instructions.MeanFundAccount]: MeanFundAccount,
[Instructions.MeanWithdrawFromAccount]: MeanWithdrawFromAccount,
Expand Down
1 change: 1 addition & 0 deletions utils/uiTypes/proposalCreationTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ export enum Instructions {
DualFinanceVoteDeposit,
DualFinanceVote,
DistributionCloseVaults,
DistributionFillVaults,
DelegateStake,
ForesightAddMarketListToCategory,
ForesightInitCategory,
Expand Down

1 comment on commit 1a07037

@vercel
Copy link

@vercel vercel bot commented on 1a07037 Oct 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.