mirror of https://github.com/certusone/oyster.git
Massive wip commit - added form for creating proposals, and working on displaying transactions
This commit is contained in:
parent
5cfd98264c
commit
d4bf02c790
|
@ -69,9 +69,9 @@ export const PROGRAM_IDS = [
|
|||
name: 'testnet',
|
||||
timelock: () => ({
|
||||
programAccountId: new PublicKey(
|
||||
'52goe5j8e25JWzcH6WnAjZ8bWfp4KwxPZsaxQ4fK1Xwx',
|
||||
'32siiZ12jH2i7BM2wtNuTDw2Hs5nBjVQuBh4P8cTSH3i',
|
||||
),
|
||||
programId: new PublicKey('5n4ciqDUt5MwRMFsViBXfQLGfqfg9mSZqgaKLu8zyrgw'),
|
||||
programId: new PublicKey('5KrVJvesyjdMy6Vq5wfuPSMdw7vWuUvtbHG98wBsEkX6'),
|
||||
}),
|
||||
wormhole: () => ({
|
||||
pubkey: new PublicKey('5gQf5AUhAgWYgUCt9ouShm9H7dzzXUsLdssYwe5krKhg'),
|
||||
|
@ -90,9 +90,9 @@ export const PROGRAM_IDS = [
|
|||
name: 'devnet',
|
||||
timelock: () => ({
|
||||
programAccountId: new PublicKey(
|
||||
'H59qsZj8wwA9h45UTbVwuYePcXXhKeeAtab3ujdQoqRc',
|
||||
'H4yVRsmzbbE9fMUREPQWsu7auiYgE398iF5rq5nHLdq4',
|
||||
),
|
||||
programId: new PublicKey('DCPZ3VHspU2LGSHs2gaqUXYQykZ9C4iFX68KqATjBcLG'),
|
||||
programId: new PublicKey('2NKCKpE1kNhY3Qr3VGikPQtiENTjSEFsJN6NyAUpoFaD'),
|
||||
}),
|
||||
wormhole: () => ({
|
||||
pubkey: new PublicKey('WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC'),
|
||||
|
@ -111,9 +111,9 @@ export const PROGRAM_IDS = [
|
|||
name: 'localnet',
|
||||
timelock: () => ({
|
||||
programAccountId: new PublicKey(
|
||||
'9gBhDCCKV7KELLFRY8sAJZXqDmvUfmNzFzpB2b4FUVVr',
|
||||
'GLmfd9F4bEY43Sg35RhvMN3N4uf5MvnTbX1bkKxVJ958',
|
||||
),
|
||||
programId: new PublicKey('9iAeqqppjn7g1Jn8o2cQCqU5aQVV3h4q9bbWdKRbeC2w'),
|
||||
programId: new PublicKey('Dj4W4ZEs5ATmAjx7pRt2gSTCgPHcjYig6KXZ4vBhPXrr'),
|
||||
}),
|
||||
wormhole: () => ({
|
||||
pubkey: new PublicKey('WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC'),
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
SystemProgram,
|
||||
TransactionInstruction,
|
||||
} from '@solana/web3.js';
|
||||
import { contexts, utils, actions, ParsedAccount } from '@oyster/common';
|
||||
import { contexts, utils, models, ParsedAccount } from '@oyster/common';
|
||||
|
||||
import {
|
||||
CustomSingleSignerTimelockTransactionLayout,
|
||||
|
@ -15,6 +15,7 @@ import { addCustomSingleSignerTransactionInstruction } from '../models/addCustom
|
|||
|
||||
const { sendTransaction } = contexts.Connection;
|
||||
const { notify } = utils;
|
||||
const { approve } = models;
|
||||
|
||||
export const addCustomSingleSignerTransaction = async (
|
||||
connection: Connection,
|
||||
|
@ -49,12 +50,22 @@ export const addCustomSingleSignerTransaction = async (
|
|||
|
||||
instructions.push(uninitializedTxnInstruction);
|
||||
|
||||
const transferAuthority = approve(
|
||||
instructions,
|
||||
[],
|
||||
sigAccount,
|
||||
wallet.publicKey,
|
||||
1,
|
||||
);
|
||||
signers.push(transferAuthority);
|
||||
|
||||
instructions.push(
|
||||
addCustomSingleSignerTransactionInstruction(
|
||||
txnKey.publicKey,
|
||||
proposal.pubkey,
|
||||
sigAccount,
|
||||
proposal.info.signatoryValidation,
|
||||
transferAuthority.publicKey,
|
||||
authority,
|
||||
'123',
|
||||
'12345',
|
||||
|
|
|
@ -20,7 +20,15 @@ const { sendTransaction } = contexts.Connection;
|
|||
const { createMint, createTokenAccount, createUninitializedMint } = actions;
|
||||
const { notify } = utils;
|
||||
|
||||
export const createProposal = async (connection: Connection, wallet: any) => {
|
||||
export const createProposal = async (
|
||||
connection: Connection,
|
||||
wallet: any,
|
||||
name: string,
|
||||
description: string,
|
||||
timelockType: TimelockType,
|
||||
consensusAlgorithm: ConsensusAlgorithm,
|
||||
executionType: ExecutionType,
|
||||
): Promise<Account> => {
|
||||
const PROGRAM_IDS = utils.programIds();
|
||||
|
||||
let signers: Account[] = [];
|
||||
|
@ -79,12 +87,12 @@ export const createProposal = async (connection: Connection, wallet: any) => {
|
|||
sigDestinationAccount,
|
||||
authority,
|
||||
{
|
||||
timelockType: TimelockType.CustomSingleSignerV1,
|
||||
consensusAlgorithm: ConsensusAlgorithm.Majority,
|
||||
executionType: ExecutionType.AllOrNothing,
|
||||
timelockType,
|
||||
consensusAlgorithm,
|
||||
executionType,
|
||||
},
|
||||
'https://gist.github.com/dummytester123/bd3567f80e13a27b02a2e0fb891ecab1',
|
||||
'Name',
|
||||
description,
|
||||
name,
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -108,6 +116,8 @@ export const createProposal = async (connection: Connection, wallet: any) => {
|
|||
type: 'success',
|
||||
description: `Transaction - ${tx}`,
|
||||
});
|
||||
|
||||
return timelockSetKey;
|
||||
} catch (ex) {
|
||||
console.error(ex);
|
||||
throw new Error();
|
||||
|
@ -209,7 +219,7 @@ async function createValidationAccountsAndMints(
|
|||
wallet.publicKey,
|
||||
accountRentExempt,
|
||||
adminMint,
|
||||
PROGRAM_IDS.timelock.programAccountId,
|
||||
authority,
|
||||
signers,
|
||||
);
|
||||
|
||||
|
@ -218,7 +228,7 @@ async function createValidationAccountsAndMints(
|
|||
wallet.publicKey,
|
||||
accountRentExempt,
|
||||
sigMint,
|
||||
PROGRAM_IDS.timelock.programAccountId,
|
||||
authority,
|
||||
signers,
|
||||
);
|
||||
|
||||
|
@ -227,7 +237,7 @@ async function createValidationAccountsAndMints(
|
|||
wallet.publicKey,
|
||||
accountRentExempt,
|
||||
voteMint,
|
||||
PROGRAM_IDS.timelock.programAccountId,
|
||||
authority,
|
||||
signers,
|
||||
);
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ import {
|
|||
GithubOutlined,
|
||||
HomeOutlined,
|
||||
ForkOutlined,
|
||||
BulbOutlined,
|
||||
UploadOutlined,
|
||||
// LineChartOutlined
|
||||
} from '@ant-design/icons';
|
||||
|
||||
|
@ -15,6 +17,7 @@ import { Link, useLocation } from 'react-router-dom';
|
|||
import { LABELS } from '../../constants';
|
||||
import config from './../../../package.json';
|
||||
import { contexts, components } from '@oyster/common';
|
||||
import { NewFormMenuItem } from '../../views/proposal/new';
|
||||
|
||||
const { AppBar } = components;
|
||||
const { useConnectionConfig } = contexts.Connection;
|
||||
|
@ -24,6 +27,7 @@ export const AppLayout = React.memo((props: any) => {
|
|||
const location = useLocation();
|
||||
|
||||
const paths: { [key: string]: string } = {
|
||||
'/': '1',
|
||||
'/dashboard': '2',
|
||||
};
|
||||
|
||||
|
@ -62,7 +66,7 @@ export const AppLayout = React.memo((props: any) => {
|
|||
defaultSelectedKeys={[defaultKey]}
|
||||
mode="inline"
|
||||
>
|
||||
<Menu.Item key="1" icon={<HomeOutlined />}>
|
||||
<Menu.Item key="1" icon={<BulbOutlined />}>
|
||||
<Link
|
||||
to={{
|
||||
pathname: '/',
|
||||
|
@ -71,7 +75,10 @@ export const AppLayout = React.memo((props: any) => {
|
|||
{LABELS.MENU_HOME}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="2" icon={<PieChartOutlined />}>
|
||||
<Menu.Item key="2" icon={<UploadOutlined />}>
|
||||
<NewFormMenuItem />
|
||||
</Menu.Item>
|
||||
<Menu.Item key="3" icon={<PieChartOutlined />}>
|
||||
<Link
|
||||
to={{
|
||||
pathname: '/dashboard',
|
||||
|
|
|
@ -4,7 +4,8 @@ export const LABELS = {
|
|||
'Oyster is an unaudited software project used for internal purposes at the Solana Foundation. This app is not for public use.',
|
||||
FOOTER:
|
||||
'This page was produced by the Solana Foundation ("SF") for internal educational and inspiration purposes only. SF does not encourage, induce or sanction the deployment, integration or use of Oyster or any similar application (including its code) in violation of applicable laws or regulations and hereby prohibits any such deployment, integration or use. Anyone using this code or a derivation thereof must comply with applicable laws and regulations when releasing related software.',
|
||||
MENU_HOME: 'Home',
|
||||
MENU_HOME: 'Proposals',
|
||||
NEW_PROPOSAL: 'New Proposal',
|
||||
MENU_DASHBOARD: 'Dashboard',
|
||||
APP_TITLE: 'Oyster Proposals',
|
||||
CONNECT_BUTTON: 'Connect',
|
||||
|
|
|
@ -9,18 +9,21 @@ import { useMemo } from 'react';
|
|||
|
||||
import { contexts, utils, ParsedAccount } from '@oyster/common';
|
||||
import {
|
||||
CustomSingleSignerTimelockTransaction,
|
||||
CustomSingleSignerTimelockTransactionLayout,
|
||||
CustomSingleSignerTimelockTransactionParser,
|
||||
TimelockSet,
|
||||
TimelockSetLayout,
|
||||
TimelockSetParser,
|
||||
Transaction,
|
||||
} from '../models/timelock';
|
||||
|
||||
const { useWallet } = contexts.Wallet;
|
||||
|
||||
const { useConnectionConfig } = contexts.Connection;
|
||||
const { cache } = contexts.Accounts;
|
||||
|
||||
export interface ProposalsContextState {
|
||||
proposals: Record<string, ParsedAccount<TimelockSet>>;
|
||||
transactions: Record<string, ParsedAccount<Transaction>>;
|
||||
}
|
||||
|
||||
export const ProposalsContext = React.createContext<ProposalsContextState | null>(
|
||||
|
@ -34,11 +37,16 @@ export default function ProposalsProvider({ children = null as any }) {
|
|||
]);
|
||||
|
||||
const [proposals, setProposals] = useState({});
|
||||
const [transactions, setTransactions] = useState({});
|
||||
|
||||
useSetupProposalsCache({ connection, setProposals });
|
||||
useSetupProposalsCache({
|
||||
connection,
|
||||
setProposals,
|
||||
setTransactions,
|
||||
});
|
||||
|
||||
return (
|
||||
<ProposalsContext.Provider value={{ proposals }}>
|
||||
<ProposalsContext.Provider value={{ proposals, transactions }}>
|
||||
{children}
|
||||
</ProposalsContext.Provider>
|
||||
);
|
||||
|
@ -47,48 +55,72 @@ export default function ProposalsProvider({ children = null as any }) {
|
|||
function useSetupProposalsCache({
|
||||
connection,
|
||||
setProposals,
|
||||
setTransactions,
|
||||
}: {
|
||||
connection: Connection;
|
||||
setProposals: React.Dispatch<React.SetStateAction<{}>>;
|
||||
setTransactions: React.Dispatch<React.SetStateAction<{}>>;
|
||||
}) {
|
||||
const PROGRAM_IDS = utils.programIds();
|
||||
|
||||
useEffect(() => {
|
||||
const queryProposals = async () => {
|
||||
const query = async () => {
|
||||
const programAccounts = await connection.getProgramAccounts(
|
||||
PROGRAM_IDS.timelock.programId,
|
||||
);
|
||||
return programAccounts;
|
||||
};
|
||||
Promise.all([queryProposals()]).then(
|
||||
(all: PublicKeyAndAccount<Buffer>[][]) => {
|
||||
const newProposals: Record<string, ParsedAccount<TimelockSet>> = {};
|
||||
all[0].forEach(a => {
|
||||
if (a.account.data.length === TimelockSetLayout.span) {
|
||||
cache.add(a.pubkey, a.account, TimelockSetParser);
|
||||
const cached = cache.get(a.pubkey) as ParsedAccount<TimelockSet>;
|
||||
console.log('Got', a.pubkey.toBase58());
|
||||
newProposals[a.pubkey.toBase58()] = cached;
|
||||
}
|
||||
});
|
||||
setProposals(newProposals);
|
||||
},
|
||||
);
|
||||
Promise.all([query()]).then((all: PublicKeyAndAccount<Buffer>[][]) => {
|
||||
const newProposals: Record<string, ParsedAccount<TimelockSet>> = {};
|
||||
const newTransactions: Record<string, ParsedAccount<Transaction>> = {};
|
||||
|
||||
all[0].forEach(a => {
|
||||
if (a.account.data.length === TimelockSetLayout.span) {
|
||||
cache.add(a.pubkey, a.account, TimelockSetParser);
|
||||
const cached = cache.get(a.pubkey) as ParsedAccount<TimelockSet>;
|
||||
newProposals[a.pubkey.toBase58()] = cached;
|
||||
}
|
||||
if (
|
||||
a.account.data.length ===
|
||||
CustomSingleSignerTimelockTransactionLayout.span
|
||||
) {
|
||||
cache.add(
|
||||
a.pubkey,
|
||||
a.account,
|
||||
CustomSingleSignerTimelockTransactionParser,
|
||||
);
|
||||
const cached = cache.get(a.pubkey) as ParsedAccount<Transaction>;
|
||||
newTransactions[a.pubkey.toBase58()] = cached;
|
||||
}
|
||||
});
|
||||
setProposals(newProposals);
|
||||
});
|
||||
const subID = connection.onProgramAccountChange(
|
||||
PROGRAM_IDS.timelock.programId,
|
||||
async (info: KeyedAccountInfo) => {
|
||||
if (info.accountInfo.data.length === TimelockSetLayout.span) {
|
||||
cache.add(info.accountId, info.accountInfo, TimelockSetParser);
|
||||
const cached = cache.get(
|
||||
info.accountId,
|
||||
) as ParsedAccount<TimelockSet>;
|
||||
setProposals(proposals => ({
|
||||
...proposals,
|
||||
[typeof info.accountId === 'string'
|
||||
? info.accountId
|
||||
: info.accountId.toBase58()]: cached,
|
||||
}));
|
||||
}
|
||||
[
|
||||
[TimelockSetLayout.span, TimelockSetParser, setProposals],
|
||||
[
|
||||
CustomSingleSignerTimelockTransactionLayout.span,
|
||||
CustomSingleSignerTimelockTransactionParser,
|
||||
setTransactions,
|
||||
],
|
||||
].forEach(arr => {
|
||||
const [span, parser, setter] = arr;
|
||||
if (info.accountInfo.data.length === span) {
|
||||
cache.add(info.accountId, info.accountInfo, parser);
|
||||
const cached =
|
||||
span === TimelockSetLayout.span
|
||||
? (cache.get(info.accountId) as ParsedAccount<TimelockSet>)
|
||||
: (cache.get(info.accountId) as ParsedAccount<Transaction>);
|
||||
setter((obj: any) => ({
|
||||
...obj,
|
||||
[typeof info.accountId === 'string'
|
||||
? info.accountId
|
||||
: info.accountId.toBase58()]: cached,
|
||||
}));
|
||||
}
|
||||
});
|
||||
},
|
||||
'singleGossip',
|
||||
);
|
||||
|
|
|
@ -1,17 +1,10 @@
|
|||
import {
|
||||
PublicKey,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
TransactionInstruction,
|
||||
} from '@solana/web3.js';
|
||||
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 {
|
||||
DESC_SIZE,
|
||||
INSTRUCTION_LIMIT,
|
||||
NAME_SIZE,
|
||||
TimelockConfig,
|
||||
TimelockInstruction,
|
||||
TRANSACTION_SLOTS,
|
||||
} from './timelock';
|
||||
|
@ -27,13 +20,15 @@ import { toUTF8Array } from '@oyster/common/dist/lib/utils';
|
|||
/// 1. `[writable]` Timelock set account.
|
||||
/// 2. `[writable]` Signatory account
|
||||
/// 3. `[writable]` Signatory validation account.
|
||||
/// 4. `[]` Timelock program account.
|
||||
/// 5. `[]` Token program account.
|
||||
/// 4. `[]` Transfer authority
|
||||
/// 5. `[]` Timelock program account.
|
||||
/// 6. `[]` Token program account.
|
||||
export const addCustomSingleSignerTransactionInstruction = (
|
||||
timelockTransactionAccount: PublicKey,
|
||||
timelockSetAccount: PublicKey,
|
||||
signatoryAccount: PublicKey,
|
||||
signatoryValidationAccount: PublicKey,
|
||||
transferAuthority: PublicKey,
|
||||
authority: PublicKey,
|
||||
slot: string,
|
||||
instruction: string,
|
||||
|
@ -82,6 +77,7 @@ export const addCustomSingleSignerTransactionInstruction = (
|
|||
{ pubkey: timelockSetAccount, isSigner: false, isWritable: true },
|
||||
{ pubkey: signatoryAccount, isSigner: false, isWritable: true },
|
||||
{ pubkey: signatoryValidationAccount, isSigner: false, isWritable: true },
|
||||
{ pubkey: transferAuthority, isSigner: true, isWritable: false },
|
||||
{ pubkey: authority, isSigner: false, isWritable: false },
|
||||
{
|
||||
pubkey: PROGRAM_IDS.timelock.programAccountId,
|
||||
|
@ -90,7 +86,7 @@ export const addCustomSingleSignerTransactionInstruction = (
|
|||
},
|
||||
{ pubkey: PROGRAM_IDS.token, isSigner: false, isWritable: false },
|
||||
];
|
||||
console.log('data', data);
|
||||
|
||||
return new TransactionInstruction({
|
||||
keys,
|
||||
programId: PROGRAM_IDS.timelock.programId,
|
||||
|
|
|
@ -169,6 +169,31 @@ export const TimelockSetParser = (
|
|||
return details;
|
||||
};
|
||||
|
||||
export const CustomSingleSignerTimelockTransactionParser = (
|
||||
pubKey: PublicKey,
|
||||
info: AccountInfo<Buffer>,
|
||||
) => {
|
||||
const buffer = Buffer.from(info.data);
|
||||
const data = CustomSingleSignerTimelockTransactionLayout.decode(buffer);
|
||||
|
||||
const details = {
|
||||
pubkey: pubKey,
|
||||
account: {
|
||||
...info,
|
||||
},
|
||||
info: {
|
||||
version: data.version,
|
||||
slot: data.slot,
|
||||
instruction: utils
|
||||
.fromUTF8Array(data.instruction)
|
||||
.replaceAll('\u0000', ''),
|
||||
authorityKey: data.authorityKey,
|
||||
},
|
||||
};
|
||||
|
||||
return details;
|
||||
};
|
||||
|
||||
export const CustomSingleSignerTimelockTransactionLayout: typeof BufferLayout.Structure = BufferLayout.struct(
|
||||
[
|
||||
BufferLayout.u8('version'),
|
||||
|
@ -177,12 +202,14 @@ export const CustomSingleSignerTimelockTransactionLayout: typeof BufferLayout.St
|
|||
Layout.publicKey('authorityKey'),
|
||||
],
|
||||
);
|
||||
export interface CustomSingleSignerTimelockTransaction {
|
||||
|
||||
export interface Transaction {
|
||||
version: number;
|
||||
|
||||
slot: BN;
|
||||
|
||||
instruction: string;
|
||||
|
||||
}
|
||||
export interface CustomSingleSignerTimelockTransaction extends Transaction {
|
||||
authorityKey: PublicKey;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,12 @@ import { contexts, hooks, ParsedAccount } from '@oyster/common';
|
|||
import './style.less';
|
||||
import { createProposal } from '../../actions/createProposal';
|
||||
import { useProposals } from '../../contexts/proposals';
|
||||
import { TimelockSet } from '../../models/timelock';
|
||||
import {
|
||||
ConsensusAlgorithm,
|
||||
ExecutionType,
|
||||
TimelockSet,
|
||||
TimelockType,
|
||||
} from '../../models/timelock';
|
||||
import { Connection } from '@solana/web3.js';
|
||||
import { addCustomSingleSignerTransaction } from '../../actions/addCustomSingleSignerTransaction';
|
||||
const { useWallet } = contexts.Wallet;
|
||||
|
@ -23,7 +28,19 @@ export const DashboardView = () => {
|
|||
return (
|
||||
<div className="dashboard-container">
|
||||
<Row gutter={GUTTER} className="home-info-row">
|
||||
<Button onClick={() => createProposal(connection, wallet.wallet)}>
|
||||
<Button
|
||||
onClick={() =>
|
||||
createProposal(
|
||||
connection,
|
||||
wallet.wallet,
|
||||
'Billy',
|
||||
'https://gist.github.com/dummytester123/bd3567f80e13a27b02a2e0fb891ecab1',
|
||||
TimelockType.CustomSingleSignerV1,
|
||||
ConsensusAlgorithm.Majority,
|
||||
ExecutionType.AnyAboveVoteFinishSlot,
|
||||
)
|
||||
}
|
||||
>
|
||||
Add Proposal
|
||||
</Button>
|
||||
</Row>
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
import { Col, Divider, Row, Space, Spin, Statistic } from 'antd';
|
||||
import { Card, Col, Divider, Row, Space, Spin, Statistic } from 'antd';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { LABELS } from '../../constants';
|
||||
import { ParsedAccount } from '@oyster/common';
|
||||
import { ConsensusAlgorithm, TimelockSet } from '../../models/timelock';
|
||||
import {
|
||||
ConsensusAlgorithm,
|
||||
TimelockSet,
|
||||
Transaction,
|
||||
} from '../../models/timelock';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { useProposals } from '../../contexts/proposals';
|
||||
import { StateBadge } from '../../components/Proposal/StateBadge';
|
||||
import { contexts } from '@oyster/common';
|
||||
import { MintInfo } from '@solana/spl-token';
|
||||
import Meta from 'antd/lib/card/Meta';
|
||||
import './style.less';
|
||||
import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
|
||||
export const urlRegex = /[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;
|
||||
const { useMint } = contexts.Accounts;
|
||||
|
||||
|
@ -25,6 +32,7 @@ export const ProposalView = () => {
|
|||
proposal={proposal}
|
||||
votingMint={votingMint}
|
||||
sigMint={sigMint}
|
||||
instructions={context.transactions}
|
||||
/>
|
||||
) : (
|
||||
<Spin />
|
||||
|
@ -37,10 +45,12 @@ function InnerProposalView({
|
|||
proposal,
|
||||
sigMint,
|
||||
votingMint,
|
||||
instructions,
|
||||
}: {
|
||||
proposal: ParsedAccount<TimelockSet>;
|
||||
sigMint: MintInfo;
|
||||
votingMint: MintInfo;
|
||||
instructions: Record<string, ParsedAccount<Transaction>>;
|
||||
}) {
|
||||
const isUrl = !!proposal.info.state.descLink.match(urlRegex);
|
||||
const isGist =
|
||||
|
@ -154,11 +164,72 @@ function InnerProposalView({
|
|||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row
|
||||
gutter={[
|
||||
{ xs: 8, sm: 16, md: 24, lg: 32 },
|
||||
{ xs: 8, sm: 16, md: 24, lg: 32 },
|
||||
]}
|
||||
>
|
||||
{[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(position => (
|
||||
<Col xs={24} sm={24} md={12} lg={8}>
|
||||
<InstructionCard
|
||||
position={position}
|
||||
instruction={{
|
||||
info: {
|
||||
slot: 5,
|
||||
instruction:
|
||||
'Dhr8DF4Wsr9WrwSJixqfe7YFwdgz3GtjTRGF4wRMw3rP91NUmzR8PGJvCw7CpFDnUMij5kSpWpocaAftpCxBJ1oneK89CeWXGEVhyYTxAnhudvUSCXtvvjNhiFC8NQhCHDskA6MdsGJP2Dd1w3kihZeB5N9sXT7NjwZCCZeH5qL5tFhgUja9Nv1ywGvU1WnXDwxsRvPGf5AQNZ5xwwrr7chFV7DoTnwc9D7dJwx3m9mMBdSUcbtP77pUxvZbN1Sw9CTpM2rUUvwSxy2cYJPcgnNKZyq3CPYCa9H2Z9KR2AigcMgRzYswYhBGiz8piMVYL1rQRmRJVnoWALh3K1rS3BPRmvq52G7EJzXdoKiQQU6zzdzFuP6TGvdJrwfppzZrFRQ3n6bb3RaBoqoopnrLwWQA673KEeJCngwnipnTo5K6i72jJBBniqmanmtuFhVne2Gr2QUxDynqwBRvbpBT4uAibmu6PRUL9BhVDJibrb7rJf9RY2TL',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</Space>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function InstructionCard({
|
||||
instruction,
|
||||
position,
|
||||
}: {
|
||||
instruction: any; //ParsedAccount<Transaction>;
|
||||
position: number;
|
||||
}) {
|
||||
const [tabKey, setTabKey] = useState('info');
|
||||
|
||||
const contentList: Record<string, JSX.Element> = {
|
||||
info: (
|
||||
<Meta
|
||||
title={'Program: TODO'}
|
||||
description={
|
||||
<>
|
||||
<p>Instruction: TODO</p>
|
||||
<p>Slot: {instruction.info.slot}</p>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
data: <p className="wordwrap">{instruction.info.instruction}</p>,
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
tabList={[
|
||||
{ key: 'info', tab: 'Info' },
|
||||
{ key: 'data', tab: 'Data' },
|
||||
]}
|
||||
title={'Instruction #' + position}
|
||||
activeTabKey={tabKey}
|
||||
onTabChange={setTabKey}
|
||||
actions={[<EditOutlined key="edit" />, <DeleteOutlined key="delete" />]}
|
||||
>
|
||||
{contentList[tabKey]}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function getVotesRequired(proposal: ParsedAccount<TimelockSet>): number {
|
||||
if (proposal.info.config.consensusAlgorithm === ConsensusAlgorithm.Majority) {
|
||||
return proposal.info.state.totalVotingTokensMinted.toNumber() * 0.5;
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Modal } from 'antd';
|
||||
import { Form, Input, Select } from 'antd';
|
||||
import { Account } from '@solana/web3.js';
|
||||
import {
|
||||
ConsensusAlgorithm,
|
||||
DESC_SIZE,
|
||||
ExecutionType,
|
||||
NAME_SIZE,
|
||||
TimelockType,
|
||||
} from '../../models/timelock';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { LABELS } from '../../constants';
|
||||
import { contexts } from '@oyster/common';
|
||||
import { createProposal } from '../../actions/createProposal';
|
||||
import { Redirect } from 'react-router';
|
||||
|
||||
const { useWallet } = contexts.Wallet;
|
||||
const { useConnection } = contexts.Connection;
|
||||
const { Option } = Select;
|
||||
|
||||
const layout = {
|
||||
labelCol: { span: 8 },
|
||||
wrapperCol: { span: 16 },
|
||||
};
|
||||
|
||||
export function NewFormMenuItem() {
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [redirect, setRedirect] = useState('');
|
||||
const handleOk = (a: Account) => {
|
||||
setIsModalVisible(false);
|
||||
setRedirect(a.publicKey.toBase58());
|
||||
};
|
||||
const handleCancel = () => {
|
||||
setIsModalVisible(false);
|
||||
};
|
||||
|
||||
if (redirect) {
|
||||
setRedirect('');
|
||||
return <Redirect push to={'/proposal/' + redirect} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Link
|
||||
to={{
|
||||
pathname: '/',
|
||||
}}
|
||||
onClick={() => setIsModalVisible(true)}
|
||||
>
|
||||
{LABELS.NEW_PROPOSAL}
|
||||
</Link>
|
||||
<NewForm
|
||||
handleOk={handleOk}
|
||||
handleCancel={handleCancel}
|
||||
isModalVisible={isModalVisible}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function NewForm({
|
||||
handleOk,
|
||||
handleCancel,
|
||||
isModalVisible,
|
||||
}: {
|
||||
handleOk: (a: Account) => void;
|
||||
handleCancel: () => void;
|
||||
isModalVisible: boolean;
|
||||
}) {
|
||||
const [form] = Form.useForm();
|
||||
const wallet = useWallet();
|
||||
const connection = useConnection();
|
||||
const onFinish = async (values: {
|
||||
name: string;
|
||||
description: string;
|
||||
timelockType: TimelockType;
|
||||
executionType: ExecutionType;
|
||||
consensusAlgorithm: ConsensusAlgorithm;
|
||||
}) => {
|
||||
const newSet = await createProposal(
|
||||
connection,
|
||||
wallet.wallet,
|
||||
values.name,
|
||||
values.description,
|
||||
values.timelockType,
|
||||
values.consensusAlgorithm,
|
||||
values.executionType,
|
||||
);
|
||||
handleOk(newSet);
|
||||
};
|
||||
return (
|
||||
<Modal
|
||||
title="New Proposal"
|
||||
visible={isModalVisible}
|
||||
onOk={form.submit}
|
||||
onCancel={handleCancel}
|
||||
>
|
||||
<Form {...layout} form={form} name="control-hooks" onFinish={onFinish}>
|
||||
<Form.Item name="name" label="Name" rules={[{ required: true }]}>
|
||||
<Input maxLength={NAME_SIZE} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="description"
|
||||
label="Description"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Input maxLength={DESC_SIZE} placeholder="Github Gist link" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="consensusAlgorithm"
|
||||
label="Consensus Algorithm"
|
||||
rules={[{ required: true }]}
|
||||
initialValue={ConsensusAlgorithm.Majority}
|
||||
>
|
||||
<Select placeholder="Select the Consensus Algorithm">
|
||||
<Option value={ConsensusAlgorithm.Majority}>Majority</Option>
|
||||
<Option value={ConsensusAlgorithm.FullConsensus}>
|
||||
Full Consensus
|
||||
</Option>
|
||||
<Option value={ConsensusAlgorithm.SuperMajority}>
|
||||
Super Majority
|
||||
</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="executionType"
|
||||
label="Execution Type"
|
||||
rules={[{ required: true }]}
|
||||
initialValue={ExecutionType.AnyAboveVoteFinishSlot}
|
||||
>
|
||||
<Select placeholder="Select the type of execution">
|
||||
<Option value={ExecutionType.AnyAboveVoteFinishSlot}>
|
||||
Any Above Vote Finish Slot
|
||||
</Option>
|
||||
<Option value={ExecutionType.AllOrNothing}>All or Nothing</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="timelockType"
|
||||
label="Proposal Type"
|
||||
rules={[{ required: true }]}
|
||||
initialValue={TimelockType.CustomSingleSignerV1}
|
||||
>
|
||||
<Select placeholder="Select the type of Proposal">
|
||||
<Option value={TimelockType.CustomSingleSignerV1}>
|
||||
Single Signer
|
||||
</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
.wordwrap {
|
||||
white-space: pre-wrap; /* CSS3 */
|
||||
white-space: -moz-pre-wrap; /* Firefox */
|
||||
white-space: -pre-wrap; /* Opera <7 */
|
||||
white-space: -o-pre-wrap; /* Opera 7 */
|
||||
word-wrap: break-word; /* IE */
|
||||
}
|
Loading…
Reference in New Issue