[xc-admin] Add message buffer instructions (#869)

* [xc-admin] Add message buffer instructions

* Use coral-xyz/anchor

* Address feedbacks

* Address feedbacks
This commit is contained in:
Ali Behjati 2023-06-09 19:56:40 +02:00 committed by GitHub
parent 183081cc20
commit 95ca9d1d92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 472 additions and 13 deletions

View File

@ -7,6 +7,7 @@ WORKDIR /home/node/
USER 1000 USER 1000
COPY --chown=1000:1000 governance/xc_admin governance/xc_admin COPY --chown=1000:1000 governance/xc_admin governance/xc_admin
COPY --chown=1000:1000 pythnet/message_buffer pythnet/message_buffer
RUN npx lerna run build --scope="{crank_executor,crank_pythnet_relayer,proposer_server}" --include-dependencies RUN npx lerna run build --scope="{crank_executor,crank_pythnet_relayer,proposer_server}" --include-dependencies

View File

@ -21,6 +21,7 @@
}, },
"dependencies": { "dependencies": {
"@certusone/wormhole-sdk": "^0.9.8", "@certusone/wormhole-sdk": "^0.9.8",
"@coral-xyz/anchor": "^0.26.0",
"@pythnetwork/client": "^2.17.0", "@pythnetwork/client": "^2.17.0",
"@solana/buffer-layout": "^4.0.1", "@solana/buffer-layout": "^4.0.1",
"@solana/web3.js": "^1.73.0", "@solana/web3.js": "^1.73.0",

View File

@ -0,0 +1,235 @@
import { AnchorProvider, Wallet, Program, Idl } from "@coral-xyz/anchor";
import {
getPythClusterApiUrl,
PythCluster,
} from "@pythnetwork/client/lib/cluster";
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
import {
MessageBufferMultisigInstruction,
MESSAGE_BUFFER_PROGRAM_ID,
MultisigInstructionProgram,
MultisigParser,
} from "..";
import messageBuffer from "message_buffer/idl/message_buffer.json";
import { MessageBuffer } from "message_buffer/idl/message_buffer";
test("Message buffer multisig instruction parse: create buffer", (done) => {
jest.setTimeout(60000);
const cluster: PythCluster = "pythtest-crosschain";
const messageBufferProgram = new Program(
messageBuffer as Idl,
new PublicKey(MESSAGE_BUFFER_PROGRAM_ID),
new AnchorProvider(
new Connection(getPythClusterApiUrl(cluster)),
new Wallet(new Keypair()),
AnchorProvider.defaultOptions()
)
) as unknown as Program<MessageBuffer>;
const parser = MultisigParser.fromCluster(cluster);
const allowedProgramAuth = PublicKey.unique();
const baseAccountKey = PublicKey.unique();
messageBufferProgram.methods
.createBuffer(allowedProgramAuth, baseAccountKey, 100)
.accounts({
admin: PublicKey.unique(),
payer: PublicKey.unique(),
})
.remainingAccounts([
{
pubkey: PublicKey.unique(),
isSigner: false,
isWritable: true,
},
])
.instruction()
.then((instruction) => {
const parsedInstruction = parser.parseInstruction(instruction);
if (parsedInstruction instanceof MessageBufferMultisigInstruction) {
expect(parsedInstruction.program).toBe(
MultisigInstructionProgram.MessageBuffer
);
expect(parsedInstruction.name).toBe("createBuffer");
expect(
parsedInstruction.accounts.named["whitelist"].pubkey.equals(
instruction.keys[0].pubkey
)
).toBeTruthy();
expect(parsedInstruction.accounts.named["whitelist"].isSigner).toBe(
instruction.keys[0].isSigner
);
expect(parsedInstruction.accounts.named["whitelist"].isWritable).toBe(
instruction.keys[0].isWritable
);
expect(
parsedInstruction.accounts.named["admin"].pubkey.equals(
instruction.keys[1].pubkey
)
).toBeTruthy();
expect(parsedInstruction.accounts.named["admin"].isSigner).toBe(
instruction.keys[1].isSigner
);
expect(parsedInstruction.accounts.named["admin"].isWritable).toBe(
instruction.keys[1].isWritable
);
expect(
parsedInstruction.accounts.named["payer"].pubkey.equals(
instruction.keys[2].pubkey
)
).toBeTruthy();
expect(parsedInstruction.accounts.named["payer"].isSigner).toBe(
instruction.keys[2].isSigner
);
expect(parsedInstruction.accounts.named["payer"].isWritable).toBe(
instruction.keys[2].isWritable
);
expect(
parsedInstruction.accounts.named["systemProgram"].pubkey.equals(
instruction.keys[3].pubkey
)
).toBeTruthy();
expect(parsedInstruction.accounts.named["systemProgram"].isSigner).toBe(
instruction.keys[3].isSigner
);
expect(
parsedInstruction.accounts.named["systemProgram"].isWritable
).toBe(instruction.keys[3].isWritable);
expect(parsedInstruction.accounts.remaining.length).toBe(1);
expect(
parsedInstruction.accounts.remaining[0].pubkey.equals(
instruction.keys[4].pubkey
)
).toBeTruthy();
expect(parsedInstruction.accounts.remaining[0].isSigner).toBe(
instruction.keys[4].isSigner
);
expect(parsedInstruction.accounts.remaining[0].isWritable).toBe(
instruction.keys[4].isWritable
);
expect(
parsedInstruction.args.allowedProgramAuth.equals(allowedProgramAuth)
).toBeTruthy();
expect(
parsedInstruction.args.baseAccountKey.equals(baseAccountKey)
).toBeTruthy();
expect(parsedInstruction.args.targetSize).toBe(100);
done();
} else {
done("Not instance of MessageBufferMultisigInstruction");
}
});
});
test("Message buffer multisig instruction parse: delete buffer", (done) => {
jest.setTimeout(60000);
const cluster: PythCluster = "pythtest-crosschain";
const messageBufferProgram = new Program(
messageBuffer as Idl,
new PublicKey(MESSAGE_BUFFER_PROGRAM_ID),
new AnchorProvider(
new Connection(getPythClusterApiUrl(cluster)),
new Wallet(new Keypair()),
AnchorProvider.defaultOptions()
)
) as unknown as Program<MessageBuffer>;
const parser = MultisigParser.fromCluster(cluster);
const allowedProgramAuth = PublicKey.unique();
const baseAccountKey = PublicKey.unique();
messageBufferProgram.methods
.deleteBuffer(allowedProgramAuth, baseAccountKey)
.accounts({
admin: PublicKey.unique(),
payer: PublicKey.unique(),
messageBuffer: PublicKey.unique(),
})
.instruction()
.then((instruction) => {
const parsedInstruction = parser.parseInstruction(instruction);
if (parsedInstruction instanceof MessageBufferMultisigInstruction) {
expect(parsedInstruction.program).toBe(
MultisigInstructionProgram.MessageBuffer
);
expect(parsedInstruction.name).toBe("deleteBuffer");
expect(
parsedInstruction.accounts.named["whitelist"].pubkey.equals(
instruction.keys[0].pubkey
)
).toBeTruthy();
expect(parsedInstruction.accounts.named["whitelist"].isSigner).toBe(
instruction.keys[0].isSigner
);
expect(parsedInstruction.accounts.named["whitelist"].isWritable).toBe(
instruction.keys[0].isWritable
);
expect(
parsedInstruction.accounts.named["admin"].pubkey.equals(
instruction.keys[1].pubkey
)
).toBeTruthy();
expect(parsedInstruction.accounts.named["admin"].isSigner).toBe(
instruction.keys[1].isSigner
);
expect(parsedInstruction.accounts.named["admin"].isWritable).toBe(
instruction.keys[1].isWritable
);
expect(
parsedInstruction.accounts.named["payer"].pubkey.equals(
instruction.keys[2].pubkey
)
).toBeTruthy();
expect(parsedInstruction.accounts.named["payer"].isSigner).toBe(
instruction.keys[2].isSigner
);
expect(parsedInstruction.accounts.named["payer"].isWritable).toBe(
instruction.keys[2].isWritable
);
expect(
parsedInstruction.accounts.named["messageBuffer"].pubkey.equals(
instruction.keys[3].pubkey
)
).toBeTruthy();
expect(parsedInstruction.accounts.named["messageBuffer"].isSigner).toBe(
instruction.keys[3].isSigner
);
expect(
parsedInstruction.accounts.named["messageBuffer"].isWritable
).toBe(instruction.keys[3].isWritable);
expect(parsedInstruction.accounts.remaining.length).toBe(0);
expect(
parsedInstruction.args.allowedProgramAuth.equals(allowedProgramAuth)
).toBeTruthy();
expect(
parsedInstruction.args.baseAccountKey.equals(baseAccountKey)
).toBeTruthy();
done();
} else {
done("Not instance of MessageBufferMultisigInstruction");
}
});
});

View File

@ -1,4 +1,4 @@
import { AnchorProvider, Wallet } from "@project-serum/anchor"; import { AnchorProvider, Wallet } from "@coral-xyz/anchor";
import { pythOracleProgram } from "@pythnetwork/client"; import { pythOracleProgram } from "@pythnetwork/client";
import { import {
getPythClusterApiUrl, getPythClusterApiUrl,

View File

@ -1,4 +1,4 @@
import { AnchorProvider, Wallet } from "@project-serum/anchor"; import { AnchorProvider, Wallet } from "@coral-xyz/anchor";
import { pythOracleProgram } from "@pythnetwork/client"; import { pythOracleProgram } from "@pythnetwork/client";
import { import {
getPythClusterApiUrl, getPythClusterApiUrl,

View File

@ -1,5 +1,5 @@
import { createWormholeProgramInterface } from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole"; import { createWormholeProgramInterface } from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole";
import { AnchorProvider, Wallet } from "@project-serum/anchor"; import { AnchorProvider, Wallet } from "@coral-xyz/anchor";
import { import {
getPythClusterApiUrl, getPythClusterApiUrl,
PythCluster, PythCluster,

View File

@ -8,3 +8,4 @@ export * from "./remote_executor";
export * from "./bpf_upgradable_loader"; export * from "./bpf_upgradable_loader";
export * from "./deterministic_oracle_accounts"; export * from "./deterministic_oracle_accounts";
export * from "./cranks"; export * from "./cranks";
export * from "./message_buffer";

View File

@ -0,0 +1,41 @@
import { getPythProgramKeyForCluster, PythCluster } from "@pythnetwork/client";
import { PublicKey } from "@solana/web3.js";
/**
* Address of the message buffer program.
*/
export const MESSAGE_BUFFER_PROGRAM_ID: PublicKey = new PublicKey(
"7Vbmv1jt4vyuqBZcpYPpnVhrqVe5e6ZPb6JxDcffRHUM"
);
export const MESSAGE_BUFFER_BUFFER_SIZE = 2048;
export function isMessageBufferAvailable(cluster: PythCluster): boolean {
return cluster === "pythtest-crosschain";
}
export function getPythOracleMessageBufferCpiAuth(
cluster: PythCluster
): PublicKey {
const pythOracleProgramId = getPythProgramKeyForCluster(cluster);
return PublicKey.findProgramAddressSync(
[Buffer.from("upd_price_write"), MESSAGE_BUFFER_PROGRAM_ID.toBuffer()],
pythOracleProgramId
)[0];
}
// TODO: We can remove this when createBuffer takes message buffer account
// as a named account because Anchor can automatically find the address.
export function getMessageBufferAddressForPrice(
cluster: PythCluster,
priceAccount: PublicKey
): PublicKey {
return PublicKey.findProgramAddressSync(
[
getPythOracleMessageBufferCpiAuth(cluster).toBuffer(),
Buffer.from("message"),
priceAccount.toBuffer(),
],
MESSAGE_BUFFER_PROGRAM_ID
)[0];
}

View File

@ -0,0 +1,55 @@
import {
MultisigInstruction,
MultisigInstructionProgram,
UNRECOGNIZED_INSTRUCTION,
} from ".";
import { AnchorAccounts, resolveAccountNames } from "./anchor";
import messageBuffer from "message_buffer/idl/message_buffer.json";
import { TransactionInstruction } from "@solana/web3.js";
import { Idl, BorshCoder } from "@coral-xyz/anchor";
export class MessageBufferMultisigInstruction implements MultisigInstruction {
readonly program = MultisigInstructionProgram.MessageBuffer;
readonly name: string;
readonly args: { [key: string]: any };
readonly accounts: AnchorAccounts;
constructor(
name: string,
args: { [key: string]: any },
accounts: AnchorAccounts
) {
this.name = name;
this.args = args;
this.accounts = accounts;
}
static fromTransactionInstruction(
instruction: TransactionInstruction
): MessageBufferMultisigInstruction {
const messageBufferInstructionCoder = new BorshCoder(messageBuffer as Idl)
.instruction;
const deserializedData = messageBufferInstructionCoder.decode(
instruction.data
);
if (deserializedData) {
return new MessageBufferMultisigInstruction(
deserializedData.name,
deserializedData.data,
resolveAccountNames(
messageBuffer as Idl,
deserializedData.name,
instruction
)
);
} else {
return new MessageBufferMultisigInstruction(
UNRECOGNIZED_INSTRUCTION,
{ data: instruction.data },
{ named: {}, remaining: instruction.keys }
);
}
}
}

View File

@ -3,7 +3,9 @@ import {
PythCluster, PythCluster,
} from "@pythnetwork/client/lib/cluster"; } from "@pythnetwork/client/lib/cluster";
import { PublicKey, TransactionInstruction } from "@solana/web3.js"; import { PublicKey, TransactionInstruction } from "@solana/web3.js";
import { MESSAGE_BUFFER_PROGRAM_ID } from "../message_buffer";
import { WORMHOLE_ADDRESS } from "../wormhole"; import { WORMHOLE_ADDRESS } from "../wormhole";
import { MessageBufferMultisigInstruction } from "./MessageBufferMultisigInstruction";
import { PythMultisigInstruction } from "./PythMultisigInstruction"; import { PythMultisigInstruction } from "./PythMultisigInstruction";
import { WormholeMultisigInstruction } from "./WormholeMultisigInstruction"; import { WormholeMultisigInstruction } from "./WormholeMultisigInstruction";
@ -11,6 +13,7 @@ export const UNRECOGNIZED_INSTRUCTION = "unrecognizedInstruction";
export enum MultisigInstructionProgram { export enum MultisigInstructionProgram {
PythOracle, PythOracle,
WormholeBridge, WormholeBridge,
MessageBuffer,
UnrecognizedProgram, UnrecognizedProgram,
} }
@ -60,6 +63,10 @@ export class MultisigParser {
); );
} else if (instruction.programId.equals(this.pythOracleAddress)) { } else if (instruction.programId.equals(this.pythOracleAddress)) {
return PythMultisigInstruction.fromTransactionInstruction(instruction); return PythMultisigInstruction.fromTransactionInstruction(instruction);
} else if (instruction.programId.equals(MESSAGE_BUFFER_PROGRAM_ID)) {
return MessageBufferMultisigInstruction.fromTransactionInstruction(
instruction
);
} else { } else {
return UnrecognizedProgram.fromTransactionInstruction(instruction); return UnrecognizedProgram.fromTransactionInstruction(instruction);
} }
@ -68,3 +75,4 @@ export class MultisigParser {
export { WormholeMultisigInstruction } from "./WormholeMultisigInstruction"; export { WormholeMultisigInstruction } from "./WormholeMultisigInstruction";
export { PythMultisigInstruction } from "./PythMultisigInstruction"; export { PythMultisigInstruction } from "./PythMultisigInstruction";
export { MessageBufferMultisigInstruction } from "./MessageBufferMultisigInstruction";

View File

@ -9,7 +9,7 @@ import {
PACKET_DATA_SIZE, PACKET_DATA_SIZE,
} from "@solana/web3.js"; } from "@solana/web3.js";
import { BN } from "bn.js"; import { BN } from "bn.js";
import { AnchorProvider } from "@project-serum/anchor"; import { AnchorProvider } from "@coral-xyz/anchor";
import { import {
createWormholeProgramInterface, createWormholeProgramInterface,
deriveWormholeBridgeDataKey, deriveWormholeBridgeDataKey,

View File

@ -1,19 +1,27 @@
import { AnchorProvider, Program } from '@coral-xyz/anchor' import { AnchorProvider, Idl, Program } from '@coral-xyz/anchor'
import { AccountType, getPythProgramKeyForCluster } from '@pythnetwork/client' import { AccountType, getPythProgramKeyForCluster } from '@pythnetwork/client'
import { PythOracle, pythOracleProgram } from '@pythnetwork/client/lib/anchor' import { PythOracle, pythOracleProgram } from '@pythnetwork/client/lib/anchor'
import { useWallet } from '@solana/wallet-adapter-react' import { useWallet } from '@solana/wallet-adapter-react'
import { Cluster, PublicKey, TransactionInstruction } from '@solana/web3.js' import { Cluster, PublicKey, TransactionInstruction } from '@solana/web3.js'
import messageBuffer from 'message_buffer/idl/message_buffer.json'
import { MessageBuffer } from 'message_buffer/idl/message_buffer'
import axios from 'axios' import axios from 'axios'
import { useCallback, useContext, useEffect, useState } from 'react' import { useCallback, useContext, useEffect, useState } from 'react'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { import {
findDetermisticAccountAddress, findDetermisticAccountAddress,
getMultisigCluster, getMultisigCluster,
getPythOracleMessageBufferCpiAuth,
isMessageBufferAvailable,
isRemoteCluster, isRemoteCluster,
mapKey, mapKey,
MESSAGE_BUFFER_PROGRAM_ID,
MESSAGE_BUFFER_BUFFER_SIZE,
PRICE_FEED_MULTISIG, PRICE_FEED_MULTISIG,
proposeInstructions, proposeInstructions,
WORMHOLE_ADDRESS, WORMHOLE_ADDRESS,
PRICE_FEED_OPS_KEY,
getMessageBufferAddressForPrice,
} from 'xc_admin_common' } from 'xc_admin_common'
import { ClusterContext } from '../../contexts/ClusterContext' import { ClusterContext } from '../../contexts/ClusterContext'
import { useMultisigContext } from '../../contexts/MultisigContext' import { useMultisigContext } from '../../contexts/MultisigContext'
@ -42,6 +50,9 @@ const General = ({ proposerServerUrl }: { proposerServerUrl: string }) => {
const [pythProgramClient, setPythProgramClient] = const [pythProgramClient, setPythProgramClient] =
useState<Program<PythOracle>>() useState<Program<PythOracle>>()
const [messageBufferClient, setMessageBufferClient] =
useState<Program<MessageBuffer>>()
const openModal = () => { const openModal = () => {
setIsModalOpen(true) setIsModalOpen(true)
} }
@ -323,6 +334,33 @@ const General = ({ proposerServerUrl }: { proposerServerUrl: string }) => {
.instruction() .instruction()
) )
if (isMessageBufferAvailable(cluster) && messageBufferClient) {
// create create buffer instruction for the price account
instructions.push(
await messageBufferClient.methods
.createBuffer(
getPythOracleMessageBufferCpiAuth(cluster),
priceAccountKey,
MESSAGE_BUFFER_BUFFER_SIZE
)
.accounts({
admin: fundingAccount,
payer: PRICE_FEED_OPS_KEY,
})
.remainingAccounts([
{
pubkey: getMessageBufferAddressForPrice(
cluster,
priceAccountKey
),
isSigner: false,
isWritable: true,
},
])
.instruction()
)
}
// create add publisher instruction if there are any publishers // create add publisher instruction if there are any publishers
for (let publisherKey of newChanges.priceAccounts[0].publishers) { for (let publisherKey of newChanges.priceAccounts[0].publishers) {
@ -350,6 +388,8 @@ const General = ({ proposerServerUrl }: { proposerServerUrl: string }) => {
) )
} }
} else if (!newChanges) { } else if (!newChanges) {
const priceAccount = new PublicKey(prev.priceAccounts[0].address)
// if new is undefined, it means that the symbol is deleted // if new is undefined, it means that the symbol is deleted
// create delete price account instruction // create delete price account instruction
instructions.push( instructions.push(
@ -358,10 +398,11 @@ const General = ({ proposerServerUrl }: { proposerServerUrl: string }) => {
.accounts({ .accounts({
fundingAccount, fundingAccount,
productAccount: new PublicKey(prev.address), productAccount: new PublicKey(prev.address),
priceAccount: new PublicKey(prev.priceAccounts[0].address), priceAccount,
}) })
.instruction() .instruction()
) )
// create delete product account instruction // create delete product account instruction
instructions.push( instructions.push(
await pythProgramClient.methods await pythProgramClient.methods
@ -373,6 +414,26 @@ const General = ({ proposerServerUrl }: { proposerServerUrl: string }) => {
}) })
.instruction() .instruction()
) )
if (isMessageBufferAvailable(cluster) && messageBufferClient) {
// create delete buffer instruction for the price buffer
instructions.push(
await messageBufferClient.methods
.deleteBuffer(
getPythOracleMessageBufferCpiAuth(cluster),
priceAccount
)
.accounts({
admin: fundingAccount,
payer: PRICE_FEED_OPS_KEY,
messageBuffer: getMessageBufferAddressForPrice(
cluster,
priceAccount
),
})
.instruction()
)
}
} else { } else {
// check if metadata has changed // check if metadata has changed
if ( if (
@ -741,6 +802,16 @@ const General = ({ proposerServerUrl }: { proposerServerUrl: string }) => {
setPythProgramClient( setPythProgramClient(
pythOracleProgram(getPythProgramKeyForCluster(cluster), provider) pythOracleProgram(getPythProgramKeyForCluster(cluster), provider)
) )
if (isMessageBufferAvailable(cluster)) {
setMessageBufferClient(
new Program(
messageBuffer as Idl,
new PublicKey(MESSAGE_BUFFER_PROGRAM_ID),
provider
) as unknown as Program<MessageBuffer>
)
}
} }
}, [connection, connected, cluster, proposeSquads]) }, [connection, connected, cluster, proposeSquads])

