mango-v4-ui/utils/governance/instructions/createProposal.ts

160 lines
4.2 KiB
TypeScript

import {
getGovernanceProgramVersion,
getInstructionDataFromBase64,
getSignatoryRecordAddress,
ProgramAccount,
serializeInstructionToBase64,
TokenOwnerRecord,
VoteType,
WalletSigner,
withAddSignatory,
withCreateProposal,
withInsertTransaction,
withSignOffProposal,
} from '@solana/spl-governance'
import {
Connection,
PublicKey,
Transaction,
TransactionInstruction,
} from '@solana/web3.js'
import chunk from 'lodash/chunk'
import { MANGO_MINT } from 'utils/constants'
import { MANGO_GOVERNANCE_PROGRAM, MANGO_REALM_PK } from '../constants'
import { VsrClient } from '../voteStakeRegistryClient'
import { updateVoterWeightRecord } from './updateVoteWeightRecord'
import { createComputeBudgetIx } from '@blockworks-foundation/mango-v4'
export const createProposal = async (
connection: Connection,
wallet: WalletSigner,
governance: PublicKey,
tokenOwnerRecord: ProgramAccount<TokenOwnerRecord>,
name: string,
descriptionLink: string,
proposalIndex: number,
proposalInstructions: TransactionInstruction[],
client: VsrClient,
fee: number,
) => {
const instructions: TransactionInstruction[] = []
const walletPk = wallet.publicKey!
const governanceAuthority = walletPk
const signatory = walletPk
const payer = walletPk
// Changed this because it is misbehaving on my local validator setup.
const programVersion = await getGovernanceProgramVersion(
connection,
MANGO_GOVERNANCE_PROGRAM,
)
// V2 Approve/Deny configuration
const voteType = VoteType.SINGLE_CHOICE
const options = ['Approve']
const useDenyOption = true
const { updateVoterWeightRecordIx, voterWeightPk } =
await updateVoterWeightRecord(
client,
tokenOwnerRecord.account.governingTokenOwner,
)
instructions.push(updateVoterWeightRecordIx)
const proposalAddress = await withCreateProposal(
instructions,
MANGO_GOVERNANCE_PROGRAM,
programVersion,
MANGO_REALM_PK,
governance,
tokenOwnerRecord.pubkey,
name,
descriptionLink,
new PublicKey(MANGO_MINT),
governanceAuthority,
proposalIndex,
voteType,
options,
useDenyOption,
payer,
voterWeightPk,
)
await withAddSignatory(
instructions,
MANGO_GOVERNANCE_PROGRAM,
programVersion,
proposalAddress,
tokenOwnerRecord.pubkey,
governanceAuthority,
signatory,
payer,
)
const signatoryRecordAddress = await getSignatoryRecordAddress(
MANGO_GOVERNANCE_PROGRAM,
proposalAddress,
signatory,
)
const insertInstructions: TransactionInstruction[] = []
for (const i in proposalInstructions) {
const instruction = getInstructionDataFromBase64(
serializeInstructionToBase64(proposalInstructions[i]),
)
await withInsertTransaction(
insertInstructions,
MANGO_GOVERNANCE_PROGRAM,
programVersion,
governance,
proposalAddress,
tokenOwnerRecord.pubkey,
governanceAuthority,
Number(i),
0,
0,
[instruction],
payer,
)
}
withSignOffProposal(
insertInstructions, // SingOff proposal needs to be executed after inserting instructions hence we add it to insertInstructions
MANGO_GOVERNANCE_PROGRAM,
programVersion,
MANGO_REALM_PK,
governance,
proposalAddress,
signatory,
signatoryRecordAddress,
undefined,
)
const txChunks = chunk([...instructions, ...insertInstructions], 2)
const transactions: Transaction[] = []
const latestBlockhash = await connection.getLatestBlockhash('confirmed')
for (const chunk of txChunks) {
const tx = new Transaction()
tx.add(createComputeBudgetIx(fee))
tx.add(...chunk)
tx.lastValidBlockHeight = latestBlockhash.lastValidBlockHeight
tx.recentBlockhash = latestBlockhash.blockhash
tx.feePayer = payer
transactions.push(tx)
}
const signedTransactions = await wallet.signAllTransactions(transactions)
for (const tx of signedTransactions) {
const rawTransaction = tx.serialize()
const address = await connection.sendRawTransaction(rawTransaction, {
skipPreflight: true,
})
await connection.confirmTransaction({
blockhash: latestBlockhash.blockhash,
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
signature: address,
})
}
return proposalAddress
}