UNPKG

@fewcha/aptos

Version:
216 lines (195 loc) 9.8 kB
/* eslint-disable no-console */ import dotenv from "dotenv"; dotenv.config(); import { AptosClient, AptosAccount, FaucetClient, BCS, TxnBuilderTypes } from "aptos"; import { sha3_256 as sha3Hash } from "@noble/hashes/sha3"; import { aptosCoinStore, FAUCET_URL, NODE_URL } from "./common"; import assert from "assert"; import * as Gen from "../../src/generated"; const { AccountAddress, EntryFunction, MultiSig, MultiSigTransactionPayload, TransactionPayloadMultisig } = TxnBuilderTypes; /** * This code example demonstrates the new multisig account module and transaction execution flow. */ (async () => { const client = new AptosClient(NODE_URL); const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL); // Create and fund 3 accounts that will be the owners of the multisig account. const owner1 = new AptosAccount(); const owner2 = new AptosAccount(); const owner3 = new AptosAccount(); await faucetClient.fundAccount(owner1.address(), 100_000_000); await faucetClient.fundAccount(owner2.address(), 100_000_000); await faucetClient.fundAccount(owner3.address(), 100_000_000); // Step 1: Setup a 2-of-3 multisig account // =========================================================================================== // Get the next multisig account address. This will be the same as the account address of the multisig account we'll // be creating. const payload: Gen.ViewRequest = { function: "0x1::multisig_account::get_next_multisig_account_address", type_arguments: [], arguments: [owner1.address().hex()], }; const multisigAddress = (await client.view(payload))[0] as string; // Create the multisig account with 3 owners and a signature threshold of 2. const createMultisig = await client.generateTransaction(owner1.address(), { function: "0x1::multisig_account::create_with_owners", type_arguments: [], arguments: [[owner2.address().hex(), owner3.address().hex()], 2, ["Shaka"], [BCS.bcsSerializeStr("Bruh")]], }); await client.generateSignSubmitWaitForTransaction(owner1, createMultisig.payload); assert((await getSignatureThreshold(client, multisigAddress)) == 2); assert((await getNumberOfOwners(client, multisigAddress)) == 3); // Fund the multisig account for transfers. await faucetClient.fundAccount(multisigAddress, 100_000_000); // Step 2: Create a multisig transaction to send 1_000_000 coins to an account. // We'll be including the full payload to be stored on chain. // =========================================================================================== const recipient = new AptosAccount(); const transferTxPayload = new MultiSigTransactionPayload( EntryFunction.natural( "0x1::aptos_account", "transfer", [], [BCS.bcsToBytes(AccountAddress.fromHex(recipient.address())), BCS.bcsSerializeUint64(1_000_000)], ), ); const multisigTxExecution = new TransactionPayloadMultisig( new MultiSig(AccountAddress.fromHex(multisigAddress), transferTxPayload), ); // We can simulate the transaction to see if it will succeed without having to create it on chain. const [simulationResp] = await client.simulateTransaction( owner2, await client.generateRawTransaction(owner2.address(), multisigTxExecution), ); assert(simulationResp.success); // Create the multisig tx on chain. const createMultisigTx = await client.generateTransaction(owner2.address(), { function: "0x1::multisig_account::create_transaction", type_arguments: [], arguments: [multisigAddress, BCS.bcsToBytes(transferTxPayload)], }); await client.generateSignSubmitWaitForTransaction(owner2, createMultisigTx.payload); // Owner 1 rejects but owner 3 approves. await rejectAndApprove(client, owner1, owner3, multisigAddress, 1); // Owner 2 can now execute the transactions as it already has 2 approvals (from owners 2 and 3). await client.generateSignSubmitWaitForTransaction(owner2, multisigTxExecution); let accountResource = await client.getAccountResource(recipient.address(), aptosCoinStore); let balance = parseInt((accountResource?.data as any).coin.value); assert(balance === 1_000_000); // Step 3: Create another multisig transaction to send 1_000_000 coins but use payload hash instead. // =========================================================================================== const transferTxPayloadHash = sha3Hash.create(); transferTxPayloadHash.update(BCS.bcsToBytes(transferTxPayload)); const createMultisigTxWithHash = await client.generateTransaction(owner2.address(), { function: "0x1::multisig_account::create_transaction_with_hash", type_arguments: [], arguments: [multisigAddress, transferTxPayloadHash.digest()], }); await client.generateSignSubmitWaitForTransaction(owner2, createMultisigTxWithHash.payload); await rejectAndApprove(client, owner1, owner3, multisigAddress, 2); const multisigTxExecution2 = new TransactionPayloadMultisig( new MultiSig(AccountAddress.fromHex(multisigAddress), transferTxPayload), ); await client.generateSignSubmitWaitForTransaction(owner2, multisigTxExecution2); accountResource = await client.getAccountResource(recipient.address(), aptosCoinStore); balance = parseInt((accountResource?.data as any).coin.value); assert(balance === 2_000_000); // Step 4: Create 2 multisig transactions: one to add a new owner and another one to remove it. // =========================================================================================== const owner_4 = new AptosAccount(); const addOwnerPayload = new MultiSigTransactionPayload( EntryFunction.natural( "0x1::multisig_account", "add_owner", [], [BCS.bcsToBytes(AccountAddress.fromHex(owner_4.address()))], ), ); const addOwnerTx = await client.generateTransaction(owner2.address(), { function: "0x1::multisig_account::create_transaction", type_arguments: [], arguments: [multisigAddress, BCS.bcsToBytes(addOwnerPayload)], }); await client.generateSignSubmitWaitForTransaction(owner2, addOwnerTx.payload); await rejectAndApprove(client, owner1, owner3, multisigAddress, 3); await client.generateSignSubmitWaitForTransaction( owner2, new TransactionPayloadMultisig(new MultiSig(AccountAddress.fromHex(multisigAddress))), ); // The multisig account should now have 4 owners. assert((await getNumberOfOwners(client, multisigAddress)) == 4); const removeOwnerPayload = new MultiSigTransactionPayload( EntryFunction.natural( "0x1::multisig_account", "remove_owner", [], [BCS.bcsToBytes(AccountAddress.fromHex(owner_4.address()))], ), ); const removeOwnerTx = await client.generateTransaction(owner2.address(), { function: "0x1::multisig_account::create_transaction", type_arguments: [], arguments: [multisigAddress, BCS.bcsToBytes(removeOwnerPayload)], }); await client.generateSignSubmitWaitForTransaction(owner2, removeOwnerTx.payload); await rejectAndApprove(client, owner1, owner3, multisigAddress, 4); await client.generateSignSubmitWaitForTransaction( owner2, new TransactionPayloadMultisig(new MultiSig(AccountAddress.fromHex(multisigAddress))), ); // The multisig account should now have 3 owners. assert((await getNumberOfOwners(client, multisigAddress)) == 3); // Step 5: Create a multisig transactions to change the signature threshold to 3-of-3. // =========================================================================================== const changeSigThresholdPayload = new MultiSigTransactionPayload( EntryFunction.natural("0x1::multisig_account", "update_signatures_required", [], [BCS.bcsSerializeUint64(3)]), ); const changeSigThresholdTx = await client.generateTransaction(owner2.address(), { function: "0x1::multisig_account::create_transaction", type_arguments: [], arguments: [multisigAddress, BCS.bcsToBytes(changeSigThresholdPayload)], }); await client.generateSignSubmitWaitForTransaction(owner2, changeSigThresholdTx.payload); await rejectAndApprove(client, owner1, owner3, multisigAddress, 5); await client.generateSignSubmitWaitForTransaction( owner2, new TransactionPayloadMultisig(new MultiSig(AccountAddress.fromHex(multisigAddress))), ); // The multisig account should now be 3-of-3. assert((await getSignatureThreshold(client, multisigAddress)) == 3); })(); const rejectAndApprove = async ( client: AptosClient, owner1: AptosAccount, owner2: AptosAccount, multisigAddress: string, transactionId: number, ) => { let rejectTx = await client.generateTransaction(owner1.address(), { function: "0x1::multisig_account::reject_transaction", type_arguments: [], arguments: [multisigAddress, transactionId], }); await client.generateSignSubmitWaitForTransaction(owner1, rejectTx.payload); let approveTx = await client.generateTransaction(owner2.address(), { function: "0x1::multisig_account::approve_transaction", type_arguments: [], arguments: [multisigAddress, transactionId], }); await client.generateSignSubmitWaitForTransaction(owner2, approveTx.payload); }; const getNumberOfOwners = async (client: AptosClient, multisigAddress: string): Promise<number> => { const multisigAccountResource = await client.getAccountResource( multisigAddress, "0x1::multisig_account::MultisigAccount", ); return Number((multisigAccountResource.data as any).owners.length); }; const getSignatureThreshold = async (client: AptosClient, multisigAddress: string): Promise<number> => { const multisigAccountResource = await client.getAccountResource( multisigAddress, "0x1::multisig_account::MultisigAccount", ); return Number((multisigAccountResource.data as any).num_signatures_required); };