View File

@ -17,9 +17,11 @@ import {
getMultisigCluster, getMultisigCluster,
getProposals, getProposals,
MultisigInstruction, MultisigInstruction,
MultisigInstructionProgram,
MultisigParser, MultisigParser,
PRICE_FEED_MULTISIG, PRICE_FEED_MULTISIG,
PythMultisigInstruction, PythMultisigInstruction,
MessageBufferMultisigInstruction,
UnrecognizedProgram, UnrecognizedProgram,
WormholeMultisigInstruction, WormholeMultisigInstruction,
} from 'xc_admin_common' } from 'xc_admin_common'
@ -788,9 +790,12 @@ const Proposal = ({
{parsedInstruction instanceof {parsedInstruction instanceof
PythMultisigInstruction PythMultisigInstruction
? 'Pyth Oracle' ? 'Pyth Oracle'
: innerInstruction instanceof : parsedInstruction instanceof
WormholeMultisigInstruction WormholeMultisigInstruction
? 'Wormhole' ? 'Wormhole'
: parsedInstruction instanceof
MessageBufferMultisigInstruction
? 'Message Buffer'
: 'Unknown'} : 'Unknown'}
</div> </div>
</div> </div>
@ -803,7 +808,9 @@ const Proposal = ({
{parsedInstruction instanceof {parsedInstruction instanceof
PythMultisigInstruction || PythMultisigInstruction ||
parsedInstruction instanceof parsedInstruction instanceof
WormholeMultisigInstruction WormholeMultisigInstruction ||
parsedInstruction instanceof
MessageBufferMultisigInstruction
? parsedInstruction.name ? parsedInstruction.name
: 'Unknown'} : 'Unknown'}
</div> </div>
@ -816,7 +823,9 @@ const Proposal = ({
{parsedInstruction instanceof {parsedInstruction instanceof
PythMultisigInstruction || PythMultisigInstruction ||
parsedInstruction instanceof parsedInstruction instanceof
WormholeMultisigInstruction ? ( WormholeMultisigInstruction ||
parsedInstruction instanceof
MessageBufferMultisigInstruction ? (
Object.keys(parsedInstruction.args).length > Object.keys(parsedInstruction.args).length >
0 ? ( 0 ? (
<div className="col-span-4 mt-2 bg-[#444157] p-4 lg:col-span-3 lg:mt-0"> <div className="col-span-4 mt-2 bg-[#444157] p-4 lg:col-span-3 lg:mt-0">
@ -906,7 +915,9 @@ const Proposal = ({
{parsedInstruction instanceof {parsedInstruction instanceof
PythMultisigInstruction || PythMultisigInstruction ||
parsedInstruction instanceof parsedInstruction instanceof
WormholeMultisigInstruction ? ( WormholeMultisigInstruction ||
parsedInstruction instanceof
MessageBufferMultisigInstruction ? (
<div <div
key={`${index}_accounts`} key={`${index}_accounts`}
className="grid grid-cols-4 justify-between" className="grid grid-cols-4 justify-between"
@ -983,9 +994,36 @@ const Proposal = ({
) : null} ) : null}
</> </>
))} ))}
{parsedInstruction.accounts.remaining.map(
(accountMeta, index) => (
<>
<div
key="rem-{index}"
className="flex justify-between border-t border-beige-300 py-3"
>
<div className="max-w-[80px] break-words sm:max-w-none sm:break-normal">
Remaining {index + 1}
</div>
<div className="space-y-2 sm:flex sm:space-y-0 sm:space-x-2">
<div className="flex items-center space-x-2 sm:ml-2">
{accountMeta.isSigner ? (
<SignerTag />
) : null}
{accountMeta.isWritable ? (
<WritableTag />
) : null}
</div>
<CopyPubkey
pubkey={accountMeta.pubkey.toBase58()}
/>
</div>
</div>
</>
)
)}
</div> </div>
) : ( ) : (
<div>No arguments</div> <div>No accounts</div>
)} )}
</div> </div>
) : parsedInstruction instanceof ) : parsedInstruction instanceof
@ -1125,7 +1163,10 @@ const Proposals = ({
keys: remoteIx.keys as AccountMeta[], keys: remoteIx.keys as AccountMeta[],
}) })
return ( return (
parsedRemoteInstruction instanceof PythMultisigInstruction parsedRemoteInstruction instanceof
PythMultisigInstruction ||
parsedRemoteInstruction instanceof
MessageBufferMultisigInstruction
) )
}) && }) &&
ix.governanceAction.targetChainId === 'pythnet') ix.governanceAction.targetChainId === 'pythnet')

