mirror of https://github.com/certusone/oyster.git
feat: add quick vote
This commit is contained in:
parent
223d4a1f36
commit
f13b63b0b2
|
@ -13,10 +13,12 @@ import {
|
||||||
} from '@oyster/common';
|
} from '@oyster/common';
|
||||||
|
|
||||||
import { TimelockSet } from '../models/timelock';
|
import { TimelockSet } from '../models/timelock';
|
||||||
|
|
||||||
import { AccountLayout } from '@solana/spl-token';
|
import { AccountLayout } from '@solana/spl-token';
|
||||||
import { depositSourceTokensInstruction } from '../models/depositSourceTokens';
|
import { depositSourceTokensInstruction } from '../models/depositSourceTokens';
|
||||||
import { LABELS } from '../constants';
|
import { LABELS } from '../constants';
|
||||||
import { createEmptyGovernanceVotingRecordInstruction } from '../models/createEmptyGovernanceVotingRecord';
|
import { createEmptyGovernanceVotingRecordInstruction } from '../models/createEmptyGovernanceVotingRecord';
|
||||||
|
|
||||||
const { createTokenAccount } = actions;
|
const { createTokenAccount } = actions;
|
||||||
const { sendTransaction } = contexts.Connection;
|
const { sendTransaction } = contexts.Connection;
|
||||||
const { notify } = utils;
|
const { notify } = utils;
|
||||||
|
@ -31,7 +33,11 @@ export const depositSourceTokens = async (
|
||||||
existingNoVoteAccount: PublicKey | undefined,
|
existingNoVoteAccount: PublicKey | undefined,
|
||||||
sourceAccount: PublicKey,
|
sourceAccount: PublicKey,
|
||||||
votingTokenAmount: number,
|
votingTokenAmount: number,
|
||||||
) => {
|
): Promise<{
|
||||||
|
voteAccount: PublicKey;
|
||||||
|
yesVoteAccount: PublicKey;
|
||||||
|
noVoteAccount: PublicKey;
|
||||||
|
}> => {
|
||||||
const PROGRAM_IDS = utils.programIds();
|
const PROGRAM_IDS = utils.programIds();
|
||||||
|
|
||||||
let signers: Account[] = [];
|
let signers: Account[] = [];
|
||||||
|
@ -74,7 +80,7 @@ export const depositSourceTokens = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!existingYesVoteAccount) {
|
if (!existingYesVoteAccount) {
|
||||||
createTokenAccount(
|
existingYesVoteAccount = createTokenAccount(
|
||||||
instructions,
|
instructions,
|
||||||
wallet.publicKey,
|
wallet.publicKey,
|
||||||
accountRentExempt,
|
accountRentExempt,
|
||||||
|
@ -85,7 +91,7 @@ export const depositSourceTokens = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!existingNoVoteAccount) {
|
if (!existingNoVoteAccount) {
|
||||||
createTokenAccount(
|
existingNoVoteAccount = createTokenAccount(
|
||||||
instructions,
|
instructions,
|
||||||
wallet.publicKey,
|
wallet.publicKey,
|
||||||
accountRentExempt,
|
accountRentExempt,
|
||||||
|
@ -148,4 +154,10 @@ export const depositSourceTokens = async (
|
||||||
console.error(ex);
|
console.error(ex);
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
voteAccount: existingVoteAccount,
|
||||||
|
yesVoteAccount: existingYesVoteAccount,
|
||||||
|
noVoteAccount: existingNoVoteAccount,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { Connection, PublicKey } from '@solana/web3.js';
|
||||||
|
import { ParsedAccount } from '@oyster/common';
|
||||||
|
|
||||||
|
import { TimelockConfig, TimelockSet, TimelockState } from '../models/timelock';
|
||||||
|
|
||||||
|
import { vote } from './vote';
|
||||||
|
import { depositSourceTokens } from './depositSourceTokens';
|
||||||
|
|
||||||
|
export const depositSourceTokensAndVote = async (
|
||||||
|
connection: Connection,
|
||||||
|
wallet: any,
|
||||||
|
proposal: ParsedAccount<TimelockSet>,
|
||||||
|
existingVoteAccount: PublicKey | undefined,
|
||||||
|
existingYesVoteAccount: PublicKey | undefined,
|
||||||
|
existingNoVoteAccount: PublicKey | undefined,
|
||||||
|
sourceAccount: PublicKey,
|
||||||
|
timelockConfig: ParsedAccount<TimelockConfig>,
|
||||||
|
state: ParsedAccount<TimelockState>,
|
||||||
|
yesVotingTokenAmount: number,
|
||||||
|
noVotingTokenAmount: number,
|
||||||
|
) => {
|
||||||
|
const votingTokenAmount =
|
||||||
|
yesVotingTokenAmount > 0 ? yesVotingTokenAmount : noVotingTokenAmount;
|
||||||
|
|
||||||
|
const {
|
||||||
|
voteAccount,
|
||||||
|
yesVoteAccount,
|
||||||
|
noVoteAccount,
|
||||||
|
} = await depositSourceTokens(
|
||||||
|
connection,
|
||||||
|
wallet,
|
||||||
|
proposal,
|
||||||
|
existingVoteAccount,
|
||||||
|
existingYesVoteAccount,
|
||||||
|
existingNoVoteAccount,
|
||||||
|
sourceAccount,
|
||||||
|
votingTokenAmount,
|
||||||
|
);
|
||||||
|
|
||||||
|
await vote(
|
||||||
|
connection,
|
||||||
|
wallet,
|
||||||
|
proposal,
|
||||||
|
timelockConfig,
|
||||||
|
state,
|
||||||
|
voteAccount,
|
||||||
|
yesVoteAccount,
|
||||||
|
noVoteAccount,
|
||||||
|
yesVotingTokenAmount,
|
||||||
|
noVotingTokenAmount,
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,118 @@
|
||||||
|
import { ParsedAccount } from '@oyster/common';
|
||||||
|
import { Button, Col, Modal, Row, Switch } from 'antd';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
TimelockConfig,
|
||||||
|
TimelockSet,
|
||||||
|
TimelockState,
|
||||||
|
TimelockStateStatus,
|
||||||
|
VotingEntryRule,
|
||||||
|
} from '../../models/timelock';
|
||||||
|
import { LABELS } from '../../constants';
|
||||||
|
import { depositSourceTokensAndVote } from '../../actions/depositSourceTokensAndVote';
|
||||||
|
import { contexts, hooks } from '@oyster/common';
|
||||||
|
import {
|
||||||
|
CheckOutlined,
|
||||||
|
CloseOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
|
||||||
|
const { useWallet } = contexts.Wallet;
|
||||||
|
const { useConnection } = contexts.Connection;
|
||||||
|
const { useAccountByMint } = hooks;
|
||||||
|
|
||||||
|
const { cache } = contexts.Accounts;
|
||||||
|
|
||||||
|
const { confirm } = Modal;
|
||||||
|
export function QuickVote({
|
||||||
|
proposal,
|
||||||
|
state,
|
||||||
|
timelockConfig,
|
||||||
|
}: {
|
||||||
|
proposal: ParsedAccount<TimelockSet>;
|
||||||
|
state: ParsedAccount<TimelockState>;
|
||||||
|
timelockConfig: ParsedAccount<TimelockConfig>;
|
||||||
|
}) {
|
||||||
|
const wallet = useWallet();
|
||||||
|
const connection = useConnection();
|
||||||
|
|
||||||
|
const voteAccount = useAccountByMint(proposal.info.votingMint);
|
||||||
|
const yesVoteAccount = useAccountByMint(proposal.info.yesVotingMint);
|
||||||
|
const noVoteAccount = useAccountByMint(proposal.info.noVotingMint);
|
||||||
|
|
||||||
|
const userTokenAccount = useAccountByMint(proposal.info.sourceMint);
|
||||||
|
const alreadyHaveTokens =
|
||||||
|
(voteAccount && voteAccount.info.amount.toNumber() > 0) ||
|
||||||
|
(yesVoteAccount && yesVoteAccount.info.amount.toNumber() > 0) ||
|
||||||
|
(noVoteAccount && noVoteAccount.info.amount.toNumber() > 0);
|
||||||
|
|
||||||
|
const [mode, setMode] = useState(true);
|
||||||
|
|
||||||
|
const eligibleToView =
|
||||||
|
timelockConfig.info.votingEntryRule == VotingEntryRule.Anytime &&
|
||||||
|
[TimelockStateStatus.Draft, TimelockStateStatus.Voting].includes(
|
||||||
|
state.info.status,
|
||||||
|
);
|
||||||
|
|
||||||
|
return eligibleToView ? (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() =>
|
||||||
|
confirm({
|
||||||
|
title: 'Confirm',
|
||||||
|
icon: <ExclamationCircleOutlined />,
|
||||||
|
content: (
|
||||||
|
<Row>
|
||||||
|
<Col span={24}>
|
||||||
|
<p>
|
||||||
|
Use {userTokenAccount?.info.amount.toNumber() || 0} tokens to
|
||||||
|
vote in favor OR against this proposal. You can refund these
|
||||||
|
at any time. Use the switch to indicate preference.
|
||||||
|
</p>
|
||||||
|
<Switch
|
||||||
|
checkedChildren={<CheckOutlined />}
|
||||||
|
unCheckedChildren={<CloseOutlined />}
|
||||||
|
defaultChecked
|
||||||
|
onChange={setMode}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
),
|
||||||
|
okText: LABELS.CONFIRM,
|
||||||
|
cancelText: LABELS.CANCEL,
|
||||||
|
onOk: async () => {
|
||||||
|
if (userTokenAccount) {
|
||||||
|
// mode is out of date in this scope, so we use a trick to get it here.
|
||||||
|
const valueHolder = { mode: true };
|
||||||
|
await setMode(mode => {
|
||||||
|
valueHolder.mode = mode;
|
||||||
|
return mode;
|
||||||
|
});
|
||||||
|
|
||||||
|
const voteAmount = userTokenAccount.info.amount.toNumber();
|
||||||
|
|
||||||
|
const yesTokenAmount = valueHolder.mode ? voteAmount : 0;
|
||||||
|
const noTokenAmount = !valueHolder.mode ? voteAmount : 0;
|
||||||
|
|
||||||
|
await depositSourceTokensAndVote(
|
||||||
|
connection,
|
||||||
|
wallet.wallet,
|
||||||
|
proposal,
|
||||||
|
voteAccount?.pubkey,
|
||||||
|
yesVoteAccount?.pubkey,
|
||||||
|
noVoteAccount?.pubkey,
|
||||||
|
userTokenAccount.pubkey,
|
||||||
|
timelockConfig,
|
||||||
|
state,
|
||||||
|
yesTokenAmount,
|
||||||
|
noTokenAmount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{LABELS.QUICK_VOTE}
|
||||||
|
</Button>
|
||||||
|
) : null;
|
||||||
|
}
|
|
@ -69,6 +69,7 @@ export const LABELS = {
|
||||||
WITHDRAWING_VOTING_TOKENS: 'Refunding voting tokens as Source Tokens',
|
WITHDRAWING_VOTING_TOKENS: 'Refunding voting tokens as Source Tokens',
|
||||||
TOKENS_WITHDRAWN: 'Voting tokens refunded as Source Tokens',
|
TOKENS_WITHDRAWN: 'Voting tokens refunded as Source Tokens',
|
||||||
REGISTER_TO_VOTE: 'Register to Vote',
|
REGISTER_TO_VOTE: 'Register to Vote',
|
||||||
|
QUICK_VOTE: 'Quick Vote',
|
||||||
CONFIRM: 'Confirm',
|
CONFIRM: 'Confirm',
|
||||||
CANCEL: 'Cancel',
|
CANCEL: 'Cancel',
|
||||||
ADD_MORE_VOTES: 'Add More Votes',
|
ADD_MORE_VOTES: 'Add More Votes',
|
||||||
|
|
|
@ -25,6 +25,7 @@ import AddSigners from '../../components/Proposal/AddSigners';
|
||||||
import MintSourceTokens from '../../components/Proposal/MintSourceTokens';
|
import MintSourceTokens from '../../components/Proposal/MintSourceTokens';
|
||||||
import { Vote } from '../../components/Proposal/Vote';
|
import { Vote } from '../../components/Proposal/Vote';
|
||||||
import { RegisterToVote } from '../../components/Proposal/RegisterToVote';
|
import { RegisterToVote } from '../../components/Proposal/RegisterToVote';
|
||||||
|
import { QuickVote } from '../../components/Proposal/QuickVote';
|
||||||
import { WithdrawTokens } from '../../components/Proposal/WithdrawTokens';
|
import { WithdrawTokens } from '../../components/Proposal/WithdrawTokens';
|
||||||
import './style.less';
|
import './style.less';
|
||||||
import { getGovernanceVotingRecords } from '../../utils/lookups';
|
import { getGovernanceVotingRecords } from '../../utils/lookups';
|
||||||
|
@ -306,6 +307,11 @@ function InnerProposalView({
|
||||||
proposal={proposal}
|
proposal={proposal}
|
||||||
state={timelockState}
|
state={timelockState}
|
||||||
/>
|
/>
|
||||||
|
<QuickVote
|
||||||
|
timelockConfig={timelockConfig}
|
||||||
|
proposal={proposal}
|
||||||
|
state={timelockState}
|
||||||
|
/>
|
||||||
<Vote
|
<Vote
|
||||||
proposal={proposal}
|
proposal={proposal}
|
||||||
timelockConfig={timelockConfig}
|
timelockConfig={timelockConfig}
|
||||||
|
|
Loading…
Reference in New Issue