mirror of https://github.com/certusone/oyster.git
Add add votes button and commands
This commit is contained in:
parent
0c95a9ab6c
commit
c5ac500648
|
@ -0,0 +1,133 @@
|
|||
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 { mintVotingTokensInstruction } from '../models/mintVotingTokens';
|
||||
import { LABELS } from '../constants';
|
||||
const { createTokenAccount } = actions;
|
||||
const { sendTransaction } = contexts.Connection;
|
||||
const { notify } = utils;
|
||||
const { approve } = models;
|
||||
|
||||
export const mintVotingTokens = async (
|
||||
connection: Connection,
|
||||
wallet: any,
|
||||
proposal: ParsedAccount<TimelockSet>,
|
||||
signatoryAccount: PublicKey,
|
||||
newVotingAccountOwner: PublicKey,
|
||||
existingVoteAccount: PublicKey | undefined,
|
||||
votingTokenAmount: number,
|
||||
) => {
|
||||
const PROGRAM_IDS = utils.programIds();
|
||||
|
||||
let signers: Account[] = [];
|
||||
let instructions: TransactionInstruction[] = [];
|
||||
|
||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
|
||||
AccountLayout.span,
|
||||
);
|
||||
|
||||
if (!existingVoteAccount) {
|
||||
existingVoteAccount = createTokenAccount(
|
||||
instructions,
|
||||
wallet.publicKey,
|
||||
accountRentExempt,
|
||||
proposal.info.votingMint,
|
||||
newVotingAccountOwner,
|
||||
signers,
|
||||
);
|
||||
|
||||
notify({
|
||||
message: LABELS.ADDING_NEW_VOTE_ACCOUNT,
|
||||
description: LABELS.PLEASE_WAIT,
|
||||
type: 'warn',
|
||||
});
|
||||
|
||||
try {
|
||||
let tx = await sendTransaction(
|
||||
connection,
|
||||
wallet,
|
||||
instructions,
|
||||
signers,
|
||||
true,
|
||||
);
|
||||
|
||||
notify({
|
||||
message: LABELS.NEW_VOTED_ACCOUNT_ADDED,
|
||||
type: 'success',
|
||||
description: LABELS.TRANSACTION + ` ${tx}`,
|
||||
});
|
||||
} catch (ex) {
|
||||
console.error(ex);
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
signers = [];
|
||||
instructions = [];
|
||||
}
|
||||
|
||||
const [mintAuthority] = await PublicKey.findProgramAddress(
|
||||
[PROGRAM_IDS.timelock.programAccountId.toBuffer()],
|
||||
PROGRAM_IDS.timelock.programId,
|
||||
);
|
||||
|
||||
const transferAuthority = approve(
|
||||
instructions,
|
||||
[],
|
||||
signatoryAccount,
|
||||
wallet.publicKey,
|
||||
1,
|
||||
);
|
||||
|
||||
signers.push(transferAuthority);
|
||||
|
||||
instructions.push(
|
||||
mintVotingTokensInstruction(
|
||||
proposal.pubkey,
|
||||
existingVoteAccount,
|
||||
proposal.info.votingMint,
|
||||
signatoryAccount,
|
||||
proposal.info.signatoryValidation,
|
||||
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();
|
||||
}
|
||||
};
|
|
@ -38,7 +38,6 @@ export default function AddSigners({
|
|||
failedSigners: string;
|
||||
}) => {
|
||||
const signers = values.signers.split(',').map(s => s.trim());
|
||||
setSaving(true);
|
||||
if (!adminAccount) {
|
||||
notify({
|
||||
message: LABELS.ADMIN_ACCOUNT_NOT_DEFINED,
|
||||
|
@ -46,13 +45,15 @@ export default function AddSigners({
|
|||
});
|
||||
return;
|
||||
}
|
||||
if (signers.length == 0 || (signers.length == 1 && !signers[0])) {
|
||||
if (!signers.find(s => s)) {
|
||||
notify({
|
||||
message: LABELS.ENTER_AT_LEAST_ONE_PUB_KEY,
|
||||
type: 'error',
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
|
||||
const failedSignersHold: string[] = [];
|
||||
|
||||
|
@ -79,6 +80,7 @@ export default function AddSigners({
|
|||
setSaving(false);
|
||||
setSavePerc(0);
|
||||
setIsModalVisible(failedSignersHold.length > 0);
|
||||
if (failedSignersHold.length === 0) form.resetFields();
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -0,0 +1,246 @@
|
|||
import { ParsedAccount } from '@oyster/common';
|
||||
import { Button, Modal, Input, Form, Progress, InputNumber, Radio } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { TimelockSet } from '../../models/timelock';
|
||||
import { utils, contexts, hooks } from '@oyster/common';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { LABELS } from '../../constants';
|
||||
import { mintVotingTokens } from '../../actions/mintVotingTokens';
|
||||
|
||||
const { notify } = utils;
|
||||
const { TextArea } = Input;
|
||||
const { useWallet } = contexts.Wallet;
|
||||
const { useConnection } = contexts.Connection;
|
||||
const { useAccountByMint } = hooks;
|
||||
const { deserializeAccount } = contexts.Accounts;
|
||||
|
||||
const layout = {
|
||||
labelCol: { span: 5 },
|
||||
wrapperCol: { span: 19 },
|
||||
};
|
||||
|
||||
export default function AddVotes({
|
||||
proposal,
|
||||
}: {
|
||||
proposal: ParsedAccount<TimelockSet>;
|
||||
}) {
|
||||
const PROGRAM_IDS = utils.programIds();
|
||||
const wallet = useWallet();
|
||||
const connection = useConnection();
|
||||
const sigAccount = useAccountByMint(proposal.info.signatoryMint);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [bulkModeVisible, setBulkModeVisible] = useState(false);
|
||||
const [savePerc, setSavePerc] = useState(0);
|
||||
const [failedVoters, setFailedVoters] = useState<any>([]);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const onSubmit = async (values: {
|
||||
voters: string;
|
||||
failedVoters: string;
|
||||
singleVoter: string;
|
||||
singleVoteCount: number;
|
||||
}) => {
|
||||
const { singleVoter, singleVoteCount } = values;
|
||||
const votersAndCounts = values.voters
|
||||
? values.voters.split(',').map(s => s.trim())
|
||||
: [];
|
||||
const voters: any[] = [];
|
||||
votersAndCounts.forEach((value: string, index: number) => {
|
||||
if (index % 2 == 0) voters.push([value, 0]);
|
||||
else voters[voters.length - 1][1] = parseInt(value);
|
||||
});
|
||||
console.log('Voters', votersAndCounts);
|
||||
if (singleVoter) voters.push([singleVoter, singleVoteCount]);
|
||||
|
||||
if (!sigAccount) {
|
||||
notify({
|
||||
message: LABELS.SIG_ACCOUNT_NOT_DEFINED,
|
||||
type: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!voters.find(v => v[0])) {
|
||||
notify({
|
||||
message: LABELS.ENTER_AT_LEAST_ONE_PUB_KEY,
|
||||
type: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
|
||||
if (voters.find(v => v[1] === 0)) {
|
||||
notify({
|
||||
message: LABELS.CANT_GIVE_ZERO_VOTES,
|
||||
type: 'error',
|
||||
});
|
||||
setSaving(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const failedVotersHold: any[] = [];
|
||||
|
||||
for (let i = 0; i < voters.length; i++) {
|
||||
try {
|
||||
const tokenAccounts = await connection.getTokenAccountsByOwner(
|
||||
new PublicKey(voters[i][0]),
|
||||
{
|
||||
programId: PROGRAM_IDS.token,
|
||||
},
|
||||
);
|
||||
const specificToThisMint = tokenAccounts.value.find(
|
||||
a =>
|
||||
deserializeAccount(a.account.data).mint.toBase58() ===
|
||||
proposal.info.votingMint.toBase58(),
|
||||
);
|
||||
await mintVotingTokens(
|
||||
connection,
|
||||
wallet.wallet,
|
||||
proposal,
|
||||
sigAccount.pubkey,
|
||||
new PublicKey(voters[i][0]),
|
||||
specificToThisMint?.pubkey,
|
||||
voters[i][1],
|
||||
);
|
||||
setSavePerc(Math.round(100 * ((i + 1) / voters.length)));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
failedVotersHold.push(voters[i]);
|
||||
notify({
|
||||
message: voters[i][0] + LABELS.PUB_KEY_FAILED,
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
}
|
||||
setFailedVoters(failedVotersHold);
|
||||
setSaving(false);
|
||||
setSavePerc(0);
|
||||
setIsModalVisible(failedVotersHold.length > 0);
|
||||
if (failedVotersHold.length === 0) form.resetFields();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{sigAccount ? (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setIsModalVisible(true);
|
||||
}}
|
||||
>
|
||||
{LABELS.ADD_VOTES}
|
||||
</Button>
|
||||
) : null}
|
||||
<Modal
|
||||
title={LABELS.ADD_VOTES}
|
||||
visible={isModalVisible}
|
||||
destroyOnClose={true}
|
||||
onOk={form.submit}
|
||||
zIndex={10000}
|
||||
onCancel={() => {
|
||||
if (!saving) setIsModalVisible(false);
|
||||
}}
|
||||
>
|
||||
<Form
|
||||
className={'voters-form'}
|
||||
{...layout}
|
||||
form={form}
|
||||
onFinish={onSubmit}
|
||||
name="control-hooks"
|
||||
>
|
||||
{!saving && (
|
||||
<>
|
||||
<Form.Item
|
||||
label={LABELS.VOTE_MODE}
|
||||
name="voteMode"
|
||||
initialValue={LABELS.SINGLE}
|
||||
rules={[{ required: false }]}
|
||||
>
|
||||
<Radio.Group
|
||||
value={layout}
|
||||
onChange={e =>
|
||||
setBulkModeVisible(e.target.value === LABELS.BULK)
|
||||
}
|
||||
>
|
||||
<Radio.Button value={LABELS.BULK}>{LABELS.BULK}</Radio.Button>
|
||||
<Radio.Button value={LABELS.SINGLE}>
|
||||
{LABELS.SINGLE}
|
||||
</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
{!bulkModeVisible && (
|
||||
<>
|
||||
<Form.Item
|
||||
name="singleVoter"
|
||||
label={LABELS.SINGLE_VOTER}
|
||||
rules={[{ required: false }]}
|
||||
>
|
||||
<Input placeholder={LABELS.SINGLE_KEY} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="singleVoteCount"
|
||||
label={LABELS.VOTE_COUNT}
|
||||
initialValue={0}
|
||||
rules={[{ required: false }]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
{bulkModeVisible && (
|
||||
<Form.Item
|
||||
name="voters"
|
||||
label={LABELS.BULK_VOTERS}
|
||||
rules={[{ required: false }]}
|
||||
>
|
||||
<TextArea
|
||||
id="voters"
|
||||
placeholder={LABELS.COMMA_SEPARATED_KEYS_AND_VOTES}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
{saving && <Progress percent={savePerc} status="active" />}
|
||||
|
||||
{!saving && failedVoters.length > 0 && (
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-evenly',
|
||||
alignItems: 'stretch',
|
||||
display: 'flex',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(failedVoters.join(','));
|
||||
notify({
|
||||
message: LABELS.FAILED_SIGNERS_COPIED_TO_CLIPBOARD,
|
||||
type: 'success',
|
||||
});
|
||||
}}
|
||||
>
|
||||
{LABELS.COPY_FAILED_ADDRESSES_TO_CLIPBOARD}
|
||||
</Button>
|
||||
<br />
|
||||
<Button
|
||||
onClick={() => {
|
||||
form.setFieldsValue({
|
||||
voters: failedVoters.join(','),
|
||||
});
|
||||
notify({
|
||||
message: LABELS.FAILED_SIGNERS_COPIED_TO_INPUT,
|
||||
type: 'success',
|
||||
});
|
||||
}}
|
||||
>
|
||||
{LABELS.COPY_FAILED_ADDRESSES_TO_INPUT}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,7 +1,11 @@
|
|||
import { ParsedAccount } from '@oyster/common';
|
||||
import { Badge, Tag } from 'antd';
|
||||
import React from 'react';
|
||||
import { STATE_COLOR, TimelockSet } from '../../models/timelock';
|
||||
import {
|
||||
STATE_COLOR,
|
||||
TimelockSet,
|
||||
TimelockStateStatus,
|
||||
} from '../../models/timelock';
|
||||
|
||||
export function StateBadgeRibbon({
|
||||
proposal,
|
||||
|
@ -26,5 +30,5 @@ export function StateBadge({
|
|||
}) {
|
||||
const status = proposal.info.state.status;
|
||||
let color = STATE_COLOR[status];
|
||||
return <Tag color={color}>{status}</Tag>;
|
||||
return <Tag color={color}>{TimelockStateStatus[status]}</Tag>;
|
||||
}
|
||||
|
|
|
@ -28,10 +28,28 @@ export const LABELS = {
|
|||
SIGNERS: 'Signers',
|
||||
ADD_SIGNERS: 'Add Signers',
|
||||
ADMIN_ACCOUNT_NOT_DEFINED: 'Admin account is not defined',
|
||||
SIG_ACCOUNT_NOT_DEFINED: 'Signature account is not defined',
|
||||
ENTER_AT_LEAST_ONE_PUB_KEY: 'Please enter at least one pub key.',
|
||||
PUB_KEY_FAILED:
|
||||
" Pub key failed. Please check your inspector tab for more information. We'll continue onward and add this to a list for you to re-upload in a later save.",
|
||||
ADD: 'Add',
|
||||
REMOVE: 'Remove',
|
||||
ADDING_OR_REMOVING: 'Type',
|
||||
ADDING_VOTES_TO_VOTER: 'Adding votes to voter',
|
||||
PLEASE_WAIT: 'Please wait...',
|
||||
VOTES_ADDED: 'Votes added.',
|
||||
NEW_VOTED_ACCOUNT_ADDED: 'New vote account added.',
|
||||
ADDING_NEW_VOTE_ACCOUNT: 'Adding new vote account...',
|
||||
TRANSACTION: 'Transaction - ',
|
||||
CANT_GIVE_ZERO_VOTES: "Can't give zero votes to a user!",
|
||||
BULK_VOTERS: 'Voters',
|
||||
COMMA_SEPARATED_KEYS_AND_VOTES:
|
||||
'base58 pubkey, vote count, base58 pubkey, vote count, ...',
|
||||
SINGLE_VOTER: 'Single Voter',
|
||||
VOTE_COUNT: 'Vote Amount',
|
||||
SINGLE_KEY: 'base58 pubkey',
|
||||
VOTE_MODE: 'Vote Mode',
|
||||
BULK: 'Bulk',
|
||||
SINGLE: 'Single',
|
||||
ADD_VOTES: 'Add Votes',
|
||||
};
|
||||
|
|
|
@ -63,7 +63,7 @@ export const addCustomSingleSignerTransactionInstruction = (
|
|||
|
||||
dataLayout.encode(
|
||||
{
|
||||
instruction: TimelockInstruction.addCustomSingleSignerTransaction,
|
||||
instruction: TimelockInstruction.AddCustomSingleSignerTransaction,
|
||||
slot: new BN(slot),
|
||||
instructions: instructionAsBytes,
|
||||
position: position,
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import { PublicKey, TransactionInstruction } from '@solana/web3.js';
|
||||
import { utils } from '@oyster/common';
|
||||
import * as Layout from '../utils/layout';
|
||||
|
||||
import * as BufferLayout from 'buffer-layout';
|
||||
import { TimelockInstruction } from './timelock';
|
||||
import BN from 'bn.js';
|
||||
|
||||
/// [Requires Signatory token]
|
||||
/// Mints voting tokens for a destination account to be used during the voting process.
|
||||
///
|
||||
/// 0. `[writable]` Timelock set account.
|
||||
/// 1. `[writable]` Initialized Voting account.
|
||||
/// 2. `[writable]` Voting mint account.
|
||||
/// 3. `[writable]` Signatory account
|
||||
/// 4. `[writable]` Signatory validation account.
|
||||
/// 5. `[]` Transfer authority
|
||||
/// 6. `[]` Timelock program mint authority
|
||||
/// 7. `[]` Timelock program account pub key.
|
||||
/// 8. `[]` Token program account.
|
||||
export const mintVotingTokensInstruction = (
|
||||
timelockSetAccount: PublicKey,
|
||||
votingAccount: PublicKey,
|
||||
votingMint: PublicKey,
|
||||
signatoryAccount: PublicKey,
|
||||
signatoryValidationAccount: PublicKey,
|
||||
transferAuthority: PublicKey,
|
||||
mintAuthority: PublicKey,
|
||||
votingTokenAmount: number,
|
||||
): TransactionInstruction => {
|
||||
const PROGRAM_IDS = utils.programIds();
|
||||
|
||||
const dataLayout = BufferLayout.struct([
|
||||
BufferLayout.u8('instruction'),
|
||||
Layout.uint64('votingTokenAmount'),
|
||||
]);
|
||||
|
||||
const data = Buffer.alloc(dataLayout.span);
|
||||
|
||||
dataLayout.encode(
|
||||
{
|
||||
instruction: TimelockInstruction.MintVotingTokens,
|
||||
votingTokenAmount: new BN(votingTokenAmount),
|
||||
},
|
||||
data,
|
||||
);
|
||||
|
||||
const keys = [
|
||||
{ pubkey: timelockSetAccount, isSigner: false, isWritable: true },
|
||||
{ pubkey: votingAccount, isSigner: false, isWritable: true },
|
||||
{ pubkey: votingMint, isSigner: false, isWritable: true },
|
||||
{ pubkey: signatoryAccount, isSigner: false, isWritable: true },
|
||||
{ pubkey: signatoryValidationAccount, isSigner: false, isWritable: true },
|
||||
{ pubkey: transferAuthority, isSigner: true, isWritable: false },
|
||||
{ pubkey: mintAuthority, isSigner: false, isWritable: false },
|
||||
{
|
||||
pubkey: PROGRAM_IDS.timelock.programAccountId,
|
||||
isSigner: false,
|
||||
isWritable: false,
|
||||
},
|
||||
{ pubkey: PROGRAM_IDS.token, isSigner: false, isWritable: false },
|
||||
];
|
||||
|
||||
return new TransactionInstruction({
|
||||
keys,
|
||||
programId: PROGRAM_IDS.timelock.programId,
|
||||
data,
|
||||
});
|
||||
};
|
|
@ -13,8 +13,9 @@ export enum TimelockInstruction {
|
|||
InitTimelockSet = 1,
|
||||
AddSigner = 2,
|
||||
RemoveSigner = 3,
|
||||
addCustomSingleSignerTransaction = 4,
|
||||
AddCustomSingleSignerTransaction = 4,
|
||||
Sign = 8,
|
||||
MintVotingTokens = 10,
|
||||
}
|
||||
|
||||
export interface TimelockConfig {
|
||||
|
@ -55,11 +56,11 @@ export enum TimelockStateStatus {
|
|||
}
|
||||
|
||||
export const STATE_COLOR: Record<string, string> = {
|
||||
Draft: 'orange',
|
||||
Voting: 'blue',
|
||||
Executing: 'green',
|
||||
Completed: 'purple',
|
||||
Deleted: 'gray',
|
||||
[TimelockStateStatus.Draft]: 'orange',
|
||||
[TimelockStateStatus.Voting]: 'blue',
|
||||
[TimelockStateStatus.Executing]: 'green',
|
||||
[TimelockStateStatus.Completed]: 'purple',
|
||||
[TimelockStateStatus.Deleted]: 'gray',
|
||||
};
|
||||
|
||||
export interface TimelockState {
|
||||
|
@ -154,7 +155,7 @@ export const TimelockSetParser = (
|
|||
adminValidation: data.adminValidation,
|
||||
votingValidation: data.votingValidation,
|
||||
state: {
|
||||
status: TimelockStateStatus[data.timelockStateStatus],
|
||||
status: data.timelockStateStatus,
|
||||
totalVotingTokensMinted: data.totalVotingTokensMinted,
|
||||
totalSigningTokensMinted: data.totalSigningTokensMinted,
|
||||
descLink: utils.fromUTF8Array(data.descLink).replaceAll('\u0000', ''),
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
ConsensusAlgorithm,
|
||||
INSTRUCTION_LIMIT,
|
||||
TimelockSet,
|
||||
TimelockStateStatus,
|
||||
TimelockTransaction,
|
||||
} from '../../models/timelock';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
@ -18,6 +19,7 @@ import { InstructionCard } from '../../components/Proposal/InstructionCard';
|
|||
import { NewInstructionCard } from '../../components/Proposal/NewInstructionCard';
|
||||
import SignButton from '../../components/Proposal/SignButton';
|
||||
import AddSigners from '../../components/Proposal/AddSigners';
|
||||
import AddVotes from '../../components/Proposal/AddVotes';
|
||||
export const urlRegex = /[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;
|
||||
const { useMint } = contexts.Accounts;
|
||||
const { useAccountByMint } = hooks;
|
||||
|
@ -162,10 +164,14 @@ function InnerProposalView({
|
|||
: 'vertical'
|
||||
}
|
||||
>
|
||||
{adminAccount && adminAccount.info.amount.toNumber() === 1 && (
|
||||
{adminAccount &&
|
||||
adminAccount.info.amount.toNumber() === 1 &&
|
||||
proposal.info.state.status === TimelockStateStatus.Draft && (
|
||||
<AddSigners proposal={proposal} />
|
||||
)}
|
||||
{sigAccount && sigAccount.info.amount.toNumber() === 1 && (
|
||||
{sigAccount &&
|
||||
sigAccount.info.amount.toNumber() === 1 &&
|
||||
proposal.info.state.status === TimelockStateStatus.Draft && (
|
||||
<SignButton proposal={proposal} />
|
||||
)}
|
||||
</Space>
|
||||
|
@ -179,6 +185,20 @@ function InnerProposalView({
|
|||
}
|
||||
suffix={`/ ${proposal.info.state.totalVotingTokensMinted}`}
|
||||
/>
|
||||
<Space
|
||||
style={{ marginTop: '10px' }}
|
||||
direction={
|
||||
breakpoint.lg || breakpoint.xl || breakpoint.xxl
|
||||
? 'horizontal'
|
||||
: 'vertical'
|
||||
}
|
||||
>
|
||||
{sigAccount &&
|
||||
sigAccount.info.amount.toNumber() === 1 &&
|
||||
proposal.info.state.status === TimelockStateStatus.Draft && (
|
||||
<AddVotes proposal={proposal} />
|
||||
)}
|
||||
</Space>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Statistic
|
||||
|
@ -218,11 +238,15 @@ function InnerProposalView({
|
|||
|
||||
function getVotesRequired(proposal: ParsedAccount<TimelockSet>): number {
|
||||
if (proposal.info.config.consensusAlgorithm === ConsensusAlgorithm.Majority) {
|
||||
return proposal.info.state.totalVotingTokensMinted.toNumber() * 0.5;
|
||||
return Math.ceil(
|
||||
proposal.info.state.totalVotingTokensMinted.toNumber() * 0.5,
|
||||
);
|
||||
} else if (
|
||||
proposal.info.config.consensusAlgorithm === ConsensusAlgorithm.SuperMajority
|
||||
) {
|
||||
return proposal.info.state.totalVotingTokensMinted.toNumber() * 0.6;
|
||||
return Math.ceil(
|
||||
proposal.info.state.totalVotingTokensMinted.toNumber() * 0.5,
|
||||
);
|
||||
} else if (
|
||||
proposal.info.config.consensusAlgorithm === ConsensusAlgorithm.FullConsensus
|
||||
) {
|
||||
|
|
Loading…
Reference in New Issue