View File

@ -35,7 +35,8 @@
"react-hot-toast": "^2.4.0", "react-hot-toast": "^2.4.0",
"typescript": "4.9.4", "typescript": "4.9.4",
"use-debounce": "^9.0.2", "use-debounce": "^9.0.2",
"xc_admin_common": "*" "xc_admin_common": "*",
"message_buffer": "*"
}, },
"devDependencies": { "devDependencies": {
"@svgr/webpack": "^6.3.1", "@svgr/webpack": "^6.3.1",

4
package-lock.json generated
View File

@ -1333,6 +1333,7 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@certusone/wormhole-sdk": "^0.9.8", "@certusone/wormhole-sdk": "^0.9.8",
"@coral-xyz/anchor": "^0.26.0",
"@pythnetwork/client": "^2.17.0", "@pythnetwork/client": "^2.17.0",
"@solana/buffer-layout": "^4.0.1", "@solana/buffer-layout": "^4.0.1",
"@solana/web3.js": "^1.73.0", "@solana/web3.js": "^1.73.0",
@ -1421,6 +1422,7 @@
"axios": "^1.4.0", "axios": "^1.4.0",
"copy-to-clipboard": "^3.3.3", "copy-to-clipboard": "^3.3.3",
"gsap": "^3.11.4", "gsap": "^3.11.4",
"message_buffer": "*",
"next": "12.2.5", "next": "12.2.5",
"next-seo": "^5.15.0", "next-seo": "^5.15.0",
"react": "18.2.0", "react": "18.2.0",
@ -106543,6 +106545,7 @@
"version": "file:governance/xc_admin/packages/xc_admin_common", "version": "file:governance/xc_admin/packages/xc_admin_common",
"requires": { "requires": {
"@certusone/wormhole-sdk": "^0.9.8", "@certusone/wormhole-sdk": "^0.9.8",
"@coral-xyz/anchor": "^0.26.0",
"@pythnetwork/client": "^2.17.0", "@pythnetwork/client": "^2.17.0",
"@solana/buffer-layout": "^4.0.1", "@solana/buffer-layout": "^4.0.1",
"@solana/web3.js": "^1.73.0", "@solana/web3.js": "^1.73.0",
@ -106626,6 +106629,7 @@
"eslint": "8.22.0", "eslint": "8.22.0",
"eslint-config-next": "12.2.5", "eslint-config-next": "12.2.5",
"gsap": "^3.11.4", "gsap": "^3.11.4",
"message_buffer": "*",
"next": "12.2.5", "next": "12.2.5",
"next-seo": "^5.15.0", "next-seo": "^5.15.0",
"postcss": "^8.4.16", "postcss": "^8.4.16",