Skip to content

Commit 29f53df

Browse files
authored
add support to execute tx through squads ui (#322)
1 parent 8760bfb commit 29f53df

File tree

3 files changed

+97
-54
lines changed

3 files changed

+97
-54
lines changed

third_party/pyth/multisig-wh-message-builder/package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

third_party/pyth/multisig-wh-message-builder/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"@ledgerhq/hw-transport-node-hid": "^6.27.2",
4444
"@project-serum/anchor": "^0.25.0",
4545
"@solana/web3.js": "^1.53.0",
46-
"@sqds/mesh": "^1.0.4",
46+
"@sqds/mesh": "^1.0.6",
4747
"bs58": "^5.0.0",
4848
"commander": "^9.4.0",
4949
"ethers": "^5.7.0"

third_party/pyth/multisig-wh-message-builder/src/index.ts

Lines changed: 89 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,24 @@ program
110110
options.ledger,
111111
new PublicKey(options.vaultAddress)
112112
);
113-
const instructions = [
114-
await setIsActiveIx(
115-
vaultAuthority,
116-
vaultAuthority,
117-
attesterProgramId,
118-
options.active
119-
),
113+
const squadIxs: SquadInstruction[] = [
114+
{
115+
instruction: await setIsActiveIx(
116+
vaultAuthority,
117+
vaultAuthority,
118+
attesterProgramId,
119+
options.active
120+
),
121+
},
120122
];
121-
await addInstructionsToTx(squad, options.ledger, txKey, instructions);
123+
await addInstructionsToTx(
124+
options.cluster,
125+
squad,
126+
options.ledger,
127+
msAccount.publicKey,
128+
txKey,
129+
squadIxs
130+
);
122131
});
123132

