Added complete working display of instructions and adding instructions

This commit is contained in:
Dummy Tester 123 2021-02-25 16:03:12 -06:00
parent d4bf02c790
commit 1883bfd8ee
9 changed files with 170 additions and 67 deletions

View File

@ -22,6 +22,9 @@ export const addCustomSingleSignerTransaction = async (
wallet: any,
proposal: ParsedAccount<TimelockSet>,
sigAccount: PublicKey,
slot: string,
instruction: string,
position: number,
) => {
const PROGRAM_IDS = utils.programIds();
@ -67,9 +70,9 @@ export const addCustomSingleSignerTransaction = async (
proposal.info.signatoryValidation,
transferAuthority.publicKey,
authority,
'123',
'12345',
0,
slot,
instruction,
position,
),
);

View File

@ -0,0 +1,47 @@
import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
import { ParsedAccount } from '@oyster/common';
import { Card } from 'antd';
import Meta from 'antd/lib/card/Meta';
import React, { useState } from 'react';
import { TimelockTransaction } from '../../models/timelock';
import './style.less';
export function InstructionCard({
instruction,
position,
}: {
instruction: ParsedAccount<TimelockTransaction>;
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.toNumber()}</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>
);
}

View File

@ -0,0 +1,77 @@
import React from 'react';
import { Card, Spin } from 'antd';
import { Form, Input } from 'antd';
import { INSTRUCTION_LIMIT, TimelockSet } from '../../models/timelock';
import { contexts, ParsedAccount, hooks, utils } from '@oyster/common';
import { addCustomSingleSignerTransaction } from '../../actions/addCustomSingleSignerTransaction';
import { SaveOutlined } from '@ant-design/icons';
const { useWallet } = contexts.Wallet;
const { useConnection } = contexts.Connection;
const { useAccountByMint } = hooks;
const { notify } = utils;
const layout = {
labelCol: { span: 8 },
wrapperCol: { span: 16 },
};
export function NewInstructionCard({
proposal,
position,
}: {
proposal: ParsedAccount<TimelockSet>;
position: number;
}) {
const [form] = Form.useForm();
const wallet = useWallet();
const connection = useConnection();
const sigAccount = useAccountByMint(proposal.info.signatoryMint);
const onFinish = async (values: { slot: string; instruction: string }) => {
if (!values.slot.match(/^\d*$/)) {
notify({
message: 'Slot can only be numeric',
type: 'error',
});
return;
}
if (sigAccount) {
await addCustomSingleSignerTransaction(
connection,
wallet.wallet,
proposal,
sigAccount.pubkey,
values.slot,
values.instruction,
position,
);
form.resetFields();
}
};
return (
<Card
title="New Instruction"
actions={[<SaveOutlined key="save" onClick={form.submit} />]}
>
{!sigAccount ? (
<Spin />
) : (
<Form {...layout} form={form} name="control-hooks" onFinish={onFinish}>
<Form.Item name="slot" label="Slot" rules={[{ required: true }]}>
<Input maxLength={64} />
</Form.Item>
<Form.Item
name="instruction"
label="Instruction"
rules={[{ required: true }]}
>
<Input
maxLength={INSTRUCTION_LIMIT}
placeholder={'Base58 encoded instruction'}
/>
</Form.Item>
</Form>
)}
</Card>
);
}

View File

