Skip to content

Commit

Permalink
Runtianz/new script composer (#587)
Browse files Browse the repository at this point in the history
* Revert "[script-composer] Revert script composers (#574)"

This reverts commit bf8afa1.

* update deps

* Update package.json

* Update build.ts

* Fix: Add default FeePayer address for sponsored transactions in Script Composer (#612)

* fix lock

* Use lazy loading

* Fix comments

* fixup! Fix comments

* fixup! fixup! Fix comments

* upgrade @aptos-labs/script-composer-pack and fix init

* fixup! upgrade @aptos-labs/script-composer-pack and fix init

* fixup! fixup! upgrade @aptos-labs/script-composer-pack and fix init

* fixup! fixup! fixup! upgrade @aptos-labs/script-composer-pack and fix init

---------

Co-authored-by: Anto <[email protected]>
Co-authored-by: WGB5445 <[email protected]>
  • Loading branch information
3 people authored Feb 4, 2025
1 parent 2386c07 commit d5f7124
Show file tree
Hide file tree
Showing 10 changed files with 355 additions and 4 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
All notable changes to the Aptos TypeScript SDK will be captured in this file. This changelog is written by hand for now. It adheres to the format set out by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

# Unreleased
- Add new `scriptComposer` api in transactionSubmission api to allow SDK callers to invoke multiple Move functions inside a same transaction and compose the calls dynamically.

# 1.33.2 (2025-01-22)

Expand All @@ -29,7 +30,6 @@ All notable changes to the Aptos TypeScript SDK will be captured in this file. T
# 1.32.1 (2024-11-11)

- Add support for Firebase issuers in the `updateFederatedKeylessJwkSetTransaction` function
- [`Breaking`] Revert new `scriptComposer` api in transactionSubmission api to allow SDK callers to invoke multiple Move functions inside a same transaction and compose the calls dynamically.

# 1.32.0 (2024-11-08)

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"@noble/hashes": "^1.4.0",
"@scure/bip32": "^1.4.0",
"@scure/bip39": "^1.3.0",
"@aptos-labs/script-composer-pack": "^0.0.9",
"eventemitter3": "^5.0.1",
"form-data": "^4.0.0",
"js-base64": "^3.7.7",
Expand Down
13 changes: 13 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

87 changes: 85 additions & 2 deletions src/api/transactionSubmission/build.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
// Copyright © Aptos Foundation
// SPDX-License-Identifier: Apache-2.0

import { AccountAddressInput } from "../../core";
import { AccountAddress, 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 { Deserializer } from "../../bcs";

/**
* A class to handle all `Build` transaction operations.
Expand Down Expand Up @@ -96,6 +103,82 @@ 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.
*
* 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.
* @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.build.scriptComposer({
* 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 scriptComposer(args: {
sender: AccountAddressInput;
builder: (builder: AptosScriptComposer) => Promise<AptosScriptComposer>;
options?: InputGenerateTransactionOptions;
withFeePayer?: boolean;
}): Promise<SimpleTransaction> {
const composer = new AptosScriptComposer(this.config);
await composer.init();
const builder = await args.builder(composer);
const bytes = builder.build();
const rawTxn = await generateRawTransaction({
aptosConfig: this.config,
payload: TransactionPayloadScript.load(new Deserializer(bytes)),
...args,
});
return new SimpleTransaction(rawTxn, args.withFeePayer === true ? AccountAddress.ZERO : undefined);
}

/**
* Build a multi-agent transaction that allows multiple signers to authorize a transaction.
*
Expand Down
1 change: 1 addition & 0 deletions src/transactions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from "./transactionBuilder";
export * from "./typeTag";
export * from "./typeTag/parser";
export * from "./types";
export * from "./scriptComposer";
87 changes: 87 additions & 0 deletions src/transactions/scriptComposer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright © Aptos Foundation
// SPDX-License-Identifier: Apache-2.0

import { ScriptComposerWasm } from "@aptos-labs/script-composer-pack";
import { AptosApiType } from "../../utils";
import { AptosConfig } from "../../api/aptosConfig";
import { InputBatchedFunctionData } from "../types";
import { fetchMoveFunctionAbi, getFunctionParts, standardizeTypeTags } from "../transactionBuilder";
import { CallArgument } from "../../types";
import { convertCallArgument } from "../transactionBuilder/remoteAbi";

/**
* 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 config: AptosConfig;

private builder?: any;

private static transactionComposer?: any;

constructor(aptosConfig: AptosConfig) {
this.config = aptosConfig;
this.builder = undefined;
}

// Initializing the wasm needed for the script composer, must be called
// before using the composer.
async init() {
if (!AptosScriptComposer.transactionComposer) {
const module = await import("@aptos-labs/script-composer-pack");
const { TransactionComposer, initSync } = module;
if (!ScriptComposerWasm.isInitialized) {
ScriptComposerWasm.init();
}
initSync({ module: ScriptComposerWasm.wasm });
AptosScriptComposer.transactionComposer = TransactionComposer;
}
this.builder = AptosScriptComposer.transactionComposer.single_signer();
}

// 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<CallArgument[]> {
const { moduleAddress, moduleName, functionName } = getFunctionParts(input.function);
const nodeUrl = this.config.getRequestUrl(AptosApiType.FULLNODE);

// 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) {
await Promise.all(input.typeArguments.map((typeTag) => this.builder.load_type_tag(nodeUrl, typeTag.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) =>
convertCallArgument(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);
}
}
56 changes: 55 additions & 1 deletion src/transactions/transactionBuilder/remoteAbi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import {
throwTypeMismatch,
convertNumber,
} from "./helpers";
import { MoveFunction } from "../../types";
import { CallArgument, MoveFunction } from "../../types";

const TEXT_ENCODER = new TextEncoder();

Expand Down Expand Up @@ -96,6 +96,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<FunctionABI> {
const functionAbi = await fetchFunctionAbi(moduleAddress, moduleName, functionName, aptosConfig);
if (!functionAbi) {
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) {
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.
Expand Down Expand Up @@ -191,6 +219,32 @@ 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<TypeTag>,
): CallArgument {
if (argument instanceof CallArgument) {
return argument;
}
return CallArgument.newBytes(
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.
Expand Down
11 changes: 11 additions & 0 deletions src/transactions/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright © Aptos Foundation
// SPDX-License-Identifier: Apache-2.0

import { CallArgument } from "@aptos-labs/script-composer-pack";
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";
Expand Down Expand Up @@ -192,6 +193,16 @@ export type InputMultiSigDataWithABI = {
* @category Transactions
*/
export type InputEntryFunctionDataWithRemoteABI = InputEntryFunctionData & { aptosConfig: AptosConfig };

/**
* The data needed to generate a batched function payload
*/
export type InputBatchedFunctionData = {
function: MoveFunctionId;
typeArguments?: Array<TypeArgument>;
functionArguments: Array<EntryFunctionArgumentTypes | CallArgument | SimpleEntryFunctionArgumentTypes>;
};

/**
* The data needed to generate a Multi Sig payload
* @group Implementation
Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./indexer";
export * from "./types";
export { CallArgument } from "@aptos-labs/script-composer-pack";
Loading

0 comments on commit d5f7124

Please sign in to comment.