add support to execute tx through squads ui (#322)

This commit is contained in:
Daniel Chew 2022-09-30 21:24:16 +08:00 committed by GitHub
parent 8760bfb739
commit 29f53df71c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 97 additions and 54 deletions

View File

@ -14,7 +14,7 @@
"@ledgerhq/hw-transport-node-hid": "^6.27.2",
"@project-serum/anchor": "^0.25.0",
"@solana/web3.js": "^1.53.0",
"@sqds/mesh": "^1.0.4",
"@sqds/mesh": "^1.0.6",
"bs58": "^5.0.0",
"commander": "^9.4.0",
"ethers": "^5.7.0"
@ -2271,9 +2271,9 @@
"integrity": "sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ=="
},
"node_modules/@sqds/mesh": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@sqds/mesh/-/mesh-1.0.4.tgz",
"integrity": "sha512-Mj2rMEkKwkq4a9PlHAWVlCOXczflwcnwlit5W97llVVitC0zT5exIHtgmGSPbYgXlx78tEkq+IvQrnrtGdQRqQ==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@sqds/mesh/-/mesh-1.0.6.tgz",
"integrity": "sha512-z+x1GjixJm8K3uPwaDebTsssU3B71zJzRCkywmtz2ZZoMvoz9w/C4nY+v7v6Wg/9OTbfSDgcX/Hoo/FlphkWvg==",
"dependencies": {
"@project-serum/anchor": "^0.25.0",
"@solana/web3.js": "^1.53.0",
@ -9427,9 +9427,9 @@
}
},
"@sqds/mesh": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@sqds/mesh/-/mesh-1.0.4.tgz",
"integrity": "sha512-Mj2rMEkKwkq4a9PlHAWVlCOXczflwcnwlit5W97llVVitC0zT5exIHtgmGSPbYgXlx78tEkq+IvQrnrtGdQRqQ==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@sqds/mesh/-/mesh-1.0.6.tgz",
"integrity": "sha512-z+x1GjixJm8K3uPwaDebTsssU3B71zJzRCkywmtz2ZZoMvoz9w/C4nY+v7v6Wg/9OTbfSDgcX/Hoo/FlphkWvg==",
"requires": {
"@project-serum/anchor": "^0.25.0",
"@solana/web3.js": "^1.53.0",

View File

@ -43,7 +43,7 @@
"@ledgerhq/hw-transport-node-hid": "^6.27.2",
"@project-serum/anchor": "^0.25.0",
"@solana/web3.js": "^1.53.0",
"@sqds/mesh": "^1.0.4",
"@sqds/mesh": "^1.0.6",
"bs58": "^5.0.0",
"commander": "^9.4.0",
"ethers": "^5.7.0"

View File

@ -110,15 +110,24 @@ program
options.ledger,
new PublicKey(options.vaultAddress)
);
const instructions = [
await setIsActiveIx(
vaultAuthority,
vaultAuthority,
attesterProgramId,
options.active
),
const squadIxs: SquadInstruction[] = [
{
instruction: await setIsActiveIx(
vaultAuthority,
vaultAuthority,
attesterProgramId,
options.active
),
},
];
await addInstructionsToTx(squad, options.ledger, txKey, instructions);
await addInstructionsToTx(
options.cluster,
squad,
options.ledger,
msAccount.publicKey,
txKey,
squadIxs
);
});
program
@ -140,11 +149,6 @@ program
"multisig wallet secret key filepath",
"keys/key.json"
)
.option(
"-m, --message <filepath>",
"multisig message account secret key filepath",
"keys/message.json"
)
.requiredOption("-t, --tx-pda <address>", "transaction PDA")
.requiredOption("-u, --rpc-url <url>", "wormhole RPC URL")
.action((options) => {
@ -155,7 +159,6 @@ program
options.ledgerDerivationAccount,
options.ledgerDerivationChange,
options.wallet,
options.message,
new PublicKey(options.txPda),
options.rpcUrl
);
@ -166,14 +169,14 @@ program
program.parse();
// custom solana cluster type
type Cluster = "devnet" | "mainnet-beta";
type Cluster = "devnet" | "mainnet";
type WormholeNetwork = "TESTNET" | "MAINNET";
// solana cluster mapping to wormhole cluster
const solanaClusterMappingToWormholeNetwork: Record<Cluster, WormholeNetwork> =
{
devnet: "TESTNET",
"mainnet-beta": "MAINNET",
mainnet: "MAINNET",
};
async function getSquadsClient(
@ -224,21 +227,36 @@ async function createTx(
return newTx.publicKey;
}
type SquadInstruction = {
instruction: anchor.web3.TransactionInstruction;
authorityIndex?: number;
authorityBump?: number;
authorityType?: string;
};
/** Adds the given instructions to the squads transaction at `txKey` and activates the transaction (makes it ready for signing). */
async function addInstructionsToTx(
cluster: Cluster,
squad: Squads,
ledger: boolean,
vault: PublicKey,
txKey: PublicKey,
instructions: TransactionInstruction[]
instructions: SquadInstruction[]
) {
for (let i = 0; i < instructions.length; i++) {
console.log(
`Adding instruction ${i}/${instructions.length} to transaction...`
`Adding instruction ${i + 1}/${instructions.length} to transaction...`
);
if (ledger) {
console.log("Please approve the transaction on your ledger device...");
}
await squad.addInstruction(txKey, instructions[i]);
await squad.addInstruction(
txKey,
instructions[i].instruction,
instructions[i].authorityIndex,
instructions[i].authorityBump,
instructions[i].authorityType
);
}
console.log("Activating transaction...");
@ -246,6 +264,16 @@ async function addInstructionsToTx(
console.log("Please approve the transaction on your ledger device...");
await squad.activateTransaction(txKey);
console.log("Transaction created.");
console.log("Approving transaction...");
if (ledger)
console.log("Please approve the transaction on your ledger device...");
await squad.approveTransaction(txKey);
console.log("Transaction approved.");
console.log(
`Tx URL: https://mesh${
cluster === "devnet" ? "-devnet" : ""
}.squads.so/transactions/${vault.toBase58()}/tx/${txKey.toBase58()}`
);
}
async function setIsActiveIx(
@ -277,7 +305,7 @@ async function setIsActiveIx(
const isActiveInt = isActive === true ? 1 : 0;
// first byte is the isActive instruction, second byte is true/false
const data = new Buffer([4, isActiveInt]);
const data = Buffer.from([4, isActiveInt]);
return {
keys: [config, opsOwner, payer],
@ -329,6 +357,22 @@ async function getWormholeMessageIx(
];
}
const getIxAuthority = async (
txPda: anchor.web3.PublicKey,
index: anchor.BN,
programId: anchor.web3.PublicKey
) => {
return anchor.web3.PublicKey.findProgramAddress(
[
anchor.utils.bytes.utf8.encode("squad"),
txPda.toBuffer(),
index.toArrayLike(Buffer, "le", 4),
anchor.utils.bytes.utf8.encode("ix_authority"),
],
programId
);
};
async function createWormholeMsgMultisigTx(
cluster: Cluster,
squad: Squads,
@ -337,7 +381,6 @@ async function createWormholeMsgMultisigTx(
payload: string
) {
const msAccount = await squad.getMultisig(vault);
const emitter = squad.getAuthorityPDA(
msAccount.publicKey,
msAccount.authorityIndex
@ -346,28 +389,41 @@ async function createWormholeMsgMultisigTx(
const txKey = await createTx(squad, ledger, vault);
const message = Keypair.generate();
fs.mkdirSync("keys", { recursive: true });
// save message to Uint8 array keypair file called mesage.json
fs.writeFileSync(
`keys/message-${txKey.toBase58()}.json`,
`[${message.secretKey.toString()}]`
const [messagePDA, messagePdaBump] = await getIxAuthority(
txKey,
new anchor.BN(1),
squad.multisigProgramId
);
console.log(`Message Address: ${message.publicKey.toBase58()}`);
console.log("Creating wormhole instructions...");
const wormholeIxs = await getWormholeMessageIx(
cluster,
emitter,
emitter,
message.publicKey,
messagePDA,
squad.connection,
payload
);
console.log("Wormhole instructions created.");
await addInstructionsToTx(squad, ledger, txKey, wormholeIxs);
const squadIxs: SquadInstruction[] = [
{ instruction: wormholeIxs[0] },
{
instruction: wormholeIxs[1],
authorityIndex: 1,
authorityBump: messagePdaBump,
authorityType: "custom",
},
];
await addInstructionsToTx(
cluster,
squad,
ledger,
msAccount.publicKey,
txKey,
squadIxs
);
}
async function executeMultisigTx(
@ -377,7 +433,6 @@ async function executeMultisigTx(
ledgerDerivationAccount: number | undefined,
ledgerDerivationChange: number | undefined,
walletPath: string,
messagePath: string,
txPDA: PublicKey,
rpcUrl: string
) {
@ -398,13 +453,6 @@ async function executeMultisigTx(
console.log(`Loaded wallet with address: ${wallet.publicKey.toBase58()}`);
}
const message = Keypair.fromSecretKey(
Uint8Array.from(JSON.parse(fs.readFileSync(messagePath, "ascii")))
);
console.log(
`Loaded message account with address: ${message.publicKey.toBase58()}`
);
const squad =
cluster === "devnet" ? Squads.devnet(wallet) : Squads.mainnet(wallet);
const msAccount = await squad.getMultisig(vault);
@ -418,11 +466,6 @@ async function executeMultisigTx(
txPDA,
wallet.publicKey
);
executeIx.keys.forEach((key) => {
if (key.pubkey.equals(message.publicKey)) {
key.isSigner = true;
}
});
// airdrop 0.1 SOL to emitter if on devnet
if (cluster === "devnet") {
@ -457,7 +500,7 @@ async function executeMultisigTx(
console.log("Sending transaction...");
if (ledger)
console.log("Please approve the transaction on your ledger device...");
const signature = await provider.sendAndConfirm(executeTx, [message]);
const signature = await provider.sendAndConfirm(executeTx);
console.log(
`Executed tx: https://explorer.solana.com/tx/${signature}${
@ -497,8 +540,8 @@ async function executeMultisigTx(
);
const { vaaBytes } = await response.json();
console.log(`VAA (Base64): ${vaaBytes}`);
const parsedVaa = await parse(vaaBytes);
console.log(`VAA (Hex): ${Buffer.from(vaaBytes).toString("hex")}`);
const parsedVaa = await parse(vaaBytes);
console.log(`Emitter chain: ${parsedVaa.emitter_chain}`);
console.log(`Nonce: ${parsedVaa.nonce}`);
console.log(`Payload: ${Buffer.from(parsedVaa.payload).toString("hex")}`);