@ -15,7 +15,7 @@ import {
TimelockSet,
TimelockSetLayout,
TimelockSetParser,
Transaction,
TimelockTransaction,
} from '../models/timelock';
const { useConnectionConfig } = contexts.Connection;
@ -23,7 +23,7 @@ const { cache } = contexts.Accounts;
export interface ProposalsContextState {
proposals: Record<string, ParsedAccount<TimelockSet>>;
transactions: Record<string, ParsedAccount<Transaction>>;
transactions: Record<string, ParsedAccount<TimelockTransaction>>;
}
export const ProposalsContext = React.createContext<ProposalsContextState | null>(
@ -72,7 +72,10 @@ function useSetupProposalsCache({
};
Promise.all([query()]).then((all: PublicKeyAndAccount<Buffer>[][]) => {
const newProposals: Record<string, ParsedAccount<TimelockSet>> = {};
const newTransactions: Record<string, ParsedAccount<Transaction>> = {};
const newTransactions: Record<
string,
ParsedAccount<TimelockTransaction>
> = {};
all[0].forEach(a => {
if (a.account.data.length === TimelockSetLayout.span) {
@ -89,11 +92,14 @@ function useSetupProposalsCache({
a.account,
CustomSingleSignerTimelockTransactionParser,
);
const cached = cache.get(a.pubkey) as ParsedAccount<Transaction>;
const cached = cache.get(
a.pubkey,
) as ParsedAccount<TimelockTransaction>;
newTransactions[a.pubkey.toBase58()] = cached;
}
});
setProposals(newProposals);
setTransactions(newTransactions);
});
const subID = connection.onProgramAccountChange(
PROGRAM_IDS.timelock.programId,
@ -112,7 +118,9 @@ function useSetupProposalsCache({
const cached =
span === TimelockSetLayout.span
? (cache.get(info.accountId) as ParsedAccount<TimelockSet>)
: (cache.get(info.accountId) as ParsedAccount<Transaction>);
: (cache.get(
info.accountId,
) as ParsedAccount<TimelockTransaction>);
setter((obj: any) => ({
...obj,
[typeof info.accountId === 'string'

View File

@ -60,7 +60,6 @@ export const addCustomSingleSignerTransactionInstruction = (
for (let i = instructionAsBytes.length; i <= INSTRUCTION_LIMIT - 1; i++) {
instructionAsBytes.push(0);
}
console.log('Num', new BN(slot).toNumber());
dataLayout.encode(
{

View File

@ -203,13 +203,14 @@ export const CustomSingleSignerTimelockTransactionLayout: typeof BufferLayout.St
],
);
export interface Transaction {
export interface TimelockTransaction {
version: number;
slot: BN;
instruction: string;
}
export interface CustomSingleSignerTimelockTransaction extends Transaction {
export interface CustomSingleSignerTimelockTransaction
extends TimelockTransaction {
authorityKey: PublicKey;
}

View File

@ -75,6 +75,9 @@ function InnerDummyView({
wallet,
proposal,
sigAccount.pubkey,
'123',
'12345',
0,
)
}
>

View File

@ -1,11 +1,12 @@
import { Card, Col, Divider, Row, Space, Spin, Statistic } from 'antd';
import { 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,
INSTRUCTION_LIMIT,
TimelockSet,
Transaction,
TimelockTransaction,
} from '../../models/timelock';
import { useParams } from 'react-router-dom';
import ReactMarkdown from 'react-markdown';
@ -13,9 +14,8 @@ 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';
import { InstructionCard } from '../../components/Proposal/InstructionCard';
import { NewInstructionCard } from '../../components/Proposal/NewInstructionCard';
export const urlRegex = /[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;
const { useMint } = contexts.Accounts;
@ -50,8 +50,11 @@ function InnerProposalView({
proposal: ParsedAccount<TimelockSet>;
sigMint: MintInfo;
votingMint: MintInfo;
instructions: Record<string, ParsedAccount<Transaction>>;
instructions: Record<string, ParsedAccount<TimelockTransaction>>;
}) {
const instructionsForProposal: ParsedAccount<TimelockTransaction>[] = proposal.info.state.timelockTransactions
.map(k => instructions[k.toBase58()])
.filter(k => k);
const isUrl = !!proposal.info.state.descLink.match(urlRegex);
const isGist =
!!proposal.info.state.descLink.match(/gist/i) &&
@ -170,66 +173,28 @@ function InnerProposalView({
{ 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}>
{instructionsForProposal.map((instruction, position) => (
<Col xs={24} sm={24} md={12} lg={8} key={position}>
<InstructionCard
position={position}
instruction={{
info: {
slot: 5,
instruction:
'Dhr8DF4Wsr9WrwSJixqfe7YFwdgz3GtjTRGF4wRMw3rP91NUmzR8PGJvCw7CpFDnUMij5kSpWpocaAftpCxBJ1oneK89CeWXGEVhyYTxAnhudvUSCXtvvjNhiFC8NQhCHDskA6MdsGJP2Dd1w3kihZeB5N9sXT7NjwZCCZeH5qL5tFhgUja9Nv1ywGvU1WnXDwxsRvPGf5AQNZ5xwwrr7chFV7DoTnwc9D7dJwx3m9mMBdSUcbtP77pUxvZbN1Sw9CTpM2rUUvwSxy2cYJPcgnNKZyq3CPYCa9H2Z9KR2AigcMgRzYswYhBGiz8piMVYL1rQRmRJVnoWALh3K1rS3BPRmvq52G7EJzXdoKiQQU6zzdzFuP6TGvdJrwfppzZrFRQ3n6bb3RaBoqoopnrLwWQA673KEeJCngwnipnTo5K6i72jJBBniqmanmtuFhVne2Gr2QUxDynqwBRvbpBT4uAibmu6PRUL9BhVDJibrb7rJf9RY2TL',
},
}}
position={position + 1}
instruction={instruction}
/>
</Col>
))}
{instructionsForProposal.length < INSTRUCTION_LIMIT && (
<Col xs={24} sm={24} md={12} lg={8}>
<NewInstructionCard
proposal={proposal}
position={instructionsForProposal.length}
/>
</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;