This commit marks a working remove signer - if only you could burn other people's tokens. Going to remove this functionality now.

This commit is contained in:
Dummy Tester 123 2021-02-28 13:15:31 -06:00
parent 941170be1d
commit 43abf3ae50
7 changed files with 376 additions and 79 deletions

View File

@ -1,4 +1,10 @@
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import React, {
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
import { useConnection } from '../contexts/connection'; import { useConnection } from '../contexts/connection';
import { useWallet } from '../contexts/wallet'; import { useWallet } from '../contexts/wallet';
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js'; import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
@ -7,7 +13,11 @@ import { TokenAccount } from '../models';
import { chunks } from '../utils/utils'; import { chunks } from '../utils/utils';
import { EventEmitter } from '../utils/eventEmitter'; import { EventEmitter } from '../utils/eventEmitter';
import { useUserAccounts } from '../hooks/useUserAccounts'; import { useUserAccounts } from '../hooks/useUserAccounts';
import { WRAPPED_SOL_MINT, programIds, LEND_HOST_FEE_ADDRESS } from '../utils/ids'; import {
WRAPPED_SOL_MINT,
programIds,
LEND_HOST_FEE_ADDRESS,
} from '../utils/ids';
const AccountsContext = React.createContext<any>(null); const AccountsContext = React.createContext<any>(null);
@ -22,7 +32,10 @@ export interface ParsedAccountBase {
info: any; // TODO: change to unkown info: any; // TODO: change to unkown
} }
export type AccountParser = (pubkey: PublicKey, data: AccountInfo<Buffer>) => ParsedAccountBase | undefined; export type AccountParser = (
pubkey: PublicKey,
data: AccountInfo<Buffer>,
) => ParsedAccountBase | undefined;
export interface ParsedAccount<T> extends ParsedAccountBase { export interface ParsedAccount<T> extends ParsedAccountBase {
info: T; info: T;
@ -55,7 +68,10 @@ export const MintParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => {
return details; return details;
}; };
export const TokenAccountParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => { export const TokenAccountParser = (
pubKey: PublicKey,
info: AccountInfo<Buffer>,
) => {
const buffer = Buffer.from(info.data); const buffer = Buffer.from(info.data);
const data = deserializeAccount(buffer); const data = deserializeAccount(buffer);
@ -70,7 +86,10 @@ export const TokenAccountParser = (pubKey: PublicKey, info: AccountInfo<Buffer>)
return details; return details;
}; };
export const GenericAccountParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => { export const GenericAccountParser = (
pubKey: PublicKey,
info: AccountInfo<Buffer>,
) => {
const buffer = Buffer.from(info.data); const buffer = Buffer.from(info.data);
const details = { const details = {
@ -88,7 +107,11 @@ export const keyToAccountParser = new Map<string, AccountParser>();
export const cache = { export const cache = {
emitter: new EventEmitter(), emitter: new EventEmitter(),
query: async (connection: Connection, pubKey: string | PublicKey, parser?: AccountParser) => { query: async (
connection: Connection,
pubKey: string | PublicKey,
parser?: AccountParser,
) => {
let id: PublicKey; let id: PublicKey;
if (typeof pubKey === 'string') { if (typeof pubKey === 'string') {
id = new PublicKey(pubKey); id = new PublicKey(pubKey);
@ -109,7 +132,7 @@ export const cache = {
} }
// TODO: refactor to use multiple accounts query with flush like behavior // TODO: refactor to use multiple accounts query with flush like behavior
query = connection.getAccountInfo(id).then((data) => { query = connection.getAccountInfo(id).then(data => {
if (!data) { if (!data) {
throw new Error('Account not found'); throw new Error('Account not found');
} }
@ -120,7 +143,11 @@ export const cache = {
return query; return query;
}, },
add: (id: PublicKey | string, obj: AccountInfo<Buffer>, parser?: AccountParser) => { add: (
id: PublicKey | string,
obj: AccountInfo<Buffer>,
parser?: AccountParser,
) => {
if (obj.data.length === 0) { if (obj.data.length === 0) {
return; return;
} }
@ -128,7 +155,9 @@ export const cache = {
const address = typeof id === 'string' ? id : id?.toBase58(); const address = typeof id === 'string' ? id : id?.toBase58();
const deserialize = parser ? parser : keyToAccountParser.get(address); const deserialize = parser ? parser : keyToAccountParser.get(address);
if (!deserialize) { if (!deserialize) {
throw new Error('Deserializer needs to be registered or passed as a parameter'); throw new Error(
'Deserializer needs to be registered or passed as a parameter',
);
} }
cache.registerParser(id, deserialize); cache.registerParser(id, deserialize);
@ -187,7 +216,52 @@ export const cache = {
} }
return pubkey; return pubkey;
} },
queryMint: async (connection: Connection, pubKey: string | PublicKey) => {
let id: PublicKey;
if (typeof pubKey === 'string') {
id = new PublicKey(pubKey);
} else {
id = pubKey;
}
const address = id.toBase58();
let mint = mintCache.get(address);
if (mint) {
return mint;
}
let query = pendingMintCalls.get(address);
if (query) {
return query;
}
query = getMintInfo(connection, id).then(data => {
pendingMintCalls.delete(address);
mintCache.set(address, data);
return data;
}) as Promise<MintInfo>;
pendingMintCalls.set(address, query as any);
return query;
},
getMint: (pubKey: string | PublicKey) => {
let key: string;
if (typeof pubKey !== 'string') {
key = pubKey.toBase58();
} else {
key = pubKey;
}
return mintCache.get(key);
},
addMint: (pubKey: PublicKey, obj: AccountInfo<Buffer>) => {
const mint = deserializeMint(obj.data);
const id = pubKey.toBase58();
mintCache.set(id, mint);
return mint;
},
}; };
export const useAccountsContext = () => { export const useAccountsContext = () => {
@ -196,7 +270,10 @@ export const useAccountsContext = () => {
return context; return context;
}; };
function wrapNativeAccount(pubkey: PublicKey, account?: AccountInfo<Buffer>): TokenAccount | undefined { function wrapNativeAccount(
pubkey: PublicKey,
account?: AccountInfo<Buffer>,
): TokenAccount | undefined {
if (!account) { if (!account) {
return undefined; return undefined;
} }
@ -219,7 +296,9 @@ function wrapNativeAccount(pubkey: PublicKey, account?: AccountInfo<Buffer>): To
}; };
} }
export const getCachedAccount = (predicate: (account: TokenAccount) => boolean) => { export const getCachedAccount = (
predicate: (account: TokenAccount) => boolean,
) => {
for (const account of genericCache.values()) { for (const account of genericCache.values()) {
if (predicate(account)) { if (predicate(account)) {
return account as TokenAccount; return account as TokenAccount;
@ -234,8 +313,8 @@ const UseNativeAccount = () => {
const [nativeAccount, setNativeAccount] = useState<AccountInfo<Buffer>>(); const [nativeAccount, setNativeAccount] = useState<AccountInfo<Buffer>>();
const updateCache = useCallback( const updateCache = useCallback(
(account) => { account => {
if(wallet && wallet.publicKey) { if (wallet && wallet.publicKey) {
const wrapped = wrapNativeAccount(wallet.publicKey, account); const wrapped = wrapNativeAccount(wallet.publicKey, account);
if (wrapped !== undefined && wallet) { if (wrapped !== undefined && wallet) {
const id = wallet.publicKey?.toBase58(); const id = wallet.publicKey?.toBase58();
@ -245,7 +324,7 @@ const UseNativeAccount = () => {
} }
} }
}, },
[wallet] [wallet],
); );
useEffect(() => { useEffect(() => {
@ -253,13 +332,13 @@ const UseNativeAccount = () => {
return; return;
} }
connection.getAccountInfo(wallet.publicKey).then((acc) => { connection.getAccountInfo(wallet.publicKey).then(acc => {
if (acc) { if (acc) {
updateCache(acc); updateCache(acc);
setNativeAccount(acc); setNativeAccount(acc);
} }
}); });
connection.onAccountChange(wallet.publicKey, (acc) => { connection.onAccountChange(wallet.publicKey, acc => {
if (acc) { if (acc) {
updateCache(acc); updateCache(acc);
setNativeAccount(acc); setNativeAccount(acc);
@ -271,7 +350,10 @@ const UseNativeAccount = () => {
}; };
const PRECACHED_OWNERS = new Set<string>(); const PRECACHED_OWNERS = new Set<string>();
const precacheUserTokenAccounts = async (connection: Connection, owner?: PublicKey) => { const precacheUserTokenAccounts = async (
connection: Connection,
owner?: PublicKey,
) => {
if (!owner) { if (!owner) {
return; return;
} }
@ -283,7 +365,7 @@ const precacheUserTokenAccounts = async (connection: Connection, owner?: PublicK
const accounts = await connection.getTokenAccountsByOwner(owner, { const accounts = await connection.getTokenAccountsByOwner(owner, {
programId: programIds().token, programId: programIds().token,
}); });
accounts.value.forEach((info) => { accounts.value.forEach(info => {
cache.add(info.pubkey.toBase58(), info.account, TokenAccountParser); cache.add(info.pubkey.toBase58(), info.account, TokenAccountParser);
}); });
}; };
@ -298,30 +380,34 @@ export function AccountsProvider({ children = null as any }) {
const selectUserAccounts = useCallback(() => { const selectUserAccounts = useCallback(() => {
return cache return cache
.byParser(TokenAccountParser) .byParser(TokenAccountParser)
.map((id) => cache.get(id)) .map(id => cache.get(id))
.filter((a) => a && a.info.owner.toBase58() === wallet?.publicKey?.toBase58()) .filter(
.map((a) => a as TokenAccount); a => a && a.info.owner.toBase58() === wallet?.publicKey?.toBase58(),
)
.map(a => a as TokenAccount);
}, [wallet]); }, [wallet]);
useEffect(() => { useEffect(() => {
const accounts = selectUserAccounts().filter((a) => a !== undefined) as TokenAccount[]; const accounts = selectUserAccounts().filter(
a => a !== undefined,
) as TokenAccount[];
setUserAccounts(accounts); setUserAccounts(accounts);
}, [nativeAccount, wallet, tokenAccounts, selectUserAccounts]); }, [nativeAccount, wallet, tokenAccounts, selectUserAccounts]);
useEffect(() => { useEffect(() => {
const subs: number[] = []; const subs: number[] = [];
cache.emitter.onCache((args) => { cache.emitter.onCache(args => {
if (args.isNew) { if (args.isNew) {
let id = args.id; let id = args.id;
let deserialize = args.parser; let deserialize = args.parser;
connection.onAccountChange(new PublicKey(id), (info) => { connection.onAccountChange(new PublicKey(id), info => {
cache.add(id, info, deserialize); cache.add(id, info, deserialize);
}); });
} }
}); });
return () => { return () => {
subs.forEach((id) => connection.removeAccountChangeListener(id)); subs.forEach(id => connection.removeAccountChangeListener(id));
}; };
}, [connection]); }, [connection]);
@ -341,7 +427,7 @@ export function AccountsProvider({ children = null as any }) {
// this should use only filter syntax to only get accounts that are owned by user // this should use only filter syntax to only get accounts that are owned by user
const tokenSubID = connection.onProgramAccountChange( const tokenSubID = connection.onProgramAccountChange(
programIds().token, programIds().token,
(info) => { info => {
// TODO: fix type in web3.js // TODO: fix type in web3.js
const id = (info.accountId as unknown) as string; const id = (info.accountId as unknown) as string;
// TODO: do we need a better way to identify layout (maybe a enum identifing type?) // TODO: do we need a better way to identify layout (maybe a enum identifing type?)
@ -354,7 +440,7 @@ export function AccountsProvider({ children = null as any }) {
} }
} }
}, },
'singleGossip' 'singleGossip',
); );
return () => { return () => {
@ -382,38 +468,49 @@ export function useNativeAccount() {
}; };
} }
export const getMultipleAccounts = async (connection: any, keys: string[], commitment: string) => { export const getMultipleAccounts = async (
connection: any,
keys: string[],
commitment: string,
) => {
const result = await Promise.all( const result = await Promise.all(
chunks(keys, 99).map((chunk) => getMultipleAccountsCore(connection, chunk, commitment)) chunks(keys, 99).map(chunk =>
getMultipleAccountsCore(connection, chunk, commitment),
),
); );
const array = result const array = result
.map( .map(
(a) => a =>
a.array a.array.map(acc => {
.map((acc) => { if (!acc) {
if (!acc) { return undefined;
return undefined; }
}
const { data, ...rest } = acc; const { data, ...rest } = acc;
const obj = { const obj = {
...rest, ...rest,
data: Buffer.from(data[0], 'base64'), data: Buffer.from(data[0], 'base64'),
} as AccountInfo<Buffer>; } as AccountInfo<Buffer>;
return obj; return obj;
}) as AccountInfo<Buffer>[] }) as AccountInfo<Buffer>[],
) )
.flat(); .flat();
return { keys, array }; return { keys, array };
}; };
const getMultipleAccountsCore = async (connection: any, keys: string[], commitment: string) => { const getMultipleAccountsCore = async (
connection: any,
keys: string[],
commitment: string,
) => {
const args = connection._buildArgs([keys], commitment, 'base64'); const args = connection._buildArgs([keys], commitment, 'base64');
const unsafeRes = await connection._rpcRequest('getMultipleAccounts', args); const unsafeRes = await connection._rpcRequest('getMultipleAccounts', args);
if (unsafeRes.error) { if (unsafeRes.error) {
throw new Error('failed to get info about account ' + unsafeRes.error.message); throw new Error(
'failed to get info about account ' + unsafeRes.error.message,
);
} }
if (unsafeRes.result.value) { if (unsafeRes.result.value) {
@ -438,13 +535,15 @@ export function useMint(key?: string | PublicKey) {
cache cache
.query(connection, id, MintParser) .query(connection, id, MintParser)
.then((acc) => setMint(acc.info as any)) .then(acc => setMint(acc.info as any))
.catch((err) => console.log(err)); .catch(err => console.log(err));
const dispose = cache.emitter.onCache((e) => { const dispose = cache.emitter.onCache(e => {
const event = e; const event = e;
if (event.id === id) { if (event.id === id) {
cache.query(connection, id, MintParser).then((mint) => setMint(mint.info as any)); cache
.query(connection, id, MintParser)
.then(mint => setMint(mint.info as any));
} }
}); });
return () => { return () => {
@ -467,7 +566,9 @@ export function useAccount(pubKey?: PublicKey) {
return; return;
} }
const acc = await cache.query(connection, key, TokenAccountParser).catch((err) => console.log(err)); const acc = await cache
.query(connection, key, TokenAccountParser)
.catch(err => console.log(err));
if (acc) { if (acc) {
setAccount(acc); setAccount(acc);
} }
@ -478,7 +579,7 @@ export function useAccount(pubKey?: PublicKey) {
query(); query();
const dispose = cache.emitter.onCache((e) => { const dispose = cache.emitter.onCache(e => {
const event = e; const event = e;
if (event.id === key) { if (event.id === key) {
query(); query();
@ -493,7 +594,7 @@ export function useAccount(pubKey?: PublicKey) {
} }
// TODO: expose in spl package // TODO: expose in spl package
const deserializeAccount = (data: Buffer) => { export const deserializeAccount = (data: Buffer) => {
const accountInfo = AccountLayout.decode(data); const accountInfo = AccountLayout.decode(data);
accountInfo.mint = new PublicKey(accountInfo.mint); accountInfo.mint = new PublicKey(accountInfo.mint);
accountInfo.owner = new PublicKey(accountInfo.owner); accountInfo.owner = new PublicKey(accountInfo.owner);

View File

@ -0,0 +1,83 @@
import {
Account,
Connection,
PublicKey,
TransactionInstruction,
} from '@solana/web3.js';
import {
contexts,
utils,
models,
ParsedAccount,
actions,
} from '@oyster/common';
import { TimelockSet } from '../models/timelock';
import { removeSignerInstruction } from '../models/removeSigner';
const { sendTransaction } = contexts.Connection;
const { notify } = utils;
const { approve } = models;
export const removeSigner = async (
connection: Connection,
wallet: any,
proposal: ParsedAccount<TimelockSet>,
adminAccount: PublicKey,
sigAccount: PublicKey,
) => {
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 transferAuthority = approve(
instructions,
[],
adminAccount,
wallet.publicKey,
1,
);
signers.push(transferAuthority);
instructions.push(
removeSignerInstruction(
sigAccount,
proposal.info.signatoryMint,
adminAccount,
proposal.info.adminValidation,
proposal.pubkey,
transferAuthority.publicKey,
mintAuthority,
),
);
notify({
message: 'Removing signer...',
description: 'Please wait...',
type: 'warn',
});
try {
let tx = await sendTransaction(
connection,
wallet,
instructions,
signers,
true,
);
notify({
message: 'Signer removed.',
type: 'success',
description: `Transaction - ${tx}`,
});
} catch (ex) {
console.error(ex);
throw new Error();
}
};

View File

@ -1,4 +1,4 @@
import { ParsedAccount } from '@oyster/common'; import { ParsedAccount, TokenAccount } from '@oyster/common';
import { import {
Button, Button,
Modal, Modal,
@ -9,6 +9,8 @@ import {
Col, Col,
Row, Row,
Space, Space,
Switch,
Radio,
} from 'antd'; } from 'antd';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { TimelockSet } from '../../models/timelock'; import { TimelockSet } from '../../models/timelock';
@ -16,19 +18,22 @@ import { utils, contexts, hooks } from '@oyster/common';
import { addSigner } from '../../actions/addSigner'; import { addSigner } from '../../actions/addSigner';
import { PublicKey } from '@solana/web3.js'; import { PublicKey } from '@solana/web3.js';
import { LABELS } from '../../constants'; import { LABELS } from '../../constants';
import { removeSigner } from '../../actions/removeSigner';
import { AccountLayout } from '@solana/spl-token';
const { notify } = utils; const { notify } = utils;
const { TextArea } = Input; const { TextArea } = Input;
const { useWallet } = contexts.Wallet; const { useWallet } = contexts.Wallet;
const { useConnection } = contexts.Connection; const { useConnection } = contexts.Connection;
const { useAccountByMint } = hooks; const { useAccountByMint } = hooks;
const { deserializeAccount } = contexts.Accounts;
const layout = { const layout = {
labelCol: { span: 5 }, labelCol: { span: 5 },
wrapperCol: { span: 19 }, wrapperCol: { span: 19 },
}; };
export default function AddSigners({ export default function EditSigners({
proposal, proposal,
}: { }: {
proposal: ParsedAccount<TimelockSet>; proposal: ParsedAccount<TimelockSet>;
@ -38,6 +43,7 @@ export default function AddSigners({
const adminAccount = useAccountByMint(proposal.info.adminMint); const adminAccount = useAccountByMint(proposal.info.adminMint);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [isModalVisible, setIsModalVisible] = useState(false); const [isModalVisible, setIsModalVisible] = useState(false);
const PROGRAM_IDS = utils.programIds();
const [savePerc, setSavePerc] = useState(0); const [savePerc, setSavePerc] = useState(0);
const [failedSigners, setFailedSigners] = useState<string[]>([]); const [failedSigners, setFailedSigners] = useState<string[]>([]);
@ -46,19 +52,20 @@ export default function AddSigners({
const onSubmit = async (values: { const onSubmit = async (values: {
signers: string; signers: string;
failedSigners: string; failedSigners: string;
type: string;
}) => { }) => {
const signers = values.signers.split(',').map(s => s.trim()); const signers = values.signers.split(',').map(s => s.trim());
setSaving(true); setSaving(true);
if (!adminAccount) { if (!adminAccount) {
notify({ notify({
message: 'Admin account is not defined', message: LABELS.ADMIN_ACCOUNT_NOT_DEFINED,
type: 'error', type: 'error',
}); });
return; return;
} }
if (signers.length == 0 || (signers.length == 1 && !signers[0])) { if (signers.length == 0 || (signers.length == 1 && !signers[0])) {
notify({ notify({
message: 'Please enter at least one pub key.', message: LABELS.ENTER_AT_LEAST_ONE_PUB_KEY,
type: 'error', type: 'error',
}); });
return; return;
@ -68,19 +75,42 @@ export default function AddSigners({
for (let i = 0; i < signers.length; i++) { for (let i = 0; i < signers.length; i++) {
try { try {
await addSigner( if (values.type == LABELS.ADD)
connection, await addSigner(
wallet.wallet, connection,
proposal, wallet.wallet,
adminAccount.pubkey, proposal,
new PublicKey(signers[i]), adminAccount.pubkey,
); new PublicKey(signers[i]),
);
else {
const tokenAccounts = await connection.getTokenAccountsByOwner(
new PublicKey(signers[i]),
{
programId: PROGRAM_IDS.token,
},
);
const specificToThisMint = tokenAccounts.value.filter(
a =>
deserializeAccount(a.account.data).mint.toBase58() ===
proposal.info.signatoryMint.toBase58(),
);
for (let j = 0; j < specificToThisMint.length; j++) {
await removeSigner(
connection,
wallet.wallet,
proposal,
adminAccount.pubkey,
specificToThisMint[j].pubkey,
);
}
}
setSavePerc(Math.round(100 * ((i + 1) / signers.length))); setSavePerc(Math.round(100 * ((i + 1) / signers.length)));
} catch (e) { } catch (e) {
console.error(e); console.error(e);
failedSignersHold.push(signers[i]); failedSignersHold.push(signers[i]);
notify({ notify({
message: `Pub key ${signers[i]} 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.`, message: signers[i] + LABELS.PUB_KEY_FAILED,
type: 'error', type: 'error',
}); });
} }
@ -99,11 +129,11 @@ export default function AddSigners({
setIsModalVisible(true); setIsModalVisible(true);
}} }}
> >
{LABELS.ADD_SIGNERS} {LABELS.EDIT_SIGNERS}
</Button> </Button>
) : null} ) : null}
<Modal <Modal
title={LABELS.ADD_SIGNERS} title={LABELS.EDIT_SIGNERS}
visible={isModalVisible} visible={isModalVisible}
destroyOnClose={true} destroyOnClose={true}
onOk={form.submit} onOk={form.submit}
@ -120,16 +150,31 @@ export default function AddSigners({
name="control-hooks" name="control-hooks"
> >
{!saving && ( {!saving && (
<Form.Item <>
name="signers" <Form.Item
label={LABELS.SIGNERS} name="type"
rules={[{ required: true }]} label={LABELS.ADDING_OR_REMOVING}
> initialValue={LABELS.ADD}
<TextArea rules={[{ required: true }]}
id="signers" >
placeholder={LABELS.COMMA_SEPARATED_KEYS} <Radio.Group value={layout}>
/> <Radio.Button value={LABELS.REMOVE}>
</Form.Item> {LABELS.REMOVE}
</Radio.Button>
<Radio.Button value={LABELS.ADD}>{LABELS.ADD}</Radio.Button>
</Radio.Group>
</Form.Item>
<Form.Item
name="signers"
label={LABELS.SIGNERS}
rules={[{ required: true }]}
>
<TextArea
id="signers"
placeholder={LABELS.COMMA_SEPARATED_KEYS}
/>
</Form.Item>
</>
)} )}
</Form> </Form>
{saving && <Progress percent={savePerc} status="active" />} {saving && <Progress percent={savePerc} status="active" />}

View File

@ -26,5 +26,12 @@ export const LABELS = {
FAILED_SIGNERS_COPIED_TO_CLIPBOARD: 'Failed signers copied to clipboard!', FAILED_SIGNERS_COPIED_TO_CLIPBOARD: 'Failed signers copied to clipboard!',
COMMA_SEPARATED_KEYS: 'Comma separated base58 pubkeys', COMMA_SEPARATED_KEYS: 'Comma separated base58 pubkeys',
SIGNERS: 'Signers', SIGNERS: 'Signers',
ADD_SIGNERS: 'Add Signers', EDIT_SIGNERS: 'Edit Signers',
ADMIN_ACCOUNT_NOT_DEFINED: 'Admin 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',
}; };

View File

@ -0,0 +1,60 @@
import { PublicKey, TransactionInstruction } from '@solana/web3.js';
import { utils } from '@oyster/common';
import * as BufferLayout from 'buffer-layout';
import { TimelockInstruction } from './timelock';
/// [Requires Admin token]
/// Removes a signer from the set.
///
/// 0. `[writable]` Signatory account to remove token from.
/// 1. `[writable]` Signatory mint account.
/// 2. `[writable]` Admin account.
/// 3. `[writable]` Admin validation account.
/// 4. `[]` Timelock set account.
/// 5. `[]` Transfer authority
/// 5. `[]` Timelock program mint authority
/// 6. `[]` Timelock program account.
/// 7. '[]` Token program id.
export const removeSignerInstruction = (
signatoryAccount: PublicKey,
signatoryMintAccount: PublicKey,
adminAccount: PublicKey,
adminValidationAccount: PublicKey,
timelockSetAccount: PublicKey,
transferAuthority: PublicKey,
mintAuthority: PublicKey,
): TransactionInstruction => {
const PROGRAM_IDS = utils.programIds();
const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]);
const data = Buffer.alloc(dataLayout.span);
dataLayout.encode(
{
instruction: TimelockInstruction.RemoveSigner,
},
data,
);
const keys = [
{ pubkey: signatoryAccount, isSigner: false, isWritable: true },
{ pubkey: signatoryMintAccount, isSigner: false, isWritable: true },
{ pubkey: adminAccount, isSigner: false, isWritable: true },
{ pubkey: adminValidationAccount, isSigner: false, isWritable: true },
{ pubkey: timelockSetAccount, 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,
});
};

View File

@ -12,6 +12,7 @@ export const TRANSACTION_SLOTS = 10;
export enum TimelockInstruction { export enum TimelockInstruction {
InitTimelockSet = 1, InitTimelockSet = 1,
AddSigner = 2, AddSigner = 2,
RemoveSigner = 3,
addCustomSingleSignerTransaction = 4, addCustomSingleSignerTransaction = 4,
Sign = 8, Sign = 8,
} }

View File

@ -17,7 +17,7 @@ import { MintInfo } from '@solana/spl-token';
import { InstructionCard } from '../../components/Proposal/InstructionCard'; import { InstructionCard } from '../../components/Proposal/InstructionCard';
import { NewInstructionCard } from '../../components/Proposal/NewInstructionCard'; import { NewInstructionCard } from '../../components/Proposal/NewInstructionCard';
import SignButton from '../../components/Proposal/SignButton'; import SignButton from '../../components/Proposal/SignButton';
import AddSigners from '../../components/Proposal/AddSigners'; import EditSigners from '../../components/Proposal/EditSigners';
export const urlRegex = /[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/; 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 { useMint } = contexts.Accounts;
const { useAccountByMint } = hooks; const { useAccountByMint } = hooks;
@ -163,7 +163,7 @@ function InnerProposalView({
} }
> >
{adminAccount && adminAccount.info.amount.toNumber() === 1 && ( {adminAccount && adminAccount.info.amount.toNumber() === 1 && (
<AddSigners proposal={proposal} /> <EditSigners proposal={proposal} />
)} )}
{sigAccount && sigAccount.info.amount.toNumber() === 1 && ( {sigAccount && sigAccount.info.amount.toNumber() === 1 && (
<SignButton proposal={proposal} /> <SignButton proposal={proposal} />