xc-admin/MultisigParser for wormhole instructions (#470)
* Checkpoint * Refactor * Unrecognized instruction -> unrecognized program * Shorten tests * Rename test * Rename WormholeInstruction to WormholeMultisigInstruction * Refactor into constructor and builder method * Rename file
This commit is contained in:
parent
7c2e02ea0b
commit
d1f5e5955a
|
@ -3749,7 +3749,8 @@
|
|||
},
|
||||
"node_modules/@pythnetwork/client": {
|
||||
"version": "2.9.0",
|
||||
"license": "Apache-2.0",
|
||||
"resolved": "https://registry.npmjs.org/@pythnetwork/client/-/client-2.9.0.tgz",
|
||||
"integrity": "sha512-2CyDmTwPWW+JCQgRKUpwMR/31oiLWH6I3GA0h2ZXIcbt/hWxcr5TXyKlWuyi+l+jh73WWh88gq8NXLoIgRcvkA==",
|
||||
"dependencies": {
|
||||
"buffer": "^6.0.1"
|
||||
},
|
||||
|
@ -12758,6 +12759,7 @@
|
|||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@certusone/wormhole-sdk": "^0.9.8",
|
||||
"@pythnetwork/client": "^2.9.0",
|
||||
"@solana/buffer-layout": "^4.0.1",
|
||||
"@solana/web3.js": "^1.73.0",
|
||||
"@sqds/mesh": "^1.0.6",
|
||||
|
@ -15330,6 +15332,8 @@
|
|||
},
|
||||
"@pythnetwork/client": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@pythnetwork/client/-/client-2.9.0.tgz",
|
||||
"integrity": "sha512-2CyDmTwPWW+JCQgRKUpwMR/31oiLWH6I3GA0h2ZXIcbt/hWxcr5TXyKlWuyi+l+jh73WWh88gq8NXLoIgRcvkA==",
|
||||
"requires": {
|
||||
"buffer": "^6.0.1"
|
||||
},
|
||||
|
@ -21188,6 +21192,7 @@
|
|||
"version": "file:packages/xc-admin-common",
|
||||
"requires": {
|
||||
"@certusone/wormhole-sdk": "^0.9.8",
|
||||
"@pythnetwork/client": "*",
|
||||
"@solana/buffer-layout": "^4.0.1",
|
||||
"@solana/web3.js": "^1.73.0",
|
||||
"@sqds/mesh": "^1.0.6",
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@certusone/wormhole-sdk": "^0.9.8",
|
||||
"@pythnetwork/client": "^2.9.0",
|
||||
"@solana/buffer-layout": "^4.0.1",
|
||||
"@solana/web3.js": "^1.73.0",
|
||||
"@sqds/mesh": "^1.0.6",
|
||||
|
|
|
@ -0,0 +1,365 @@
|
|||
import { ChainName } from "@certusone/wormhole-sdk";
|
||||
import { createWormholeProgramInterface } from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole";
|
||||
import { AnchorProvider, Wallet } from "@project-serum/anchor";
|
||||
import {
|
||||
getPythClusterApiUrl,
|
||||
PythCluster,
|
||||
} from "@pythnetwork/client/lib/cluster";
|
||||
import {
|
||||
Connection,
|
||||
Keypair,
|
||||
PublicKey,
|
||||
SystemProgram,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import {
|
||||
MultisigInstructionProgram,
|
||||
MultisigParser,
|
||||
WORMHOLE_ADDRESS,
|
||||
} from "..";
|
||||
import {
|
||||
encodeExecutePostedVaa,
|
||||
ExecutePostedVaaArgs,
|
||||
} from "../governance_payload/ExecutePostedVaa";
|
||||
import { WormholeMultisigInstruction } from "../multisig_transaction/WormholeMultisigInstruction";
|
||||
|
||||
test("Wormhole multisig instruction parse: send message without governance payload", (done) => {
|
||||
jest.setTimeout(60000);
|
||||
|
||||
const cluster: PythCluster = "devnet";
|
||||
const wormholeProgram = createWormholeProgramInterface(
|
||||
WORMHOLE_ADDRESS[cluster]!,
|
||||
new AnchorProvider(
|
||||
new Connection(getPythClusterApiUrl(cluster)),
|
||||
new Wallet(new Keypair()),
|
||||
AnchorProvider.defaultOptions()
|
||||
)
|
||||
);
|
||||
const parser = new MultisigParser(cluster);
|
||||
|
||||
wormholeProgram.methods
|
||||
.postMessage(1, Buffer.from([0]), 1)
|
||||
.accounts({
|
||||
bridge: PublicKey.unique(),
|
||||
message: PublicKey.unique(),
|
||||
emitter: PublicKey.unique(),
|
||||
sequence: PublicKey.unique(),
|
||||
feeCollector: PublicKey.unique(),
|
||||
clock: PublicKey.unique(),
|
||||
})
|
||||
.instruction()
|
||||
.then((instruction) => {
|
||||
const parsedInstruction = parser.parseInstruction(instruction);
|
||||
expect(
|
||||
parsedInstruction instanceof WormholeMultisigInstruction
|
||||
).toBeTruthy();
|
||||
if (parsedInstruction instanceof WormholeMultisigInstruction) {
|
||||
expect(parsedInstruction.program).toBe(
|
||||
MultisigInstructionProgram.WormholeBridge
|
||||
);
|
||||
expect(parsedInstruction.name).toBe("postMessage");
|
||||
expect(
|
||||
parsedInstruction.accounts.named["bridge"].pubkey.equals(
|
||||
instruction.keys[0].pubkey
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(parsedInstruction.accounts.named["bridge"].isSigner).toBe(
|
||||
instruction.keys[0].isSigner
|
||||
);
|
||||
expect(parsedInstruction.accounts.named["bridge"].isWritable).toBe(
|
||||
instruction.keys[0].isWritable
|
||||
);
|
||||
expect(
|
||||
parsedInstruction.accounts.named["message"].pubkey.equals(
|
||||
instruction.keys[1].pubkey
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(parsedInstruction.accounts.named["message"].isSigner).toBe(
|
||||
instruction.keys[1].isSigner
|
||||
);
|
||||
expect(parsedInstruction.accounts.named["message"].isWritable).toBe(
|
||||
instruction.keys[1].isWritable
|
||||
);
|
||||
expect(
|
||||
parsedInstruction.accounts.named["emitter"].pubkey.equals(
|
||||
instruction.keys[2].pubkey
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(parsedInstruction.accounts.named["emitter"].isSigner).toBe(
|
||||
instruction.keys[2].isSigner
|
||||
);
|
||||
expect(parsedInstruction.accounts.named["emitter"].isWritable).toBe(
|
||||
instruction.keys[2].isWritable
|
||||
);
|
||||
expect(
|
||||
parsedInstruction.accounts.named["sequence"].pubkey.equals(
|
||||
instruction.keys[3].pubkey
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(parsedInstruction.accounts.named["sequence"].isSigner).toBe(
|
||||
instruction.keys[3].isSigner
|
||||
);
|
||||
expect(parsedInstruction.accounts.named["sequence"].isWritable).toBe(
|
||||
instruction.keys[3].isWritable
|
||||
);
|
||||
expect(
|
||||
parsedInstruction.accounts.named["payer"].pubkey.equals(
|
||||
instruction.keys[4].pubkey
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(parsedInstruction.accounts.named["payer"].isSigner).toBe(
|
||||
instruction.keys[4].isSigner
|
||||
);
|
||||
expect(parsedInstruction.accounts.named["payer"].isWritable).toBe(
|
||||
instruction.keys[4].isWritable
|
||||
);
|
||||
expect(
|
||||
parsedInstruction.accounts.named["feeCollector"].pubkey.equals(
|
||||
instruction.keys[5].pubkey
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(parsedInstruction.accounts.named["feeCollector"].isSigner).toBe(
|
||||
instruction.keys[5].isSigner
|
||||
);
|
||||
expect(
|
||||
parsedInstruction.accounts.named["feeCollector"].isWritable
|
||||
).toBe(instruction.keys[5].isWritable);
|
||||
expect(
|
||||
parsedInstruction.accounts.named["clock"].pubkey.equals(
|
||||
instruction.keys[6].pubkey
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(parsedInstruction.accounts.named["clock"].isSigner).toBe(
|
||||
instruction.keys[6].isSigner
|
||||
);
|
||||
expect(parsedInstruction.accounts.named["clock"].isWritable).toBe(
|
||||
instruction.keys[6].isWritable
|
||||
);
|
||||
expect(
|
||||
parsedInstruction.accounts.named["rent"].pubkey.equals(
|
||||
instruction.keys[7].pubkey
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(parsedInstruction.accounts.named["rent"].isSigner).toBe(
|
||||
instruction.keys[7].isSigner
|
||||
);
|
||||
expect(parsedInstruction.accounts.named["rent"].isWritable).toBe(
|
||||
instruction.keys[7].isWritable
|
||||
);
|
||||
expect(
|
||||
parsedInstruction.accounts.named["systemProgram"].pubkey.equals(
|
||||
instruction.keys[8].pubkey
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(parsedInstruction.accounts.named["systemProgram"].isSigner).toBe(
|
||||
instruction.keys[8].isSigner
|
||||
);
|
||||
expect(
|
||||
parsedInstruction.accounts.named["systemProgram"].isWritable
|
||||
).toBe(instruction.keys[8].isWritable);
|
||||
expect(parsedInstruction.accounts.remaining.length).toBe(0);
|
||||
|
||||
expect(parsedInstruction.args.nonce).toBe(1);
|
||||
expect(parsedInstruction.args.payload.equals(Buffer.from([0])));
|
||||
expect(parsedInstruction.args.consistencyLevel).toBe(1);
|
||||
expect(parsedInstruction.args.targetChain).toBeUndefined();
|
||||
done();
|
||||
} else {
|
||||
done("Not instance of WormholeInstruction");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test("Wormhole multisig instruction parse: send message with governance payload", (done) => {
|
||||
jest.setTimeout(60000);
|
||||
|
||||
const cluster: PythCluster = "devnet";
|
||||
const wormholeProgram = createWormholeProgramInterface(
|
||||
WORMHOLE_ADDRESS[cluster]!,
|
||||
new AnchorProvider(
|
||||
new Connection(getPythClusterApiUrl(cluster)),
|
||||
new Wallet(new Keypair()),
|
||||
AnchorProvider.defaultOptions()
|
||||
)
|
||||
);
|
||||
const parser = new MultisigParser(cluster);
|
||||
|
||||
const executePostedVaaArgs: ExecutePostedVaaArgs = {
|
||||
targetChainId: "pythnet" as ChainName,
|
||||
instructions: [
|
||||
SystemProgram.transfer({
|
||||
fromPubkey: PublicKey.unique(),
|
||||
toPubkey: PublicKey.unique(),
|
||||
lamports: 890880,
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
wormholeProgram.methods
|
||||
.postMessage(0, encodeExecutePostedVaa(executePostedVaaArgs), 0)
|
||||
.accounts({
|
||||
bridge: PublicKey.unique(),
|
||||
message: PublicKey.unique(),
|
||||
emitter: PublicKey.unique(),
|
||||
sequence: PublicKey.unique(),
|
||||
feeCollector: PublicKey.unique(),
|
||||
clock: PublicKey.unique(),
|
||||
})
|
||||
.instruction()
|
||||
.then((instruction) => {
|
||||
const parsedInstruction = parser.parseInstruction(instruction);
|
||||
if (parsedInstruction instanceof WormholeMultisigInstruction) {
|
||||
expect(
|
||||
parsedInstruction instanceof WormholeMultisigInstruction
|
||||
).toBeTruthy();
|
||||
expect(parsedInstruction.program).toBe(
|
||||
MultisigInstructionProgram.WormholeBridge
|
||||
);
|
||||
expect(parsedInstruction.name).toBe("postMessage");
|
||||
expect(
|
||||
parsedInstruction.accounts.named["bridge"].pubkey.equals(
|
||||
instruction.keys[0].pubkey
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(parsedInstruction.accounts.named["bridge"].isSigner).toBe(
|
||||
instruction.keys[0].isSigner
|
||||
);
|
||||
expect(parsedInstruction.accounts.named["bridge"].isWritable).toBe(
|
||||
instruction.keys[0].isWritable
|
||||
);
|
||||
expect(
|
||||
parsedInstruction.accounts.named["message"].pubkey.equals(
|
||||
instruction.keys[1].pubkey
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(parsedInstruction.accounts.named["message"].isSigner).toBe(
|
||||
instruction.keys[1].isSigner
|
||||
);
|
||||
expect(parsedInstruction.accounts.named["message"].isWritable).toBe(
|
||||
instruction.keys[1].isWritable
|
||||
);
|
||||
expect(
|
||||
parsedInstruction.accounts.named["emitter"].pubkey.equals(
|
||||
instruction.keys[2].pubkey
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(parsedInstruction.accounts.named["emitter"].isSigner).toBe(
|
||||
instruction.keys[2].isSigner
|
||||
);
|
||||
expect(parsedInstruction.accounts.named["emitter"].isWritable).toBe(
|
||||
instruction.keys[2].isWritable
|
||||
);
|
||||
expect(
|
||||
parsedInstruction.accounts.named["sequence"].pubkey.equals(
|
||||
instruction.keys[3].pubkey
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(parsedInstruction.accounts.named["sequence"].isSigner).toBe(
|
||||
instruction.keys[3].isSigner
|
||||
);
|
||||
expect(parsedInstruction.accounts.named["sequence"].isWritable).toBe(
|
||||
instruction.keys[3].isWritable
|
||||
);
|
||||
expect(
|
||||
parsedInstruction.accounts.named["payer"].pubkey.equals(
|
||||
instruction.keys[4].pubkey
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(parsedInstruction.accounts.named["payer"].isSigner).toBe(
|
||||
instruction.keys[4].isSigner
|
||||
);
|
||||
expect(parsedInstruction.accounts.named["payer"].isWritable).toBe(
|
||||
instruction.keys[4].isWritable
|
||||
);
|
||||
expect(
|
||||
parsedInstruction.accounts.named["feeCollector"].pubkey.equals(
|
||||
instruction.keys[5].pubkey
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(parsedInstruction.accounts.named["feeCollector"].isSigner).toBe(
|
||||
instruction.keys[5].isSigner
|
||||
);
|
||||
expect(
|
||||
parsedInstruction.accounts.named["feeCollector"].isWritable
|
||||
).toBe(instruction.keys[5].isWritable);
|
||||
expect(
|
||||
parsedInstruction.accounts.named["clock"].pubkey.equals(
|
||||
instruction.keys[6].pubkey
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(parsedInstruction.accounts.named["clock"].isSigner).toBe(
|
||||
instruction.keys[6].isSigner
|
||||
);
|
||||
expect(parsedInstruction.accounts.named["clock"].isWritable).toBe(
|
||||
instruction.keys[6].isWritable
|
||||
);
|
||||
expect(
|
||||
parsedInstruction.accounts.named["rent"].pubkey.equals(
|
||||
instruction.keys[7].pubkey
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(parsedInstruction.accounts.named["rent"].isSigner).toBe(
|
||||
instruction.keys[7].isSigner
|
||||
);
|
||||
expect(parsedInstruction.accounts.named["rent"].isWritable).toBe(
|
||||
instruction.keys[7].isWritable
|
||||
);
|
||||
expect(
|
||||
parsedInstruction.accounts.named["systemProgram"].pubkey.equals(
|
||||
instruction.keys[8].pubkey
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(parsedInstruction.accounts.named["systemProgram"].isSigner).toBe(
|
||||
instruction.keys[8].isSigner
|
||||
);
|
||||
expect(
|
||||
parsedInstruction.accounts.named["systemProgram"].isWritable
|
||||
).toBe(instruction.keys[8].isWritable);
|
||||
expect(parsedInstruction.accounts.remaining.length).toBe(0);
|
||||
|
||||
expect(parsedInstruction.args.nonce).toBe(0);
|
||||
expect(
|
||||
parsedInstruction.args.payload.equals(
|
||||
encodeExecutePostedVaa(executePostedVaaArgs)
|
||||
)
|
||||
);
|
||||
expect(parsedInstruction.args.consistencyLevel).toBe(0);
|
||||
|
||||
expect(parsedInstruction.args.governanceName).toBe("ExecutePostedVaa");
|
||||
|
||||
expect(parsedInstruction.args.governanceArgs.targetChainId).toBe(
|
||||
"pythnet"
|
||||
);
|
||||
|
||||
(
|
||||
parsedInstruction.args.governanceArgs
|
||||
.instructions as TransactionInstruction[]
|
||||
).forEach((instruction, i) => {
|
||||
expect(
|
||||
instruction.programId.equals(
|
||||
executePostedVaaArgs.instructions[i].programId
|
||||
)
|
||||
);
|
||||
expect(
|
||||
instruction.data.equals(executePostedVaaArgs.instructions[i].data)
|
||||
);
|
||||
instruction.keys.forEach((account, j) => {
|
||||
expect(
|
||||
account.pubkey.equals(
|
||||
executePostedVaaArgs.instructions[i].keys[j].pubkey
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(account.isSigner).toBe(
|
||||
executePostedVaaArgs.instructions[i].keys[j].isSigner
|
||||
);
|
||||
expect(account.isWritable).toBe(
|
||||
executePostedVaaArgs.instructions[i].keys[j].isWritable
|
||||
);
|
||||
});
|
||||
});
|
||||
done();
|
||||
} else {
|
||||
done("Not instance of WormholeInstruction");
|
||||
}
|
||||
});
|
||||
});
|
|
@ -5,6 +5,10 @@ import {
|
|||
toChainName,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import * as BufferLayout from "@solana/buffer-layout";
|
||||
import {
|
||||
decodeExecutePostedVaa,
|
||||
ExecutePostedVaaArgs,
|
||||
} from "./ExecutePostedVaa";
|
||||
|
||||
export const ExecutorAction = {
|
||||
ExecutePostedVaa: 0,
|
||||
|
@ -130,4 +134,17 @@ export function verifyHeader(
|
|||
return governanceHeader;
|
||||
}
|
||||
|
||||
export function decodeGovernancePayload(data: Buffer): {
|
||||
name: string;
|
||||
args: ExecutePostedVaaArgs;
|
||||
} {
|
||||
const header = decodeHeader(data);
|
||||
switch (header.action) {
|
||||
case "ExecutePostedVaa":
|
||||
return { name: "ExecutePostedVaa", args: decodeExecutePostedVaa(data) };
|
||||
default:
|
||||
throw "Not supported";
|
||||
}
|
||||
}
|
||||
|
||||
export { decodeExecutePostedVaa } from "./ExecutePostedVaa";
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
export * from "./multisig";
|
||||
export * from "./propose";
|
||||
export * from "./governance_payload";
|
||||
export * from "./wormhole";
|
||||
export * from "./multisig_transaction";
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
import { createReadOnlyWormholeProgramInterface } from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole";
|
||||
import { WormholeInstructionCoder } from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole/coder/instruction";
|
||||
import { getPythClusterApiUrl } from "@pythnetwork/client/lib/cluster";
|
||||
import { Connection, TransactionInstruction } from "@solana/web3.js";
|
||||
import { MultisigInstruction, MultisigInstructionProgram } from ".";
|
||||
import { decodeGovernancePayload } from "../governance_payload";
|
||||
import { AnchorAccounts, resolveAccountNames } from "./anchor";
|
||||
|
||||
export class WormholeMultisigInstruction implements MultisigInstruction {
|
||||
readonly program = MultisigInstructionProgram.WormholeBridge;
|
||||
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
|
||||
): WormholeMultisigInstruction {
|
||||
const wormholeProgram = createReadOnlyWormholeProgramInterface(
|
||||
instruction.programId,
|
||||
new Connection(getPythClusterApiUrl("devnet")) // Hack to get a decoder, this connection won't actually be used
|
||||
);
|
||||
|
||||
const deserializedData = (
|
||||
wormholeProgram.coder.instruction as WormholeInstructionCoder
|
||||
).decode(instruction.data);
|
||||
|
||||
if (deserializedData) {
|
||||
let result = new WormholeMultisigInstruction(
|
||||
deserializedData.name,
|
||||
deserializedData.data,
|
||||
resolveAccountNames(
|
||||
wormholeProgram.idl,
|
||||
deserializedData.name,
|
||||
instruction
|
||||
)
|
||||
);
|
||||
|
||||
if (result.name === "postMessage") {
|
||||
try {
|
||||
const decoded = decodeGovernancePayload(result.args.payload);
|
||||
result.args.governanceName = decoded.name;
|
||||
result.args.governanceArgs = decoded.args;
|
||||
} catch {
|
||||
result.args.governanceName = "Unrecognized governance message";
|
||||
result.args.governanceArgs = {};
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return new WormholeMultisigInstruction(
|
||||
"Unrecognized instruction",
|
||||
{},
|
||||
{ named: {}, remaining: instruction.keys }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import { Idl } from "@coral-xyz/anchor";
|
||||
import { AccountMeta, TransactionInstruction } from "@solana/web3.js";
|
||||
|
||||
type NamedAccounts = Record<string, AccountMeta>;
|
||||
type RemainingAccounts = AccountMeta[];
|
||||
export type AnchorAccounts = {
|
||||
named: NamedAccounts;
|
||||
remaining: RemainingAccounts;
|
||||
};
|
||||
|
||||
export function resolveAccountNames(
|
||||
idl: Idl,
|
||||
name: string,
|
||||
instruction: TransactionInstruction
|
||||
): { named: NamedAccounts; remaining: RemainingAccounts } {
|
||||
const ix = idl.instructions.find((ix) => ix.name == name);
|
||||
if (!ix) {
|
||||
throw Error("Instruction name not found");
|
||||
}
|
||||
const named: NamedAccounts = {};
|
||||
const remaining: RemainingAccounts = [];
|
||||
instruction.keys.map((account, idx) => {
|
||||
if (idx < ix.accounts.length) {
|
||||
named[ix.accounts[idx].name] = account;
|
||||
} else {
|
||||
remaining.push(account);
|
||||
}
|
||||
});
|
||||
return { named, remaining };
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
import {
|
||||
getPythProgramKeyForCluster,
|
||||
PythCluster,
|
||||
} from "@pythnetwork/client/lib/cluster";
|
||||
import { PublicKey, TransactionInstruction } from "@solana/web3.js";
|
||||
import { WORMHOLE_ADDRESS } from "../wormhole";
|
||||
import { WormholeMultisigInstruction } from "./WormholeMultisigInstruction";
|
||||
|
||||
export enum MultisigInstructionProgram {
|
||||
PythOracle,
|
||||
WormholeBridge,
|
||||
UnrecognizedProgram,
|
||||
}
|
||||
|
||||
export interface MultisigInstruction {
|
||||
readonly program: MultisigInstructionProgram;
|
||||
}
|
||||
|
||||
export class UnrecognizedProgram implements MultisigInstruction {
|
||||
readonly program = MultisigInstructionProgram.UnrecognizedProgram;
|
||||
readonly instruction: TransactionInstruction;
|
||||
|
||||
constructor(instruction: TransactionInstruction) {
|
||||
this.instruction = instruction;
|
||||
}
|
||||
|
||||
static fromTransactionInstruction(
|
||||
instruction: TransactionInstruction
|
||||
): UnrecognizedProgram {
|
||||
return new UnrecognizedProgram(instruction);
|
||||
}
|
||||
}
|
||||
|
||||
export class PythMultisigInstruction implements MultisigInstruction {
|
||||
readonly program = MultisigInstructionProgram.PythOracle;
|
||||
}
|
||||
|
||||
export class MultisigParser {
|
||||
readonly pythOracleAddress: PublicKey;
|
||||
readonly wormholeBridgeAddress: PublicKey | undefined;
|
||||
|
||||
constructor(cluster: PythCluster) {
|
||||
this.pythOracleAddress = getPythProgramKeyForCluster(cluster);
|
||||
this.wormholeBridgeAddress = WORMHOLE_ADDRESS[cluster];
|
||||
}
|
||||
|
||||
parseInstruction(instruction: TransactionInstruction): MultisigInstruction {
|
||||
if (
|
||||
this.wormholeBridgeAddress &&
|
||||
instruction.programId.equals(this.wormholeBridgeAddress)
|
||||
) {
|
||||
return WormholeMultisigInstruction.fromTransactionInstruction(
|
||||
instruction
|
||||
);
|
||||
} else {
|
||||
return UnrecognizedProgram.fromTransactionInstruction(instruction);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import { PythCluster } from "@pythnetwork/client/lib/cluster";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
export const WORMHOLE_ADDRESS: Record<PythCluster, PublicKey | undefined> = {
|
||||
"mainnet-beta": new PublicKey("worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth"),
|
||||
pythtest: new PublicKey("EUrRARh92Cdc54xrDn6qzaqjA77NRrCcfbr8kPwoTL4z"),
|
||||
devnet: new PublicKey("3u8hJUVTA4jH1wYAyUur7FFZVQ8H635K3tSHHF4ssjQ5"),
|
||||
pythnet: new PublicKey("H3fxXJ86ADW2PNuDDmZJg6mzTtPxkYCpNuQUTgmJ7AjU"),
|
||||
testnet: undefined,
|
||||
};
|
Loading…
Reference in New Issue