UNPKG

@fewcha/aptos

Version:
297 lines (270 loc) 12.6 kB
/* eslint-disable no-console */ import dotenv from "dotenv"; dotenv.config(); import { AptosAccount, FaucetClient, BCS, TxnBuilderTypes, Types, HexString, FungibleAssetClient, Provider, } from "aptos"; import { sha3_256 as sha3Hash } from "@noble/hashes/sha3"; import { FAUCET_URL, NODE_URL, fungibleStore } from "./common"; import assert from "assert"; const { AccountAddress, EntryFunction, MultiSig, MultiSigTransactionPayload, TransactionPayloadMultisig } = TxnBuilderTypes; // Step 0: After publishing the example code with the example code under any `MODULE_ADDR` using CLI or any sdk. const MODULE_ADDR = process.env.MODULE_ADDR; const ASSET_SYMBOL = "MEME"; /** * This code example demonstrates how to use framework mulltisig account module to manage fungible asset with exmaple move code. */ (async () => { const client = new Provider({ fullnodeUrl: NODE_URL, indexerUrl: NODE_URL /* not used */ }); const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL); const fa_client = new FungibleAssetClient(client); console.log(`node url: ${NODE_URL}`); console.log(`faucet url: ${FAUCET_URL}`); // Create and fund 3 accounts that will be the owners of the multisig account where owner1 would be the one that publish the move module later. // :!:>section_1 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); console.log(`owner1: ${owner1.address()}`); console.log(`owner2: ${owner2.address()}`); console.log(`owner3: ${owner3.address()}`); // <:!:section_1 // Step 1: Setup a k-of-n (2-of-3 here) multisig account // =========================================================================================== // :!:>section_2 // Find the next multisig account address. const payload: Types.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; const createMultisigManagedCoin = await client.generateTransaction(owner1.address(), { function: `${MODULE_ADDR}::multisig_managed_coin::initialize`, type_arguments: [], arguments: [ [owner2.address().hex(), owner3.address().hex()], 2, ["description"], [BCS.bcsSerializeStr("The multisig account for Meme")], 0, "meme coin", ASSET_SYMBOL, 8, "http://meme.xyz/favicon.ico", "http://meme.xyz", [true, true, true], ], }); // <:!:section_2 await client.generateSignSubmitWaitForTransaction(owner1, createMultisigManagedCoin.payload, { checkSuccess: true }); assert((await getSignatureThreshold(client, multisigAddress)) == 2); assert((await getNumberOfOwners(client, multisigAddress)) == 3); // Fund the multisig account for gas. await faucetClient.fundAccount(multisigAddress, 100_000_000); // Step 2: Create a multisig transaction to mint coins to an account. // We'll be including the full payload to be stored on chain. // =========================================================================================== // Deterministically calculate the named metadata object address from the creator address and asset symbol. const metadata = getNamedObjectAddress(owner1, ASSET_SYMBOL); console.log(`metadata: ${metadata.toHexString()}`); // Create the mint transaction payload { // :!:>section_3 const recipientsSerializer = new BCS.Serializer(); BCS.serializeVector( [AccountAddress.fromHex(owner2.address()), AccountAddress.fromHex(owner3.address())], recipientsSerializer, ); const mintTxPayload = new MultiSigTransactionPayload( EntryFunction.natural( `${MODULE_ADDR}::managed_fungible_asset`, "mint_to_primary_stores", [], [ BCS.bcsToBytes(metadata), recipientsSerializer.getBytes(), BCS.serializeVectorWithFunc([1_000, 2_000], "serializeU64"), ], ), ); const mintTxExecution = new TransactionPayloadMultisig(new MultiSig(AccountAddress.fromHex(multisigAddress))); // Create the mint multisig tx on chain. const mintTx = await client.generateTransaction(owner2.address(), { function: "0x1::multisig_account::create_transaction", type_arguments: [], arguments: [multisigAddress, BCS.bcsToBytes(mintTxPayload)], }); await client.generateSignSubmitWaitForTransaction(owner2, mintTx.payload, { checkSuccess: true }); // Owner 3 approves. await approve(client, 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, mintTxExecution, { checkSuccess: true }); // <:!:section_3 // Check the primary store balance of owner2 and owner3. assert((await fa_client.getPrimaryBalance(owner2.address(), metadata.toHexString())) === BigInt(1_000)); assert((await fa_client.getPrimaryBalance(owner3.address(), metadata.toHexString())) === BigInt(2_000)); } // Step 3: Create another multisig transaction to freeze accounts but use payload hash instead. // =========================================================================================== // Create the reeze transaction payload { // :!:>section_4 const freezeAccountsSerializer = new BCS.Serializer(); BCS.serializeVector([AccountAddress.fromHex(owner1.address())], freezeAccountsSerializer); // Create freeze tx payload. The last paramter can be set to `false` to unfreeze. const freezeTxPayload = new MultiSigTransactionPayload( EntryFunction.natural( `${MODULE_ADDR}::managed_fungible_asset`, "set_primary_stores_frozen_status", [], [BCS.bcsToBytes(metadata), freezeAccountsSerializer.getBytes(), BCS.bcsSerializeBool(true)], ), ); const multisigTxExecution = new TransactionPayloadMultisig( new MultiSig(AccountAddress.fromHex(multisigAddress), freezeTxPayload), ); const transferTxPayloadHash = sha3Hash.create(); transferTxPayloadHash.update(BCS.bcsToBytes(freezeTxPayload)); 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, { checkSuccess: true }); await approve(client, owner1, multisigAddress, 2); const multisigTxExecution2 = new TransactionPayloadMultisig( new MultiSig(AccountAddress.fromHex(multisigAddress), freezeTxPayload), ); await client.generateSignSubmitWaitForTransaction(owner2, multisigTxExecution2, { checkSuccess: true }); // <:!:section_4 let frozen = await client.view({ function: "0x1::primary_fungible_store::is_frozen", type_arguments: ["0x1::fungible_asset::Metadata"], arguments: [owner1.address().hex(), metadata.toHexString()], }); assert(frozen); } // Step 4: Create another multisig transaction to forcefully transfer fungible assets. // =========================================================================================== { // :!:>section_5 const transferSendersSerializer = new BCS.Serializer(); BCS.serializeVector([AccountAddress.fromHex(owner3.address())], transferSendersSerializer); const transferRecipentsSerializer = new BCS.Serializer(); BCS.serializeVector([AccountAddress.fromHex(owner1.address())], transferRecipentsSerializer); const transferPayload = new MultiSigTransactionPayload( EntryFunction.natural( `${MODULE_ADDR}::managed_fungible_asset`, "transfer_between_primary_stores", [], [ BCS.bcsToBytes(metadata), transferSendersSerializer.getBytes(), transferRecipentsSerializer.getBytes(), BCS.serializeVectorWithFunc([1_000], "serializeU64"), ], ), ); const transferTx = await client.generateTransaction(owner2.address(), { function: "0x1::multisig_account::create_transaction", type_arguments: [], arguments: [multisigAddress, BCS.bcsToBytes(transferPayload)], }); await client.generateSignSubmitWaitForTransaction(owner2, transferTx.payload, { checkSuccess: true }); await approve(client, owner1, multisigAddress, 3); await client.generateSignSubmitWaitForTransaction( owner2, new TransactionPayloadMultisig(new MultiSig(AccountAddress.fromHex(multisigAddress))), { checkSuccess: true }, ); // <:!:section_5 // Check the primary store balance of owner1 and owner3. assert((await fa_client.getPrimaryBalance(owner1.address(), metadata.toHexString())) === BigInt(1_000)); assert((await fa_client.getPrimaryBalance(owner3.address(), metadata.toHexString())) === BigInt(1_000)); } // Step 5: Create another multisig transaction to burn fungible assets. // =========================================================================================== { // :!:>section_6 const burnAccountsSerializer = new BCS.Serializer(); BCS.serializeVector( [ AccountAddress.fromHex(owner1.address()), AccountAddress.fromHex(owner2.address()), AccountAddress.fromHex(owner3.address()), ], burnAccountsSerializer, ); const burnPayload = new MultiSigTransactionPayload( EntryFunction.natural( `${MODULE_ADDR}::managed_fungible_asset`, "burn_from_primary_stores", [], [ BCS.bcsToBytes(metadata), burnAccountsSerializer.getBytes(), BCS.serializeVectorWithFunc([1_000, 1_000, 1_000], "serializeU64"), ], ), ); const burnTx = await client.generateTransaction(owner2.address(), { function: "0x1::multisig_account::create_transaction", type_arguments: [], arguments: [multisigAddress, BCS.bcsToBytes(burnPayload)], }); await client.generateSignSubmitWaitForTransaction(owner2, burnTx.payload, { checkSuccess: true }); await approve(client, owner1, multisigAddress, 4); await client.generateSignSubmitWaitForTransaction( owner2, new TransactionPayloadMultisig(new MultiSig(AccountAddress.fromHex(multisigAddress))), { checkSuccess: true }, ); // <:!:section_6 // Check the primary store balance of owner1, owner2 and owner3. assert((await fa_client.getPrimaryBalance(owner1.address(), metadata.toHexString())) === BigInt(0)); assert((await fa_client.getPrimaryBalance(owner2.address(), metadata.toHexString())) === BigInt(0)); assert((await fa_client.getPrimaryBalance(owner3.address(), metadata.toHexString())) === BigInt(0)); console.log("done."); } })(); const approve = async (client: Provider, owner: AptosAccount, multisigAddress: string, transactionId: number) => { let approveTx = await client.generateTransaction(owner.address(), { function: "0x1::multisig_account::approve_transaction", type_arguments: [], arguments: [multisigAddress, transactionId], }); await client.generateSignSubmitWaitForTransaction(owner, approveTx.payload, { checkSuccess: true }); }; const getNamedObjectAddress = (owner: AptosAccount, seed: string): TxnBuilderTypes.AccountAddress => { const hash = sha3Hash.create(); hash.update(BCS.bcsToBytes(AccountAddress.fromHex(owner.address()))); hash.update(seed); hash.update(new Uint8Array([0xfe])); return AccountAddress.fromHex(Buffer.from(hash.digest()).toString("hex")); }; const getNumberOfOwners = async (client: Provider, 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: Provider, multisigAddress: string): Promise<number> => { const multisigAccountResource = await client.getAccountResource( multisigAddress, "0x1::multisig_account::MultisigAccount", ); return Number((multisigAccountResource.data as any).num_signatures_required); };