From 34e1f03ff48facf10631eccc74b8dd8c62e36c5c Mon Sep 17 00:00:00 2001 From: runtianz Date: Wed, 10 Jul 2024 17:42:54 -0700 Subject: [PATCH 1/9] import aptos-intent --- package.json | 1 + src/api/transactionSubmission/build.ts | 27 ++++++- src/bcs/serializable/movePrimitives.ts | 2 +- src/transactions/index.ts | 1 + src/transactions/script-composer/index.ts | 79 +++++++++++++++++++ .../transactionBuilder/remoteAbi.ts | 28 +++++++ src/transactions/types.ts | 11 +++ .../transaction/transactionSubmission.test.ts | 64 +++++++++++++++ 8 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 src/transactions/script-composer/index.ts diff --git a/package.json b/package.json index 04f982a1b..613681540 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@noble/hashes": "^1.4.0", "@scure/bip32": "^1.4.0", "@scure/bip39": "^1.3.0", + "@wgb5445/aptos-intent-npm": "^0.1.7", "eventemitter3": "^5.0.1", "form-data": "^4.0.0", "js-base64": "^3.7.7", diff --git a/src/api/transactionSubmission/build.ts b/src/api/transactionSubmission/build.ts index e56fbbce0..cdf51d7b6 100644 --- a/src/api/transactionSubmission/build.ts +++ b/src/api/transactionSubmission/build.ts @@ -3,10 +3,18 @@ import { AccountAddressInput } from "../../core"; import { generateTransaction } from "../../internal/transactionSubmission"; -import { InputGenerateTransactionPayloadData, InputGenerateTransactionOptions } from "../../transactions"; +import { + InputGenerateTransactionPayloadData, + InputGenerateTransactionOptions, + AptosScriptComposer, + TransactionPayloadScript, + generateRawTransaction, +} from "../../transactions"; import { MultiAgentTransaction } from "../../transactions/instances/multiAgentTransaction"; import { SimpleTransaction } from "../../transactions/instances/simpleTransaction"; import { AptosConfig } from "../aptosConfig"; +import { singleSignerED25519 } from "../../../tests/unit/helper"; +import { Deserializer } from "../../bcs"; /** * A class to handle all `Build` transaction operations. @@ -93,6 +101,23 @@ export class Build { return generateTransaction({ aptosConfig: this.config, ...args }); } + async script_composer(args: { + sender: AccountAddressInput; + builder: (builder: AptosScriptComposer) => Promise; + options?: InputGenerateTransactionOptions; + withFeePayer?: boolean; + }): Promise { + let builder = new AptosScriptComposer(this.config); + builder = await args.builder(builder); + const bytes = builder.build(); + let raw_txn = await generateRawTransaction({ + aptosConfig: this.config, + payload: TransactionPayloadScript.load(new Deserializer(bytes)), + ...args, + }); + return new SimpleTransaction(raw_txn); + } + /** * Build a multi-agent transaction that allows multiple signers to authorize a transaction. * diff --git a/src/bcs/serializable/movePrimitives.ts b/src/bcs/serializable/movePrimitives.ts index 7980925d9..35a175094 100644 --- a/src/bcs/serializable/movePrimitives.ts +++ b/src/bcs/serializable/movePrimitives.ts @@ -12,7 +12,7 @@ import { import { Deserializer } from "../deserializer"; import { Serializable, Serializer, ensureBoolean, validateNumberInRange } from "../serializer"; import { TransactionArgument } from "../../transactions/instances/transactionArgument"; -import { AnyNumber, Uint16, Uint32, Uint8, ScriptTransactionArgumentVariants } from "../../types"; +import { AnyNumber, Uint16, Uint32, Uint8, ScriptTransactionArgumentVariants ,HexInput } from "../../types"; /** * Represents a boolean value that can be serialized and deserialized. diff --git a/src/transactions/index.ts b/src/transactions/index.ts index b8da34334..0d9bfa892 100644 --- a/src/transactions/index.ts +++ b/src/transactions/index.ts @@ -7,3 +7,4 @@ export * from "./transactionBuilder"; export * from "./typeTag"; export * from "./typeTag/parser"; export * from "./types"; +export * from "./script-composer"; diff --git a/src/transactions/script-composer/index.ts b/src/transactions/script-composer/index.ts new file mode 100644 index 000000000..7a2ce973a --- /dev/null +++ b/src/transactions/script-composer/index.ts @@ -0,0 +1,79 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +import { CallArgument, TransactionComposer, initSync, create_wasm} from "@wgb5445/aptos-intent-npm"; +import { AptosApiType, Network } from "../../utils"; +import { AptosConfig } from "../../api"; +import { + EntryFunctionArgumentTypes, + FunctionABI, + InputBatchedFunctionData, + SimpleEntryFunctionArgumentTypes, +} from "../types"; +import { convertArgument, fetchMoveFunctionAbi, getFunctionParts, standardizeTypeTags } from "../transactionBuilder"; +import { TypeTag } from "../typeTag"; +let wasm = null; + +(async ()=>{ + wasm = initSync(await create_wasm()); +})(); + +function convert_batch_argument( + argument: CallArgument | EntryFunctionArgumentTypes | SimpleEntryFunctionArgumentTypes, + functionName: string, + functionAbi: FunctionABI, + position: number, + genericTypeParams: Array, +): CallArgument { + if (argument instanceof CallArgument) { + return argument; + } else { + return CallArgument.new_bytes( + convertArgument(functionName, functionAbi, argument, position, genericTypeParams).bcsToBytes(), + ); + } +} + +export class AptosScriptComposer { + private builder: TransactionComposer; + private config: AptosConfig; + + constructor(aptos_config: AptosConfig) { + this.builder = TransactionComposer.single_signer(); + this.config = aptos_config; + } + + async add_batched_calls(input: InputBatchedFunctionData): Promise { + const { moduleAddress, moduleName, functionName } = getFunctionParts(input.function); + const node_url = this.config.getRequestUrl(AptosApiType.FULLNODE); + await this.builder.load_module(node_url, moduleAddress + "::" + moduleName); + if(input.typeArguments != undefined) { + for (const type_tag of input.typeArguments) { + await this.builder.load_type_tag(node_url, type_tag.toString()); + } + } + const typeArguments = standardizeTypeTags(input.typeArguments); + const functionAbi = await fetchMoveFunctionAbi(moduleAddress, moduleName, functionName, this.config); + // Check the type argument count against the ABI + if (typeArguments.length !== functionAbi.typeParameters.length) { + throw new Error( + `Type argument count mismatch, expected ${functionAbi.typeParameters.length}, received ${typeArguments.length}`, + ); + } + + const functionArguments: CallArgument[] = input.functionArguments.map((arg, i) => + convert_batch_argument(arg, functionName, functionAbi, i, typeArguments), + ); + + return this.builder.add_batched_call( + `${moduleAddress}::${moduleName}`, + functionName, + typeArguments.map((arg) => arg.toString()), + functionArguments, + ); + } + + build(): Uint8Array { + return this.builder.generate_batched_calls(true); + } +} diff --git a/src/transactions/transactionBuilder/remoteAbi.ts b/src/transactions/transactionBuilder/remoteAbi.ts index 92707f66d..5021efc07 100644 --- a/src/transactions/transactionBuilder/remoteAbi.ts +++ b/src/transactions/transactionBuilder/remoteAbi.ts @@ -92,6 +92,34 @@ export async function fetchFunctionAbi( return undefined; } +/** + * Fetches a function ABI from the on-chain module ABI. It doesn't validate whether it's a view or entry function. + * @param moduleAddress + * @param moduleName + * @param functionName + * @param aptosConfig + */ +export async function fetchMoveFunctionAbi( + moduleAddress: string, + moduleName: string, + functionName: string, + aptosConfig: AptosConfig, +): Promise { + const functionAbi = await fetchFunctionAbi(moduleAddress, moduleName, functionName, aptosConfig); + if (!functionAbi) { + throw new Error(`Could not find entry function ABI for '${moduleAddress}::${moduleName}::${functionName}'`); + } + const params: TypeTag[] = []; + for (let i = 0; i < functionAbi.params.length; i += 1) { + params.push(parseTypeTag(functionAbi.params[i], { allowGenerics: true })); + } + + return { + typeParameters: functionAbi.generic_type_params, + parameters: params, + }; +} + /** * Fetches the ABI for an entry function from the specified module address. * This function validates if the ABI corresponds to an entry function and retrieves its parameters. diff --git a/src/transactions/types.ts b/src/transactions/types.ts index 5fdd85b41..4e0fe8b28 100644 --- a/src/transactions/types.ts +++ b/src/transactions/types.ts @@ -21,6 +21,7 @@ import { AccountAuthenticator } from "./authenticator/account"; import { SimpleTransaction } from "./instances/simpleTransaction"; import { MultiAgentTransaction } from "./instances/multiAgentTransaction"; import { Serialized } from "../bcs"; +import { CallArgument } from "@wgb5445/aptos-intent-npm"; /** * Entry function arguments for building a raw transaction using remote ABI, supporting various data types including primitives and arrays. @@ -164,6 +165,16 @@ export type InputMultiSigDataWithABI = { * Combines input function data with Aptos configuration for remote ABI interactions. */ export type InputEntryFunctionDataWithRemoteABI = InputEntryFunctionData & { aptosConfig: AptosConfig }; + +/** + * The data needed to generate a batched function payload + */ +export type InputBatchedFunctionData = { + function: MoveFunctionId; + typeArguments?: Array; + functionArguments: Array; +}; + /** * The data needed to generate a Multi Sig payload */ diff --git a/tests/e2e/transaction/transactionSubmission.test.ts b/tests/e2e/transaction/transactionSubmission.test.ts index cbea950af..8936c9cd0 100644 --- a/tests/e2e/transaction/transactionSubmission.test.ts +++ b/tests/e2e/transaction/transactionSubmission.test.ts @@ -1,6 +1,7 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 +import { CallArgument } from "@wgb5445/aptos-intent-npm"; import { Account, U64, @@ -12,6 +13,11 @@ import { TransactionPayloadEntryFunction, Bool, MoveString, + AptosScriptComposer, + TransactionPayloadScript, + generateRawTransaction, + SimpleTransaction, + Network, } from "../../../src"; import { MAX_U64_BIG_INT } from "../../../src/bcs/consts"; import { longTestTimeout } from "../../unit/helper"; @@ -61,6 +67,64 @@ describe("transaction submission", () => { expect(response.signature?.type).toBe("single_sender"); }); + test("with batch payload", async () => { + const builder = new AptosScriptComposer(aptos.config); + await builder.add_batched_calls({ + function: `${contractPublisherAccount.accountAddress}::transfer::transfer`, + functionArguments: [CallArgument.new_signer(0), 1, receiverAccounts[0].accountAddress], + }); + const bytes = builder.build(); + const transaction = await generateRawTransaction({ + aptosConfig: aptos.config, + sender: singleSignerED25519SenderAccount.accountAddress, + payload: TransactionPayloadScript.load(new Deserializer(bytes)), + }); + const response = await aptos.signAndSubmitTransaction({ + signer: singleSignerED25519SenderAccount, + transaction: new SimpleTransaction(transaction), + }); + + await aptos.waitForTransaction({ + transactionHash: response.hash, + }); + + expect(response.signature?.type).toBe("single_sender"); + }); + test.only("with batch withdraw payload", async () => { + const transaction = await aptos.transaction.build.script_composer({ + sender: singleSignerED25519SenderAccount.accountAddress, + builder: async (builder) => { + let return_1 = await builder.add_batched_calls({ + function: `0x1::coin::withdraw`, + functionArguments: [CallArgument.new_signer(0), 1], + typeArguments: ["0x1::aptos_coin::AptosCoin"] + }); + + let return_2 = await builder.add_batched_calls({ + function: `0x1::coin::coin_to_fungible_asset`, + functionArguments: [return_1[0]], + typeArguments: ["0x1::aptos_coin::AptosCoin"] + }); + + await builder.add_batched_calls({ + function: `0x1::primary_fungible_store::deposit`, + functionArguments: [singleSignerED25519SenderAccount.accountAddress, return_2[0]], + typeArguments: [] + }); + return builder; + } + }); + const response = await aptos.signAndSubmitTransaction({ + signer: singleSignerED25519SenderAccount, + transaction: transaction, + }); + + await aptos.waitForTransaction({ + transactionHash: response.hash, + }); + + expect(response.signature?.type).toBe("single_sender"); + }); test("with entry function payload", async () => { const transaction = await aptos.transaction.build.simple({ sender: singleSignerED25519SenderAccount.accountAddress, From c34fd0f035964f2cb9eac17fa85dcbe644662819 Mon Sep 17 00:00:00 2001 From: runtianz Date: Tue, 5 Nov 2024 10:57:58 -0800 Subject: [PATCH 2/9] Fix some lints --- src/api/transactionSubmission/build.ts | 2 +- src/bcs/serializable/movePrimitives.ts | 2 +- src/transactions/script-composer/index.ts | 26 +++++++++-------- src/transactions/types.ts | 2 +- .../transaction/transactionSubmission.test.ts | 29 +++++++++---------- 5 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/api/transactionSubmission/build.ts b/src/api/transactionSubmission/build.ts index cdf51d7b6..b4eaa71c1 100644 --- a/src/api/transactionSubmission/build.ts +++ b/src/api/transactionSubmission/build.ts @@ -110,7 +110,7 @@ export class Build { let builder = new AptosScriptComposer(this.config); builder = await args.builder(builder); const bytes = builder.build(); - let raw_txn = await generateRawTransaction({ + const raw_txn = await generateRawTransaction({ aptosConfig: this.config, payload: TransactionPayloadScript.load(new Deserializer(bytes)), ...args, diff --git a/src/bcs/serializable/movePrimitives.ts b/src/bcs/serializable/movePrimitives.ts index 35a175094..7980925d9 100644 --- a/src/bcs/serializable/movePrimitives.ts +++ b/src/bcs/serializable/movePrimitives.ts @@ -12,7 +12,7 @@ import { import { Deserializer } from "../deserializer"; import { Serializable, Serializer, ensureBoolean, validateNumberInRange } from "../serializer"; import { TransactionArgument } from "../../transactions/instances/transactionArgument"; -import { AnyNumber, Uint16, Uint32, Uint8, ScriptTransactionArgumentVariants ,HexInput } from "../../types"; +import { AnyNumber, Uint16, Uint32, Uint8, ScriptTransactionArgumentVariants } from "../../types"; /** * Represents a boolean value that can be serialized and deserialized. diff --git a/src/transactions/script-composer/index.ts b/src/transactions/script-composer/index.ts index 7a2ce973a..40d930bbc 100644 --- a/src/transactions/script-composer/index.ts +++ b/src/transactions/script-composer/index.ts @@ -1,8 +1,8 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -import { CallArgument, TransactionComposer, initSync, create_wasm} from "@wgb5445/aptos-intent-npm"; -import { AptosApiType, Network } from "../../utils"; +import { CallArgument, TransactionComposer, initSync, create_wasm } from "@wgb5445/aptos-intent-npm"; +import { AptosApiType } from "../../utils"; import { AptosConfig } from "../../api"; import { EntryFunctionArgumentTypes, @@ -12,13 +12,14 @@ import { } from "../types"; import { convertArgument, fetchMoveFunctionAbi, getFunctionParts, standardizeTypeTags } from "../transactionBuilder"; import { TypeTag } from "../typeTag"; + let wasm = null; -(async ()=>{ +(async () => { wasm = initSync(await create_wasm()); })(); -function convert_batch_argument( +function convertCallArgument( argument: CallArgument | EntryFunctionArgumentTypes | SimpleEntryFunctionArgumentTypes, functionName: string, functionAbi: FunctionABI, @@ -27,15 +28,16 @@ function convert_batch_argument( ): CallArgument { if (argument instanceof CallArgument) { return argument; - } else { + } return CallArgument.new_bytes( convertArgument(functionName, functionAbi, argument, position, genericTypeParams).bcsToBytes(), ); - } + } export class AptosScriptComposer { private builder: TransactionComposer; + private config: AptosConfig; constructor(aptos_config: AptosConfig) { @@ -45,11 +47,11 @@ export class AptosScriptComposer { async add_batched_calls(input: InputBatchedFunctionData): Promise { const { moduleAddress, moduleName, functionName } = getFunctionParts(input.function); - const node_url = this.config.getRequestUrl(AptosApiType.FULLNODE); - await this.builder.load_module(node_url, moduleAddress + "::" + moduleName); - if(input.typeArguments != undefined) { - for (const type_tag of input.typeArguments) { - await this.builder.load_type_tag(node_url, type_tag.toString()); + const nodeUrl = this.config.getRequestUrl(AptosApiType.FULLNODE); + await this.builder.load_module(nodeUrl, `${moduleAddress }::${ moduleName}`); + if (input.typeArguments !== undefined) { + for (const typeTag of input.typeArguments) { + await this.builder.load_type_tag(nodeUrl, typeTag.toString()); } } const typeArguments = standardizeTypeTags(input.typeArguments); @@ -62,7 +64,7 @@ export class AptosScriptComposer { } const functionArguments: CallArgument[] = input.functionArguments.map((arg, i) => - convert_batch_argument(arg, functionName, functionAbi, i, typeArguments), + convertCallArgument(arg, functionName, functionAbi, i, typeArguments), ); return this.builder.add_batched_call( diff --git a/src/transactions/types.ts b/src/transactions/types.ts index 4e0fe8b28..1b41e1f45 100644 --- a/src/transactions/types.ts +++ b/src/transactions/types.ts @@ -1,6 +1,7 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 +import { CallArgument } from "@wgb5445/aptos-intent-npm"; import { AptosConfig } from "../api/aptosConfig"; import { MoveOption, MoveString, MoveVector } from "../bcs/serializable/moveStructs"; import { Bool, U128, U16, U256, U32, U64, U8 } from "../bcs/serializable/movePrimitives"; @@ -21,7 +22,6 @@ import { AccountAuthenticator } from "./authenticator/account"; import { SimpleTransaction } from "./instances/simpleTransaction"; import { MultiAgentTransaction } from "./instances/multiAgentTransaction"; import { Serialized } from "../bcs"; -import { CallArgument } from "@wgb5445/aptos-intent-npm"; /** * Entry function arguments for building a raw transaction using remote ABI, supporting various data types including primitives and arrays. diff --git a/tests/e2e/transaction/transactionSubmission.test.ts b/tests/e2e/transaction/transactionSubmission.test.ts index 8936c9cd0..c0a73051c 100644 --- a/tests/e2e/transaction/transactionSubmission.test.ts +++ b/tests/e2e/transaction/transactionSubmission.test.ts @@ -17,7 +17,6 @@ import { TransactionPayloadScript, generateRawTransaction, SimpleTransaction, - Network, } from "../../../src"; import { MAX_U64_BIG_INT } from "../../../src/bcs/consts"; import { longTestTimeout } from "../../unit/helper"; @@ -94,29 +93,29 @@ describe("transaction submission", () => { const transaction = await aptos.transaction.build.script_composer({ sender: singleSignerED25519SenderAccount.accountAddress, builder: async (builder) => { - let return_1 = await builder.add_batched_calls({ - function: `0x1::coin::withdraw`, + const coin = await builder.add_batched_calls({ + function: "0x1::coin::withdraw", functionArguments: [CallArgument.new_signer(0), 1], - typeArguments: ["0x1::aptos_coin::AptosCoin"] + typeArguments: ["0x1::aptos_coin::AptosCoin"], }); - - let return_2 = await builder.add_batched_calls({ - function: `0x1::coin::coin_to_fungible_asset`, - functionArguments: [return_1[0]], - typeArguments: ["0x1::aptos_coin::AptosCoin"] + + const fungibleAsset = await builder.add_batched_calls({ + function: "0x1::coin::coin_to_fungible_asset", + functionArguments: [coin[0]], + typeArguments: ["0x1::aptos_coin::AptosCoin"], }); - + await builder.add_batched_calls({ - function: `0x1::primary_fungible_store::deposit`, - functionArguments: [singleSignerED25519SenderAccount.accountAddress, return_2[0]], - typeArguments: [] + function: "0x1::primary_fungible_store::deposit", + functionArguments: [singleSignerED25519SenderAccount.accountAddress, fungibleAsset[0]], + typeArguments: [], }); return builder; - } + }, }); const response = await aptos.signAndSubmitTransaction({ signer: singleSignerED25519SenderAccount, - transaction: transaction, + transaction, }); await aptos.waitForTransaction({ From 3b40a497d2db4dbcfaab429ffadfaf0de7777a77 Mon Sep 17 00:00:00 2001 From: runtianz Date: Tue, 5 Nov 2024 12:10:15 -0800 Subject: [PATCH 3/9] Fix comments --- src/api/transactionSubmission/build.ts | 58 ++++++++++++++++++- src/transactions/script-composer/index.ts | 24 ++++++-- .../transactionBuilder/remoteAbi.ts | 2 +- .../transaction/transactionSubmission.test.ts | 8 +-- 4 files changed, 81 insertions(+), 11 deletions(-) diff --git a/src/api/transactionSubmission/build.ts b/src/api/transactionSubmission/build.ts index b4eaa71c1..83b3c4f47 100644 --- a/src/api/transactionSubmission/build.ts +++ b/src/api/transactionSubmission/build.ts @@ -101,14 +101,68 @@ export class Build { return generateTransaction({ aptosConfig: this.config, ...args }); } + /** + * Build a transaction from a series of Move calls. + * + * This function allows you to create a transaction with a list of Move calls. + * + * @param args.sender - The sender account address. + * @param args.builder - The closure to construct the list of calls. + * @param args.options - Optional transaction configurations. + * @param args.withFeePayer - Whether there is a fee payer for the transaction. + * + * @returns SimpleTransaction + * + * @example + * ```typescript + * import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"; + * + * const config = new AptosConfig({ network: Network.TESTNET }); + * const aptos = new Aptos(config); + * + * async function runExample() { + * // Build a transaction from a chained series of Move calls. + * const transaction = await aptos.transaction.script_composer({ + * sender: "0x1", // replace with a real sender account address + * builder: builder: async (builder) => { + * const coin = await builder.addBatchedCalls({ + * function: "0x1::coin::withdraw", + * functionArguments: [CallArgument.new_signer(0), 1], + * typeArguments: ["0x1::aptos_coin::AptosCoin"], + * }); + * + * // Pass the returned value from the first function call to the second call + * const fungibleAsset = await builder.addBatchedCalls({ + * function: "0x1::coin::coin_to_fungible_asset", + * functionArguments: [coin[0]], + * typeArguments: ["0x1::aptos_coin::AptosCoin"], + * }); + * + * await builder.addBatchedCalls({ + * function: "0x1::primary_fungible_store::deposit", + * functionArguments: [singleSignerED25519SenderAccount.accountAddress, fungibleAsset[0]], + * typeArguments: [], + * }); + * return builder; + * }, + * options: { + * gasUnitPrice: 100, // specify your own gas unit price if needed + * maxGasAmount: 1000, // specify your own max gas amount if needed + * }, + * }); + * + * console.log(transaction); + * } + * runExample().catch(console.error); + * ``` + */ async script_composer(args: { sender: AccountAddressInput; builder: (builder: AptosScriptComposer) => Promise; options?: InputGenerateTransactionOptions; withFeePayer?: boolean; }): Promise { - let builder = new AptosScriptComposer(this.config); - builder = await args.builder(builder); + const builder = await args.builder(new AptosScriptComposer(this.config)); const bytes = builder.build(); const raw_txn = await generateRawTransaction({ aptosConfig: this.config, diff --git a/src/transactions/script-composer/index.ts b/src/transactions/script-composer/index.ts index 40d930bbc..1cfdde4b7 100644 --- a/src/transactions/script-composer/index.ts +++ b/src/transactions/script-composer/index.ts @@ -35,20 +35,36 @@ function convertCallArgument( } +// A wrapper class around TransactionComposer, which is a WASM library compiled +// from aptos-core/aptos-move/script-composer. +// +// This class allows the SDK caller to build a transaction that invokes multiple Move functions +// and allow for arguments to be passed around. export class AptosScriptComposer { private builder: TransactionComposer; private config: AptosConfig; - constructor(aptos_config: AptosConfig) { + constructor(aptosConfig: AptosConfig) { this.builder = TransactionComposer.single_signer(); - this.config = aptos_config; + this.config = aptosConfig; } - async add_batched_calls(input: InputBatchedFunctionData): Promise { + // Add a move function invocation to the TransactionComposer. + // + // Similar to how to create an entry function, the difference is that input arguments could + // either be a `CallArgument` which represents an abstract value returned from a previous Move call + // or the regular entry function arguments. + // + // The function would also return a list of `CallArgument` that can be passed on to future calls. + async addBatchedCalls(input: InputBatchedFunctionData): Promise { const { moduleAddress, moduleName, functionName } = getFunctionParts(input.function); const nodeUrl = this.config.getRequestUrl(AptosApiType.FULLNODE); - await this.builder.load_module(nodeUrl, `${moduleAddress }::${ moduleName}`); + + // Load the calling module into the builder. + await this.builder.load_module(nodeUrl, `${moduleAddress}::${moduleName}`); + + // Load the calling type arguments into the loader. if (input.typeArguments !== undefined) { for (const typeTag of input.typeArguments) { await this.builder.load_type_tag(nodeUrl, typeTag.toString()); diff --git a/src/transactions/transactionBuilder/remoteAbi.ts b/src/transactions/transactionBuilder/remoteAbi.ts index 5021efc07..578790ab6 100644 --- a/src/transactions/transactionBuilder/remoteAbi.ts +++ b/src/transactions/transactionBuilder/remoteAbi.ts @@ -107,7 +107,7 @@ export async function fetchMoveFunctionAbi( ): Promise { const functionAbi = await fetchFunctionAbi(moduleAddress, moduleName, functionName, aptosConfig); if (!functionAbi) { - throw new Error(`Could not find entry function ABI for '${moduleAddress}::${moduleName}::${functionName}'`); + throw new Error(`Could not find function ABI for '${moduleAddress}::${moduleName}::${functionName}'`); } const params: TypeTag[] = []; for (let i = 0; i < functionAbi.params.length; i += 1) { diff --git a/tests/e2e/transaction/transactionSubmission.test.ts b/tests/e2e/transaction/transactionSubmission.test.ts index c0a73051c..bd2800b6a 100644 --- a/tests/e2e/transaction/transactionSubmission.test.ts +++ b/tests/e2e/transaction/transactionSubmission.test.ts @@ -68,7 +68,7 @@ describe("transaction submission", () => { }); test("with batch payload", async () => { const builder = new AptosScriptComposer(aptos.config); - await builder.add_batched_calls({ + await builder.addBatchedCalls({ function: `${contractPublisherAccount.accountAddress}::transfer::transfer`, functionArguments: [CallArgument.new_signer(0), 1, receiverAccounts[0].accountAddress], }); @@ -93,19 +93,19 @@ describe("transaction submission", () => { const transaction = await aptos.transaction.build.script_composer({ sender: singleSignerED25519SenderAccount.accountAddress, builder: async (builder) => { - const coin = await builder.add_batched_calls({ + const coin = await builder.addBatchedCalls({ function: "0x1::coin::withdraw", functionArguments: [CallArgument.new_signer(0), 1], typeArguments: ["0x1::aptos_coin::AptosCoin"], }); - const fungibleAsset = await builder.add_batched_calls({ + const fungibleAsset = await builder.addBatchedCalls({ function: "0x1::coin::coin_to_fungible_asset", functionArguments: [coin[0]], typeArguments: ["0x1::aptos_coin::AptosCoin"], }); - await builder.add_batched_calls({ + await builder.addBatchedCalls({ function: "0x1::primary_fungible_store::deposit", functionArguments: [singleSignerED25519SenderAccount.accountAddress, fungibleAsset[0]], typeArguments: [], From 9567f26dd5bec8e716b54aeff78dbd8f827a7d69 Mon Sep 17 00:00:00 2001 From: runtianz Date: Tue, 5 Nov 2024 13:28:36 -0800 Subject: [PATCH 4/9] Fix deps, re-export CallArgument --- src/api/transactionSubmission/build.ts | 10 ++++++---- src/transactions/script-composer/index.ts | 10 +++++----- src/types/index.ts | 1 + tests/e2e/transaction/transactionSubmission.test.ts | 5 +++-- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/api/transactionSubmission/build.ts b/src/api/transactionSubmission/build.ts index 83b3c4f47..179726645 100644 --- a/src/api/transactionSubmission/build.ts +++ b/src/api/transactionSubmission/build.ts @@ -13,7 +13,6 @@ import { import { MultiAgentTransaction } from "../../transactions/instances/multiAgentTransaction"; import { SimpleTransaction } from "../../transactions/instances/simpleTransaction"; import { AptosConfig } from "../aptosConfig"; -import { singleSignerED25519 } from "../../../tests/unit/helper"; import { Deserializer } from "../../bcs"; /** @@ -105,6 +104,9 @@ export class Build { * Build a transaction from a series of Move calls. * * This function allows you to create a transaction with a list of Move calls. + * + * Right now we only tested this logic with single signer and we will add support + * for mutli agent transactions if needed. * * @param args.sender - The sender account address. * @param args.builder - The closure to construct the list of calls. @@ -156,7 +158,7 @@ export class Build { * runExample().catch(console.error); * ``` */ - async script_composer(args: { + async scriptComposer(args: { sender: AccountAddressInput; builder: (builder: AptosScriptComposer) => Promise; options?: InputGenerateTransactionOptions; @@ -164,12 +166,12 @@ export class Build { }): Promise { const builder = await args.builder(new AptosScriptComposer(this.config)); const bytes = builder.build(); - const raw_txn = await generateRawTransaction({ + const rawTxn = await generateRawTransaction({ aptosConfig: this.config, payload: TransactionPayloadScript.load(new Deserializer(bytes)), ...args, }); - return new SimpleTransaction(raw_txn); + return new SimpleTransaction(rawTxn); } /** diff --git a/src/transactions/script-composer/index.ts b/src/transactions/script-composer/index.ts index 1cfdde4b7..0ef58d49d 100644 --- a/src/transactions/script-composer/index.ts +++ b/src/transactions/script-composer/index.ts @@ -1,9 +1,9 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -import { CallArgument, TransactionComposer, initSync, create_wasm } from "@wgb5445/aptos-intent-npm"; +import { TransactionComposer, initSync, create_wasm } from "@wgb5445/aptos-intent-npm"; import { AptosApiType } from "../../utils"; -import { AptosConfig } from "../../api"; +import { AptosConfig } from "../../api/aptosConfig"; import { EntryFunctionArgumentTypes, FunctionABI, @@ -12,11 +12,10 @@ import { } from "../types"; import { convertArgument, fetchMoveFunctionAbi, getFunctionParts, standardizeTypeTags } from "../transactionBuilder"; import { TypeTag } from "../typeTag"; - -let wasm = null; +import { CallArgument } from "../../types"; (async () => { - wasm = initSync(await create_wasm()); + initSync(await create_wasm()); })(); function convertCallArgument( @@ -67,6 +66,7 @@ export class AptosScriptComposer { // Load the calling type arguments into the loader. if (input.typeArguments !== undefined) { for (const typeTag of input.typeArguments) { + // eslint-disable-next-line no-await-in-loop await this.builder.load_type_tag(nodeUrl, typeTag.toString()); } } diff --git a/src/types/index.ts b/src/types/index.ts index bec57ae9f..1e8bd7537 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,2 +1,3 @@ export * from "./indexer"; export * from "./types"; +export { CallArgument } from "@wgb5445/aptos-intent-npm" diff --git a/tests/e2e/transaction/transactionSubmission.test.ts b/tests/e2e/transaction/transactionSubmission.test.ts index bd2800b6a..3f2c30bf6 100644 --- a/tests/e2e/transaction/transactionSubmission.test.ts +++ b/tests/e2e/transaction/transactionSubmission.test.ts @@ -1,7 +1,7 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -import { CallArgument } from "@wgb5445/aptos-intent-npm"; +//import { CallArgument } from "@wgb5445/aptos-intent-npm"; import { Account, U64, @@ -17,6 +17,7 @@ import { TransactionPayloadScript, generateRawTransaction, SimpleTransaction, + CallArgument, } from "../../../src"; import { MAX_U64_BIG_INT } from "../../../src/bcs/consts"; import { longTestTimeout } from "../../unit/helper"; @@ -90,7 +91,7 @@ describe("transaction submission", () => { expect(response.signature?.type).toBe("single_sender"); }); test.only("with batch withdraw payload", async () => { - const transaction = await aptos.transaction.build.script_composer({ + const transaction = await aptos.transaction.build.scriptComposer({ sender: singleSignerED25519SenderAccount.accountAddress, builder: async (builder) => { const coin = await builder.addBatchedCalls({ From f73c2a83f608a84df30669fe5b675b1f1f77c29e Mon Sep 17 00:00:00 2001 From: runtianz Date: Tue, 5 Nov 2024 15:02:51 -0800 Subject: [PATCH 5/9] fixup! Fix deps, re-export CallArgument --- CHANGELOG.md | 1 + tests/e2e/transaction/transactionSubmission.test.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28cbd1ca4..04caa056a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to the Aptos TypeScript SDK will be captured in this file. T - Includes the address in the `AbstractKeylessAccount` serialization to prevent information loss for key rotated accounts. - [`Breaking`] Deprecate `serializeOptionStr` and `deserializeOptionStr` in favor of `serializeOption` and `deserializeOption`. - [`Breaking`] Renames `KeylessConfiguration.verficationKey` to `verificationKey` +- Add a new `scriptComposer` api in transactionSubmission api to allower SDK callers to invoke multiple Move functions inside a same transaction and compose the calls dynamically. # 1.31.0 (2024-10-24) diff --git a/tests/e2e/transaction/transactionSubmission.test.ts b/tests/e2e/transaction/transactionSubmission.test.ts index 3f2c30bf6..393c58b17 100644 --- a/tests/e2e/transaction/transactionSubmission.test.ts +++ b/tests/e2e/transaction/transactionSubmission.test.ts @@ -90,7 +90,7 @@ describe("transaction submission", () => { expect(response.signature?.type).toBe("single_sender"); }); - test.only("with batch withdraw payload", async () => { + test("with batch withdraw payload", async () => { const transaction = await aptos.transaction.build.scriptComposer({ sender: singleSignerED25519SenderAccount.accountAddress, builder: async (builder) => { From eb078014835202c99d91ab4bd72dd3f65ab26e29 Mon Sep 17 00:00:00 2001 From: runtianz Date: Wed, 6 Nov 2024 19:08:46 -0800 Subject: [PATCH 6/9] Fix comments --- pnpm-lock.yaml | 7 +++++ src/transactions/script-composer/index.ts | 17 +---------- .../transactionBuilder/remoteAbi.ts | 29 ++++++++++++++++++- .../transaction/transactionSubmission.test.ts | 2 -- 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 16c7a18d8..9bcad3364 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ dependencies: '@scure/bip39': specifier: ^1.3.0 version: 1.4.0 + '@wgb5445/aptos-intent-npm': + specifier: ^0.1.7 + version: 0.1.7 eventemitter3: specifier: ^5.0.1 version: 5.0.1 @@ -3018,6 +3021,10 @@ packages: '@xtuc/long': 4.2.2 dev: true + /@wgb5445/aptos-intent-npm@0.1.7: + resolution: {integrity: sha512-DyaiJQFWih3o1yxS0WX7LJiYydRba6gXGrlv/hiyn3xApoXCtdln3lpc9VRLLdxdG5IMbO7eh/+AHqM1h1DnGQ==} + dev: false + /@whatwg-node/fetch@0.9.21: resolution: {integrity: sha512-Wt0jPb+04JjobK0pAAN7mEHxVHcGA9HoP3OyCsZtyAecNQeADXCZ1MihFwVwjsgaRYuGVmNlsCmLxlG6mor8Gw==} engines: {node: '>=18.0.0'} diff --git a/src/transactions/script-composer/index.ts b/src/transactions/script-composer/index.ts index 0ef58d49d..b3f20ed4f 100644 --- a/src/transactions/script-composer/index.ts +++ b/src/transactions/script-composer/index.ts @@ -13,27 +13,12 @@ import { import { convertArgument, fetchMoveFunctionAbi, getFunctionParts, standardizeTypeTags } from "../transactionBuilder"; import { TypeTag } from "../typeTag"; import { CallArgument } from "../../types"; +import { convertCallArgument } from "../transactionBuilder/remoteAbi" (async () => { initSync(await create_wasm()); })(); -function convertCallArgument( - argument: CallArgument | EntryFunctionArgumentTypes | SimpleEntryFunctionArgumentTypes, - functionName: string, - functionAbi: FunctionABI, - position: number, - genericTypeParams: Array, -): CallArgument { - if (argument instanceof CallArgument) { - return argument; - } - return CallArgument.new_bytes( - convertArgument(functionName, functionAbi, argument, position, genericTypeParams).bcsToBytes(), - ); - -} - // A wrapper class around TransactionComposer, which is a WASM library compiled // from aptos-core/aptos-move/script-composer. // diff --git a/src/transactions/transactionBuilder/remoteAbi.ts b/src/transactions/transactionBuilder/remoteAbi.ts index 578790ab6..c6ff1d7f2 100644 --- a/src/transactions/transactionBuilder/remoteAbi.ts +++ b/src/transactions/transactionBuilder/remoteAbi.ts @@ -45,7 +45,7 @@ import { throwTypeMismatch, convertNumber, } from "./helpers"; -import { MoveFunction } from "../../types"; +import { CallArgument, MoveFunction } from "../../types"; const TEXT_ENCODER = new TextEncoder(); @@ -211,6 +211,33 @@ export async function fetchViewFunctionAbi( }; } +/** + * Converts a entry function argument into CallArgument, if necessary. + * This function checks the provided argument against the expected parameter type and converts it accordingly. + * + * @param functionName - The name of the function for which the argument is being converted. + * @param functionAbi - The ABI (Application Binary Interface) of the function, which defines its parameters. + * @param argument - The argument to be converted, which can be of various types. If the argument is already + * CallArgument returned from TransactionComposer it would be returned immediately. + * @param position - The index of the argument in the function's parameter list. + * @param genericTypeParams - An array of type tags for any generic type parameters. + */ +export function convertCallArgument( + argument: CallArgument | EntryFunctionArgumentTypes | SimpleEntryFunctionArgumentTypes, + functionName: string, + functionAbi: FunctionABI, + position: number, + genericTypeParams: Array, +): CallArgument { + if (argument instanceof CallArgument) { + return argument; + } + return CallArgument.new_bytes( + convertArgument(functionName, functionAbi, argument, position, genericTypeParams).bcsToBytes(), + ); + +} + /** * Converts a non-BCS encoded argument into BCS encoded, if necessary. * This function checks the provided argument against the expected parameter type and converts it accordingly. diff --git a/tests/e2e/transaction/transactionSubmission.test.ts b/tests/e2e/transaction/transactionSubmission.test.ts index 393c58b17..89b00f3e2 100644 --- a/tests/e2e/transaction/transactionSubmission.test.ts +++ b/tests/e2e/transaction/transactionSubmission.test.ts @@ -1,7 +1,6 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -//import { CallArgument } from "@wgb5445/aptos-intent-npm"; import { Account, U64, @@ -109,7 +108,6 @@ describe("transaction submission", () => { await builder.addBatchedCalls({ function: "0x1::primary_fungible_store::deposit", functionArguments: [singleSignerED25519SenderAccount.accountAddress, fungibleAsset[0]], - typeArguments: [], }); return builder; }, From c8e9323767595c3b8535bf7ff0853e14592c5e32 Mon Sep 17 00:00:00 2001 From: runtianz Date: Thu, 7 Nov 2024 09:12:45 -0800 Subject: [PATCH 7/9] fix lint --- src/api/transactionSubmission/build.ts | 4 ++-- src/transactions/script-composer/index.ts | 16 +++++----------- src/transactions/transactionBuilder/remoteAbi.ts | 9 ++++----- src/types/index.ts | 2 +- 4 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/api/transactionSubmission/build.ts b/src/api/transactionSubmission/build.ts index 179726645..469a690bf 100644 --- a/src/api/transactionSubmission/build.ts +++ b/src/api/transactionSubmission/build.ts @@ -104,7 +104,7 @@ export class Build { * Build a transaction from a series of Move calls. * * This function allows you to create a transaction with a list of Move calls. - * + * * Right now we only tested this logic with single signer and we will add support * for mutli agent transactions if needed. * @@ -132,7 +132,7 @@ export class Build { * functionArguments: [CallArgument.new_signer(0), 1], * typeArguments: ["0x1::aptos_coin::AptosCoin"], * }); - * + * * // Pass the returned value from the first function call to the second call * const fungibleAsset = await builder.addBatchedCalls({ * function: "0x1::coin::coin_to_fungible_asset", diff --git a/src/transactions/script-composer/index.ts b/src/transactions/script-composer/index.ts index b3f20ed4f..e0835df06 100644 --- a/src/transactions/script-composer/index.ts +++ b/src/transactions/script-composer/index.ts @@ -4,23 +4,17 @@ import { TransactionComposer, initSync, create_wasm } from "@wgb5445/aptos-intent-npm"; import { AptosApiType } from "../../utils"; import { AptosConfig } from "../../api/aptosConfig"; -import { - EntryFunctionArgumentTypes, - FunctionABI, - InputBatchedFunctionData, - SimpleEntryFunctionArgumentTypes, -} from "../types"; -import { convertArgument, fetchMoveFunctionAbi, getFunctionParts, standardizeTypeTags } from "../transactionBuilder"; -import { TypeTag } from "../typeTag"; +import { InputBatchedFunctionData } from "../types"; +import { fetchMoveFunctionAbi, getFunctionParts, standardizeTypeTags } from "../transactionBuilder"; import { CallArgument } from "../../types"; -import { convertCallArgument } from "../transactionBuilder/remoteAbi" +import { convertCallArgument } from "../transactionBuilder/remoteAbi"; (async () => { - initSync(await create_wasm()); + initSync(await create_wasm()); })(); // A wrapper class around TransactionComposer, which is a WASM library compiled -// from aptos-core/aptos-move/script-composer. +// from aptos-core/aptos-move/script-composer. // // This class allows the SDK caller to build a transaction that invokes multiple Move functions // and allow for arguments to be passed around. diff --git a/src/transactions/transactionBuilder/remoteAbi.ts b/src/transactions/transactionBuilder/remoteAbi.ts index c6ff1d7f2..f5a2cda7b 100644 --- a/src/transactions/transactionBuilder/remoteAbi.ts +++ b/src/transactions/transactionBuilder/remoteAbi.ts @@ -231,11 +231,10 @@ export function convertCallArgument( ): CallArgument { if (argument instanceof CallArgument) { return argument; - } - return CallArgument.new_bytes( - convertArgument(functionName, functionAbi, argument, position, genericTypeParams).bcsToBytes(), - ); - + } + return CallArgument.new_bytes( + convertArgument(functionName, functionAbi, argument, position, genericTypeParams).bcsToBytes(), + ); } /** diff --git a/src/types/index.ts b/src/types/index.ts index 1e8bd7537..69f97cb61 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,3 +1,3 @@ export * from "./indexer"; export * from "./types"; -export { CallArgument } from "@wgb5445/aptos-intent-npm" +export { CallArgument } from "@wgb5445/aptos-intent-npm"; From 0446a3de428531c418ffd38b1351c39eda9048b8 Mon Sep 17 00:00:00 2001 From: runtianz Date: Thu, 7 Nov 2024 10:13:26 -0800 Subject: [PATCH 8/9] fixup! fix lint --- package.json | 2 +- pnpm-lock.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 613681540..afcdc9735 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "@noble/hashes": "^1.4.0", "@scure/bip32": "^1.4.0", "@scure/bip39": "^1.3.0", - "@wgb5445/aptos-intent-npm": "^0.1.7", + "@wgb5445/aptos-intent-npm": "^0.1.8", "eventemitter3": "^5.0.1", "form-data": "^4.0.0", "js-base64": "^3.7.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9bcad3364..1c468439c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,8 +24,8 @@ dependencies: specifier: ^1.3.0 version: 1.4.0 '@wgb5445/aptos-intent-npm': - specifier: ^0.1.7 - version: 0.1.7 + specifier: ^0.1.8 + version: 0.1.8 eventemitter3: specifier: ^5.0.1 version: 5.0.1 @@ -3021,8 +3021,8 @@ packages: '@xtuc/long': 4.2.2 dev: true - /@wgb5445/aptos-intent-npm@0.1.7: - resolution: {integrity: sha512-DyaiJQFWih3o1yxS0WX7LJiYydRba6gXGrlv/hiyn3xApoXCtdln3lpc9VRLLdxdG5IMbO7eh/+AHqM1h1DnGQ==} + /@wgb5445/aptos-intent-npm@0.1.8: + resolution: {integrity: sha512-At8NZy5U68OQDQV019MC2ZXeIT9u1KDja5BJPiqv/vWAcsZ9LS2rW1i/Etd4ow6q5NFptHpkZkxvL/heXdsy2w==} dev: false /@whatwg-node/fetch@0.9.21: From 11fd622b0baa6beed7c8b6937e506767e42983a4 Mon Sep 17 00:00:00 2001 From: runtianz Date: Thu, 7 Nov 2024 11:11:13 -0800 Subject: [PATCH 9/9] Update package --- package.json | 2 +- pnpm-lock.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index afcdc9735..25fcb42e9 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "@noble/hashes": "^1.4.0", "@scure/bip32": "^1.4.0", "@scure/bip39": "^1.3.0", - "@wgb5445/aptos-intent-npm": "^0.1.8", + "@wgb5445/aptos-intent-npm": "^0.1.9", "eventemitter3": "^5.0.1", "form-data": "^4.0.0", "js-base64": "^3.7.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1c468439c..94ab8ad97 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,8 +24,8 @@ dependencies: specifier: ^1.3.0 version: 1.4.0 '@wgb5445/aptos-intent-npm': - specifier: ^0.1.8 - version: 0.1.8 + specifier: ^0.1.9 + version: 0.1.9 eventemitter3: specifier: ^5.0.1 version: 5.0.1 @@ -3021,8 +3021,8 @@ packages: '@xtuc/long': 4.2.2 dev: true - /@wgb5445/aptos-intent-npm@0.1.8: - resolution: {integrity: sha512-At8NZy5U68OQDQV019MC2ZXeIT9u1KDja5BJPiqv/vWAcsZ9LS2rW1i/Etd4ow6q5NFptHpkZkxvL/heXdsy2w==} + /@wgb5445/aptos-intent-npm@0.1.9: + resolution: {integrity: sha512-Mv+RjURwKtiV8YjW7XmysbnBAR1aSRDCmtC0TmGPxSFsUVyWCE1whtmO7zyxm16rLfEAhW9wkvfmkZU6K3Wf0w==} dev: false /@whatwg-node/fetch@0.9.21: