fix: combine deposit source toenas and vote into a single transaction

This commit is contained in:
Sebastian.Bor 2021-04-03 00:50:15 +01:00
parent 15958d211a
commit 63467f1acb
5 changed files with 180 additions and 302 deletions

View File

@ -1,163 +0,0 @@
import {
Account,
Connection,
PublicKey,
TransactionInstruction,
} from '@solana/web3.js';
import {
contexts,
utils,
models,
ParsedAccount,
actions,
} from '@oyster/common';
import { TimelockSet } from '../models/timelock';
import { AccountLayout } from '@solana/spl-token';
import { depositSourceTokensInstruction } from '../models/depositSourceTokens';
import { LABELS } from '../constants';
import { createEmptyGovernanceVotingRecordInstruction } from '../models/createEmptyGovernanceVotingRecord';
const { createTokenAccount } = actions;
const { sendTransaction } = contexts.Connection;
const { notify } = utils;
const { approve } = models;
export const depositSourceTokens = async (
connection: Connection,
wallet: any,
proposal: ParsedAccount<TimelockSet>,
existingVoteAccount: PublicKey | undefined,
existingYesVoteAccount: PublicKey | undefined,
existingNoVoteAccount: PublicKey | undefined,
sourceAccount: PublicKey,
votingTokenAmount: number,
): Promise<{
voteAccount: PublicKey;
yesVoteAccount: PublicKey;
noVoteAccount: PublicKey;
}> => {
const PROGRAM_IDS = utils.programIds();
let signers: Account[] = [];
let instructions: TransactionInstruction[] = [];
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
AccountLayout.span,
);
let needToCreateGovAccountToo = !existingVoteAccount;
if (!existingVoteAccount) {
existingVoteAccount = createTokenAccount(
instructions,
wallet.publicKey,
accountRentExempt,
proposal.info.votingMint,
wallet.publicKey,
signers,
);
}
const [governanceVotingRecord] = await PublicKey.findProgramAddress(
[
PROGRAM_IDS.timelock.programAccountId.toBuffer(),
proposal.pubkey.toBuffer(),
existingVoteAccount.toBuffer(),
],
PROGRAM_IDS.timelock.programId,
);
if (needToCreateGovAccountToo) {
instructions.push(
createEmptyGovernanceVotingRecordInstruction(
governanceVotingRecord,
proposal.pubkey,
existingVoteAccount,
wallet.publicKey,
),
);
}
if (!existingYesVoteAccount) {
existingYesVoteAccount = createTokenAccount(
instructions,
wallet.publicKey,
accountRentExempt,
proposal.info.yesVotingMint,
wallet.publicKey,
signers,
);
}
if (!existingNoVoteAccount) {
existingNoVoteAccount = createTokenAccount(
instructions,
wallet.publicKey,
accountRentExempt,
proposal.info.noVotingMint,
wallet.publicKey,
signers,
);
}
const [mintAuthority] = await PublicKey.findProgramAddress(
[PROGRAM_IDS.timelock.programAccountId.toBuffer()],
PROGRAM_IDS.timelock.programId,
);
const transferAuthority = approve(
instructions,
[],
sourceAccount,
wallet.publicKey,
votingTokenAmount,
);
signers.push(transferAuthority);
instructions.push(
depositSourceTokensInstruction(
governanceVotingRecord,
existingVoteAccount,
sourceAccount,
proposal.info.sourceHolding,
proposal.info.votingMint,
proposal.pubkey,
transferAuthority.publicKey,
mintAuthority,
votingTokenAmount,
),
);
notify({
message: LABELS.ADDING_VOTES_TO_VOTER,
description: LABELS.PLEASE_WAIT,
type: 'warn',
});
try {
let tx = await sendTransaction(
connection,
wallet,
instructions,
signers,
true,
);
notify({
message: LABELS.VOTES_ADDED,
type: 'success',
description: LABELS.TRANSACTION + ` ${tx}`,
});
} catch (ex) {
console.error(ex);
throw new Error();
}
return {
voteAccount: existingVoteAccount,
yesVoteAccount: existingYesVoteAccount,
noVoteAccount: existingNoVoteAccount,
};
};

View File

@ -1,10 +1,31 @@
import { Connection, PublicKey } from '@solana/web3.js';
import { ParsedAccount } from '@oyster/common';
import {
Account,
Connection,
PublicKey,
TransactionInstruction,
} from '@solana/web3.js';
import {
contexts,
utils,
models,
ParsedAccount,
actions,
} from '@oyster/common';
import { TimelockConfig, TimelockSet, TimelockState } from '../models/timelock';
import { vote } from './vote';
import { depositSourceTokens } from './depositSourceTokens';
import { AccountLayout } from '@solana/spl-token';
import { LABELS } from '../constants';
import { depositSourceTokensInstruction } from '../models/depositSourceTokens';
import { createEmptyGovernanceVotingRecordInstruction } from '../models/createEmptyGovernanceVotingRecord';
import { voteInstruction } from '../models/vote';
const { createTokenAccount } = actions;
const { sendTransactions } = contexts.Connection;
const { notify } = utils;
const { approve } = models;
export const depositSourceTokensAndVote = async (
connection: Connection,
@ -22,31 +43,156 @@ export const depositSourceTokensAndVote = async (
const votingTokenAmount =
yesVotingTokenAmount > 0 ? yesVotingTokenAmount : noVotingTokenAmount;
const {
voteAccount,
yesVoteAccount,
noVoteAccount,
} = await depositSourceTokens(
connection,
wallet,
proposal,
existingVoteAccount,
existingYesVoteAccount,
existingNoVoteAccount,
const PROGRAM_IDS = utils.programIds();
let depositSigners: Account[] = [];
let depositInstructions: TransactionInstruction[] = [];
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
AccountLayout.span,
);
let needToCreateGovAccountToo = !existingVoteAccount;
if (!existingVoteAccount) {
existingVoteAccount = createTokenAccount(
depositInstructions,
wallet.publicKey,
accountRentExempt,
proposal.info.votingMint,
wallet.publicKey,
depositSigners,
);
}
const [governanceVotingRecord] = await PublicKey.findProgramAddress(
[
PROGRAM_IDS.timelock.programAccountId.toBuffer(),
proposal.pubkey.toBuffer(),
existingVoteAccount.toBuffer(),
],
PROGRAM_IDS.timelock.programId,
);
if (needToCreateGovAccountToo) {
depositInstructions.push(
createEmptyGovernanceVotingRecordInstruction(
governanceVotingRecord,
proposal.pubkey,
existingVoteAccount,
wallet.publicKey,
),
);
}
if (!existingYesVoteAccount) {
existingYesVoteAccount = createTokenAccount(
depositInstructions,
wallet.publicKey,
accountRentExempt,
proposal.info.yesVotingMint,
wallet.publicKey,
depositSigners,
);
}
if (!existingNoVoteAccount) {
existingNoVoteAccount = createTokenAccount(
depositInstructions,
wallet.publicKey,
accountRentExempt,
proposal.info.noVotingMint,
wallet.publicKey,
depositSigners,
);
}
const [mintAuthority] = await PublicKey.findProgramAddress(
[PROGRAM_IDS.timelock.programAccountId.toBuffer()],
PROGRAM_IDS.timelock.programId,
);
const depositAuthority = approve(
depositInstructions,
[],
sourceAccount,
wallet.publicKey,
votingTokenAmount,
);
await vote(
connection,
wallet,
proposal,
timelockConfig,
state,
voteAccount,
yesVoteAccount,
noVoteAccount,
yesVotingTokenAmount,
noVotingTokenAmount,
depositSigners.push(depositAuthority);
depositInstructions.push(
depositSourceTokensInstruction(
governanceVotingRecord,
existingVoteAccount,
sourceAccount,
proposal.info.sourceHolding,
proposal.info.votingMint,
proposal.pubkey,
depositAuthority.publicKey,
mintAuthority,
votingTokenAmount,
),
);
let voteSigners: Account[] = [];
let voteInstructions: TransactionInstruction[] = [];
const voteAuthority = approve(
voteInstructions,
[],
existingVoteAccount,
wallet.publicKey,
yesVotingTokenAmount + noVotingTokenAmount,
);
voteSigners.push(voteAuthority);
voteInstructions.push(
voteInstruction(
governanceVotingRecord,
state.pubkey,
existingVoteAccount,
existingYesVoteAccount,
existingNoVoteAccount,
proposal.info.votingMint,
proposal.info.yesVotingMint,
proposal.info.noVotingMint,
proposal.info.sourceMint,
proposal.pubkey,
timelockConfig.pubkey,
voteAuthority.publicKey,
mintAuthority,
yesVotingTokenAmount,
noVotingTokenAmount,
),
);
notify({
message: LABELS.VOTING_FOR_PROPOSAL,
description: LABELS.PLEASE_WAIT,
type: 'warn',
});
try {
await sendTransactions(
connection,
wallet,
[depositInstructions, voteInstructions],
[depositSigners, voteSigners],
true,
);
notify({
message: LABELS.PROPOSAL_VOTED,
type: 'success',
description:
yesVotingTokenAmount > 0
? `${yesVotingTokenAmount} ${LABELS.TOKENS_VOTED_FOR_THE_PROPOSAL}.`
: `${noVotingTokenAmount} ${LABELS.TOKENS_VOTED_AGAINST_THE_PROPOSAL}.`,
});
} catch (ex) {
console.error(ex);
throw new Error();
}
};

View File

@ -1,108 +0,0 @@
import {
Account,
Connection,
PublicKey,
TransactionInstruction,
} from '@solana/web3.js';
import {
contexts,
utils,
models,
ParsedAccount,
actions,
} from '@oyster/common';
import { TimelockConfig, TimelockSet, TimelockState } from '../models/timelock';
import { LABELS } from '../constants';
import { voteInstruction } from '../models/vote';
const { createTokenAccount } = actions;
const { sendTransaction } = contexts.Connection;
const { notify } = utils;
const { approve } = models;
export const vote = async (
connection: Connection,
wallet: any,
proposal: ParsedAccount<TimelockSet>,
timelockConfig: ParsedAccount<TimelockConfig>,
state: ParsedAccount<TimelockState>,
votingAccount: PublicKey,
yesVotingAccount: PublicKey,
noVotingAccount: PublicKey,
yesVotingTokenAmount: number,
noVotingTokenAmount: number,
) => {
const PROGRAM_IDS = utils.programIds();
let signers: Account[] = [];
let instructions: TransactionInstruction[] = [];
const [mintAuthority] = await PublicKey.findProgramAddress(
[PROGRAM_IDS.timelock.programAccountId.toBuffer()],
PROGRAM_IDS.timelock.programId,
);
const [governanceVotingRecord] = await PublicKey.findProgramAddress(
[
PROGRAM_IDS.timelock.programAccountId.toBuffer(),
proposal.pubkey.toBuffer(),
votingAccount.toBuffer(),
],
PROGRAM_IDS.timelock.programId,
);
const transferAuthority = approve(
instructions,
[],
votingAccount,
wallet.publicKey,
yesVotingTokenAmount + noVotingTokenAmount,
);
signers.push(transferAuthority);
instructions.push(
voteInstruction(
governanceVotingRecord,
state.pubkey,
votingAccount,
yesVotingAccount,
noVotingAccount,
proposal.info.votingMint,
proposal.info.yesVotingMint,
proposal.info.noVotingMint,
proposal.info.sourceMint,
proposal.pubkey,
timelockConfig.pubkey,
transferAuthority.publicKey,
mintAuthority,
yesVotingTokenAmount,
noVotingTokenAmount,
),
);
notify({
message: LABELS.BURNING_VOTES,
description: LABELS.PLEASE_WAIT,
type: 'warn',
});
try {
let tx = await sendTransaction(
connection,
wallet,
instructions,
signers,
true,
);
notify({
message: LABELS.VOTES_BURNED,
type: 'success',
description: LABELS.TRANSACTION + ` ${tx}`,
});
} catch (ex) {
console.error(ex);
throw new Error();
}
};

View File

@ -74,8 +74,9 @@ export function Vote({
okText: LABELS.CONFIRM,
cancelText: LABELS.CANCEL,
onOk: async () => {
const vote = await getLatestVote();
if (userTokenAccount && vote != 0) {
const vote = await getLatestVote();
const voteAmount = userTokenAccount.info.amount.toNumber();
const yesTokenAmount = vote > 0 ? voteAmount : 0;

View File

@ -39,8 +39,12 @@ export const LABELS = {
ADD: 'Add',
REMOVE: 'Remove',
ADDING_OR_REMOVING: 'Type',
ADDING_VOTES_TO_VOTER: 'Converting governance tokens to voting tokens',
VOTES_ADDED: 'Governance tokens converted.',
VOTING_FOR_PROPOSAL: 'Voting for proposal.',
PROPOSAL_VOTED: 'Proposal voted.',
TOKENS_VOTED_FOR_THE_PROPOSAL: 'tokens voted for the proposal',
TOKENS_VOTED_AGAINST_THE_PROPOSAL: 'tokens voted against the proposal',
ADDING_GOVERNANCE_TOKENS: 'Adding governance tokens',
PLEASE_WAIT: 'Please wait...',
GOVERNANCE_TOKENS_ADDED: 'Governance tokens added.',
@ -61,8 +65,6 @@ export const LABELS = {
ADD_GOVERNANCE_TOKENS: 'Add Governance Tokens',
ADD_COUNCIL_TOKENS: 'Add Council Tokens',
ACTIONS: 'Actions',
BURNING_VOTES: 'Burning your votes...',
VOTES_BURNED: 'Votes burned',
VOTE: 'Vote',
EXECUTING: 'Executing...',
EXECUTED: 'Executed.',