124133
program
@@ -140,11 +149,6 @@ program
140149
"multisig wallet secret key filepath",
141150
"keys/key.json"
142151
)
143-
.option(
144-
"-m, --message <filepath>",
145-
"multisig message account secret key filepath",
146-
"keys/message.json"
147-
)
148152
.requiredOption("-t, --tx-pda <address>", "transaction PDA")
149153
.requiredOption("-u, --rpc-url <url>", "wormhole RPC URL")
150154
.action((options) => {
@@ -155,7 +159,6 @@ program
155159
options.ledgerDerivationAccount,
156160
options.ledgerDerivationChange,
157161
options.wallet,
158-
options.message,
159162
new PublicKey(options.txPda),
160163
options.rpcUrl
161164
);
@@ -166,14 +169,14 @@ program
166169
program.parse();
167170

168171
// custom solana cluster type
169-
type Cluster = "devnet" | "mainnet-beta";
172+
type Cluster = "devnet" | "mainnet";
170173
type WormholeNetwork = "TESTNET" | "MAINNET";
171174

172175
// solana cluster mapping to wormhole cluster
173176
const solanaClusterMappingToWormholeNetwork: Record<Cluster, WormholeNetwork> =
174177
{
175178
devnet: "TESTNET",
176-
"mainnet-beta": "MAINNET",
179+
mainnet: "MAINNET",
177180
};
178181

179182
async function getSquadsClient(
@@ -224,28 +227,53 @@ async function createTx(
224227
return newTx.publicKey;
225228
}
226229

230+
type SquadInstruction = {
231+
instruction: anchor.web3.TransactionInstruction;
232+
authorityIndex?: number;
233+
authorityBump?: number;
234+
authorityType?: string;
235+
};
236+
227237
/** Adds the given instructions to the squads transaction at `txKey` and activates the transaction (makes it ready for signing). */
228238
async function addInstructionsToTx(
239+
cluster: Cluster,
229240
squad: Squads,
230241
ledger: boolean,
242+
vault: PublicKey,
231243
txKey: PublicKey,
232-
instructions: TransactionInstruction[]
244+
instructions: SquadInstruction[]
233245
) {
234246
for (let i = 0; i < instructions.length; i++) {
235247
console.log(
236-
`Adding instruction ${i}/${instructions.length} to transaction...`
248+
`Adding instruction ${i + 1}/${instructions.length} to transaction...`
237249
);
238250
if (ledger) {
239251
console.log("Please approve the transaction on your ledger device...");
240252
}
241-
await squad.addInstruction(txKey, instructions[i]);
253+
await squad.addInstruction(
254+
txKey,
255+
instructions[i].instruction,
256+
instructions[i].authorityIndex,
257+
instructions[i].authorityBump,
258+
instructions[i].authorityType
259+
);
242260
}
243261

244262
console.log("Activating transaction...");
245263
if (ledger)
246264
console.log("Please approve the transaction on your ledger device...");
247265
await squad.activateTransaction(txKey);
248266
console.log("Transaction created.");
267+
console.log("Approving transaction...");
268+
if (ledger)
269+
console.log("Please approve the transaction on your ledger device...");
270+
await squad.approveTransaction(txKey);
271+
console.log("Transaction approved.");
272+
console.log(
273+
`Tx URL: https://mesh${
274+
cluster === "devnet" ? "-devnet" : ""
275+
}.squads.so/transactions/${vault.toBase58()}/tx/${txKey.toBase58()}`
276+
);
249277
}
250278

251279
async function setIsActiveIx(
@@ -277,7 +305,7 @@ async function setIsActiveIx(
277305

278306
const isActiveInt = isActive === true ? 1 : 0;
279307
// first byte is the isActive instruction, second byte is true/false
280-
const data = new Buffer([4, isActiveInt]);
308+
const data = Buffer.from([4, isActiveInt]);
281309

282310
return {
283311
keys: [config, opsOwner, payer],
@@ -329,6 +357,22 @@ async function getWormholeMessageIx(
329357
];
330358
}
331359

360+
const getIxAuthority = async (
361+
txPda: anchor.web3.PublicKey,
362+
index: anchor.BN,
363+
programId: anchor.web3.PublicKey
364+
) => {
365+
return anchor.web3.PublicKey.findProgramAddress(
366+
[
367+
anchor.utils.bytes.utf8.encode("squad"),
368+
txPda.toBuffer(),
369+
index.toArrayLike(Buffer, "le", 4),
370+
anchor.utils.bytes.utf8.encode("ix_authority"),
371+
],
372+
programId
373+
);
374+
};
375+
332376
async function createWormholeMsgMultisigTx(
333377
cluster: Cluster,
334378
squad: Squads,
@@ -337,7 +381,6 @@ async function createWormholeMsgMultisigTx(
337381
payload: string
338382
) {
339383
const msAccount = await squad.getMultisig(vault);
340-
341384
const emitter = squad.getAuthorityPDA(
342385
msAccount.publicKey,
343386
msAccount.authorityIndex
@@ -346,28 +389,41 @@ async function createWormholeMsgMultisigTx(
346389

347390
const txKey = await createTx(squad, ledger, vault);
348391

349-
const message = Keypair.generate();
350-
351-
fs.mkdirSync("keys", { recursive: true });
352-
// save message to Uint8 array keypair file called mesage.json
353-
fs.writeFileSync(
354-
`keys/message-${txKey.toBase58()}.json`,
355-
`[${message.secretKey.toString()}]`
392+
const [messagePDA, messagePdaBump] = await getIxAuthority(
393+
txKey,
394+
new anchor.BN(1),
395+
squad.multisigProgramId
356396
);
357-
console.log(`Message Address: ${message.publicKey.toBase58()}`);
358397

359398
console.log("Creating wormhole instructions...");
360399
const wormholeIxs = await getWormholeMessageIx(
361400
cluster,
362401
emitter,
363402
emitter,
364-
message.publicKey,
403+
messagePDA,
365404
squad.connection,
366405
payload
367406
);
368407
console.log("Wormhole instructions created.");
369408

370-
await addInstructionsToTx(squad, ledger, txKey, wormholeIxs);
409+
const squadIxs: SquadInstruction[] = [
410+
{ instruction: wormholeIxs[0] },
411+
{
412+
instruction: wormholeIxs[1],
413+
authorityIndex: 1,
414+
authorityBump: messagePdaBump,
415+
authorityType: "custom",
416+
},
417+
];
418+
419+
await addInstructionsToTx(
420+
cluster,
421+
squad,
422+
ledger,
423+
msAccount.publicKey,
424+
txKey,
425+
squadIxs
426+
);
371427
}
372428

373429
async function executeMultisigTx(
@@ -377,7 +433,6 @@ async function executeMultisigTx(
377433
ledgerDerivationAccount: number | undefined,
378434
ledgerDerivationChange: number | undefined,
379435
walletPath: string,
380-
messagePath: string,
381436
txPDA: PublicKey,
382437
rpcUrl: string
383438
) {
@@ -398,13 +453,6 @@ async function executeMultisigTx(
398453
console.log(`Loaded wallet with address: ${wallet.publicKey.toBase58()}`);
399454
}
400455

401-
const message = Keypair.fromSecretKey(
402-
Uint8Array.from(JSON.parse(fs.readFileSync(messagePath, "ascii")))
403-
);
404-
console.log(
405-
`Loaded message account with address: ${message.publicKey.toBase58()}`
406-
);
407-
408456
const squad =
409457
cluster === "devnet" ? Squads.devnet(wallet) : Squads.mainnet(wallet);
410458
const msAccount = await squad.getMultisig(vault);
@@ -418,11 +466,6 @@ async function executeMultisigTx(
418466
txPDA,
419467
wallet.publicKey
420468
);
421-
executeIx.keys.forEach((key) => {
422-
if (key.pubkey.equals(message.publicKey)) {
423-
key.isSigner = true;
424-
}
425-
});
426469

427470
// airdrop 0.1 SOL to emitter if on devnet
428471
if (cluster === "devnet") {
@@ -457,7 +500,7 @@ async function executeMultisigTx(
457500
console.log("Sending transaction...");
458501
if (ledger)
459502
console.log("Please approve the transaction on your ledger device...");
460-
const signature = await provider.sendAndConfirm(executeTx, [message]);
503+
const signature = await provider.sendAndConfirm(executeTx);
461504

462505
console.log(
463506
`Executed tx: https://explorer.solana.com/tx/${signature}${
@@ -497,8 +540,8 @@ async function executeMultisigTx(
497540
);
498541
const { vaaBytes } = await response.json();
499542
console.log(`VAA (Base64): ${vaaBytes}`);
500-
const parsedVaa = await parse(vaaBytes);
501543
console.log(`VAA (Hex): ${Buffer.from(vaaBytes).toString("hex")}`);
544+
const parsedVaa = await parse(vaaBytes);
502545
console.log(`Emitter chain: ${parsedVaa.emitter_chain}`);
503546
console.log(`Nonce: ${parsedVaa.nonce}`);
504547
console.log(`Payload: ${Buffer.from(parsedVaa.payload).toString("hex")}`);

0 commit comments

Comments
 (0)