[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:
parent
183081cc20
commit
95ca9d1d92
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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];
|
||||||
|
}
|
|
@ -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 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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";
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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])
|
||||||
|
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in New Issue