Skip to content

Commit bd06004

Browse files
authored
chore: update SDK version to 0.3.0-3 and auto-fetch capability (#10)
* chore: update SDK version to 0.3.0-0, add .npmrc for authentication, and enhance AptosScriptComposer with auto-fetch capability
1 parent a862491 commit bd06004

8 files changed

Lines changed: 226 additions & 266 deletions

File tree

examples/nextjs-project/src/app/components/ScriptComposer.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ export default function ScriptComposer() {
2727
function: '0x1::aptos_account::transfer',
2828
functionArguments: [CallArgument.newSigner(0), '0x1', 1],
2929
typeArguments: [],
30-
moduleAbi: aptos_account_module.abi!,
30+
moduleAbi: aptos_account_module.abi,
31+
moduleBytecodes: [aptos_account_module.bytecode],
3132
});
3233
return composer
3334
},

examples/nodejs/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ async function mainWithCache() {
2121
// Do not allow auto fetch
2222
options: {
2323
allowFetch: false,
24-
}
25-
// moduleAbi: aptos_account_module.abi,
24+
},
25+
moduleAbi: aptos_account_module.abi,
26+
moduleBytecodes: [aptos_account_module.bytecode],
2627
});
2728
return composer
2829
},

examples/react-project/src/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ function App() {
2626
function: '0x1::aptos_account::transfer',
2727
functionArguments: [ CallArgument.newSigner(0) ,'0x1', 1],
2828
typeArguments: [],
29-
moduleAbi: aptos_account_module.abi!,
29+
moduleAbi: aptos_account_module.abi,
30+
moduleBytecodes: [aptos_account_module.bytecode],
3031
});
3132
return composer
3233
},

packages/sdk/.npmrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
2+
registry=https://registry.npmjs.org/
3+
always-auth=true

packages/sdk/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@aptos-labs/script-composer-sdk",
3-
"version": "0.2.0",
3+
"version": "0.3.0-3",
44
"description": "Script Composer SDK",
55
"main": "dist/index.cjs",
66
"module": "dist/index.js",
@@ -37,7 +37,6 @@
3737
"author": "",
3838
"license": "ISC",
3939
"peerDependencies": {
40-
"@aptos-labs/script-composer-pack": "0.1.0",
4140
"@aptos-labs/ts-sdk": "^3.0.0 || ^4.0.0 || ^5.0.0"
4241
},
4342
"devDependencies": {
@@ -51,5 +50,8 @@
5150
"tsup": "8.5.0",
5251
"typescript": "5.8.3",
5352
"vitest": "3.2.4"
53+
},
54+
"dependencies": {
55+
"@aptos-labs/script-composer-pack": "0.2.0"
5456
}
5557
}

packages/sdk/src/index.ts

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,10 @@ export type InputBatchedFunctionData = {
3939
functionArguments: Array<
4040
EntryFunctionArgumentTypes | CallArgument | SimpleEntryFunctionArgumentTypes
4141
>;
42-
moduleAbi: MoveModule;
42+
moduleAbi?: MoveModule;
4343
moduleBytecodes?: string[];
4444
options?: {
45+
/** @default true - Automatically fetch missing modules from the chain */
4546
allowFetch?: boolean;
4647
};
4748
};
@@ -88,33 +89,65 @@ export class AptosScriptComposer {
8889
// or the regular entry function arguments.
8990
//
9091
// The function would also return a list of `CallArgument` that can be passed on to future calls.
92+
//
93+
// Validation behavior:
94+
// - If allowFetch is true (default): Validates that the function exists in the provided ABI (if any)
95+
// - If allowFetch is false: Requires both moduleAbi and moduleBytecodes to be provided
96+
// - Automatically fetches missing modules from the chain when allowFetch is enabled
9197
async addBatchedCalls(input: InputBatchedFunctionData): Promise<CallArgument[]> {
9298
const { moduleAddress, moduleName, functionName } = getFunctionParts(input.function);
9399
const module = input.moduleAbi;
94100
const moduleBytecode = input.moduleBytecodes;
101+
const autoFetch = input.options?.allowFetch ?? true;
102+
103+
// Validation logic based on auto-fetch option
104+
if (autoFetch) {
105+
// Auto-fetch mode: Check if function exists in ABI
106+
if (module) {
107+
const functionAbi = module.exposed_functions.find((func) => func.name === functionName);
108+
if (!functionAbi) {
109+
throw new Error(
110+
`Function '${functionName}' not found in provided ABI for module '${moduleAddress}::${moduleName}'`
111+
);
112+
}
113+
}
114+
} else {
115+
// Manual mode: Check if both ABI and bytecode are provided
116+
if (!module) {
117+
throw new Error(
118+
`Module ABI is required when auto-fetch is disabled for '${moduleAddress}::${moduleName}'`
119+
);
120+
}
121+
if (!moduleBytecode || moduleBytecode.length === 0) {
122+
throw new Error(
123+
`Module bytecode is required when auto-fetch is disabled for '${moduleAddress}::${moduleName}'`
124+
);
125+
}
126+
}
95127

96128
moduleBytecode?.forEach((module) => {
97129
this.builder.store_module(Hex.fromHexInput(module).toUint8Array());
98130
});
99131

100-
if (!AptosScriptComposer.loadedModulesCache.has(`${moduleAddress}::${moduleName}`)) {
101-
if (input.options?.allowFetch) {
102-
// If the module is not loaded, we can fetch it.
103-
const moduleBytecode = await getModuleInner({
104-
aptosConfig: this.config,
105-
accountAddress: moduleAddress,
106-
moduleName: moduleName.toString(),
107-
});
108-
if (moduleBytecode) {
109-
this.storeModule(moduleBytecode, `${moduleAddress}::${moduleName}`);
110-
} else {
111-
throw new Error(
112-
`Module '${moduleAddress}::${moduleName}' could not be fetched. Please ensure it exists on the chain.`
113-
);
114-
}
132+
const moduleId = `${moduleAddress}::${moduleName}`;
133+
const isModuleLoaded = AptosScriptComposer.loadedModulesCache.has(moduleId);
134+
const isModuleStored = this.storedModulesMap.has(moduleId);
135+
136+
// If the module is not loaded in the global cache (isModuleLoaded) or not stored in the local map (isModuleStored),
137+
// and autoFetch is enabled, we need to fetch and store the module.
138+
// This ensures that the module is available both globally and locally for execution.
139+
if ((!isModuleLoaded || !isModuleStored) && autoFetch) {
140+
// If the module is not loaded, we can fetch it.
141+
const moduleBytecode = await getModuleInner({
142+
aptosConfig: this.config,
143+
accountAddress: moduleAddress,
144+
moduleName: moduleName.toString(),
145+
});
146+
if (moduleBytecode) {
147+
this.storeModule(moduleBytecode, moduleId);
115148
} else {
116149
throw new Error(
117-
`Module '${moduleAddress}::${moduleName}' is not loaded in the cache. Please load it before using it in a batched call.`
150+
`Module '${moduleAddress}::${moduleName}' could not be fetched. Please ensure it exists on the chain.`
118151
);
119152
}
120153
}
@@ -190,11 +223,11 @@ export class AptosScriptComposer {
190223
}
191224

192225
/**
193-
* Recursively collects all required module IDs from a given TypeTag, ensuring all dependent modules are present in the cache.
194-
* If allowFetch is true, missing modules will be fetched from the chain and added to the cache.
226+
* Recursively collects all required module IDs from a given TypeTag, ensuring all dependent modules are loaded and stored.
227+
* If allowFetch is true, missing modules will be fetched from the chain and stored in both the global cache and local composer.
195228
*
196229
* @param typeTag - The TypeTag to analyze (can be struct, vector, etc.).
197-
* @param options - Optional object. If options.allowFetch is true, missing modules will be fetched from the chain.
230+
* @param options - Optional object. If options.allowFetch is true (default), missing modules will be fetched from the chain.
198231
* @returns Promise<Set<string>> - A set of all module IDs (address::moduleName) required by the typeTag and its nested type arguments.
199232
* @throws Error if a required module is missing and allowFetch is false, or if fetching fails.
200233
*/
@@ -207,8 +240,9 @@ export class AptosScriptComposer {
207240
const structTag = typeTag as TypeTagStruct;
208241
const moduleId = `${structTag.value.address}::${structTag.value.moduleName.identifier.toString()}`;
209242
modules.add(moduleId);
243+
const autoFetch = options?.allowFetch ?? true;
210244
if (!AptosScriptComposer.loadedModulesCache.has(moduleId)) {
211-
if (options?.allowFetch) {
245+
if (autoFetch) {
212246
// If the module is not loaded, we can fetch it.
213247
const module = await getModuleInner({
214248
aptosConfig: this.config,

packages/sdk/tests/composer.test.ts

Lines changed: 115 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,11 @@ test('test composer build txn', async () => {
7070
function: '0x1::coin::withdraw',
7171
functionArguments: [CallArgument.newSigner(0), 1],
7272
typeArguments: ['0x1::aptos_coin::AptosCoin'],
73-
moduleAbi: coin_module.abi!,
73+
moduleAbi: coin_module.abi,
74+
moduleBytecodes: [coin_module.bytecode],
75+
options: {
76+
allowFetch: false,
77+
},
7478
});
7579

7680
// Passing the coin value to the 0x1::coin::coin_to_fungible_asset to convert a coin
@@ -80,15 +84,23 @@ test('test composer build txn', async () => {
8084
// coin[0] represents the first return value from the first call you added.
8185
functionArguments: [coin[0]],
8286
typeArguments: ['0x1::aptos_coin::AptosCoin'],
83-
moduleAbi: coin_module.abi!,
87+
moduleAbi: coin_module.abi,
88+
moduleBytecodes: [coin_module.bytecode],
89+
options: {
90+
allowFetch: false,
91+
},
8492
});
8593

8694
// Deposit the fungibleAsset converted from second call.
8795
await builder.addBatchedCalls({
8896
function: '0x1::primary_fungible_store::deposit',
8997
functionArguments: ['0x1', fungibleAsset[0]],
9098
typeArguments: [],
91-
moduleAbi: primary_fungible_store_module.abi!,
99+
moduleAbi: primary_fungible_store_module.abi,
100+
moduleBytecodes: [primary_fungible_store_module.bytecode],
101+
options: {
102+
allowFetch: false,
103+
},
92104
});
93105

94106
const tx = builder.build();
@@ -108,7 +120,11 @@ test('test composer build Payload', async () => {
108120
function: '0x1::coin::withdraw',
109121
functionArguments: [CallArgument.newSigner(0), 1],
110122
typeArguments: ['0x1::aptos_coin::AptosCoin'],
111-
moduleAbi: coin_module.abi!,
123+
moduleAbi: coin_module.abi,
124+
moduleBytecodes: [coin_module.bytecode],
125+
options: {
126+
allowFetch: false,
127+
},
112128
});
113129

114130
// Passing the coin value to the 0x1::coin::coin_to_fungible_asset to convert a coin
@@ -119,14 +135,22 @@ test('test composer build Payload', async () => {
119135
functionArguments: [coin[0]],
120136
typeArguments: ['0x1::aptos_coin::AptosCoin'],
121137
moduleAbi: coin_module.abi!,
138+
moduleBytecodes: [coin_module.bytecode],
139+
options: {
140+
allowFetch: false,
141+
},
122142
});
123143

124144
// Deposit the fungibleAsset converted from second call.
125145
await builder.addBatchedCalls({
126146
function: '0x1::primary_fungible_store::deposit',
127147
functionArguments: ['0x1', fungibleAsset[0]],
128148
typeArguments: [],
129-
moduleAbi: primary_fungible_store_module.abi!,
149+
moduleAbi: primary_fungible_store_module.abi,
150+
moduleBytecodes: [primary_fungible_store_module.bytecode],
151+
options: {
152+
allowFetch: false,
153+
},
130154
});
131155

132156
const payload = builder.build_payload();
@@ -148,7 +172,11 @@ test('test composer build Txn', async () => {
148172
function: '0x1::coin::withdraw',
149173
functionArguments: [CallArgument.newSigner(0), 1],
150174
typeArguments: ['0x1::aptos_coin::AptosCoin'],
151-
moduleAbi: coin_module.abi!,
175+
moduleAbi: coin_module.abi,
176+
moduleBytecodes: [coin_module.bytecode],
177+
options: {
178+
allowFetch: false,
179+
},
152180
});
153181

154182
// Passing the coin value to the 0x1::coin::coin_to_fungible_asset to convert a coin
@@ -158,15 +186,23 @@ test('test composer build Txn', async () => {
158186
// coin[0] represents the first return value from the first call you added.
159187
functionArguments: [coin[0]],
160188
typeArguments: ['0x1::aptos_coin::AptosCoin'],
161-
moduleAbi: coin_module.abi!,
189+
moduleAbi: coin_module.abi,
190+
moduleBytecodes: [coin_module.bytecode],
191+
options: {
192+
allowFetch: false,
193+
},
162194
});
163195

164196
// Deposit the fungibleAsset converted from second call.
165197
await builder.addBatchedCalls({
166198
function: '0x1::primary_fungible_store::deposit',
167199
functionArguments: ['0x1', fungibleAsset[0]],
168200
typeArguments: [],
169-
moduleAbi: primary_fungible_store_module.abi!,
201+
moduleAbi: primary_fungible_store_module.abi,
202+
moduleBytecodes: [primary_fungible_store_module.bytecode],
203+
options: {
204+
allowFetch: false,
205+
},
170206
});
171207

172208
return builder;
@@ -175,3 +211,74 @@ test('test composer build Txn', async () => {
175211

176212
expect(txn).toBeDefined();
177213
});
214+
215+
test('test composer with fetch enabled', async () => {
216+
const builder = new AptosScriptComposer(new AptosConfig({ network: Network.TESTNET }));
217+
218+
// Test with allowFetch enabled (default behavior)
219+
// This should automatically fetch the module from the chain without needing to store it first
220+
const coin = await builder.addBatchedCalls({
221+
function: '0x1::coin::withdraw',
222+
functionArguments: [CallArgument.newSigner(0), 1000000], // 0.01 APT
223+
typeArguments: ['0x1::aptos_coin::AptosCoin'],
224+
// Note: Not providing moduleAbi or moduleBytecodes - should be fetched automatically
225+
options: {
226+
allowFetch: true, // Explicitly enable fetch (this is the default)
227+
},
228+
});
229+
230+
// Test chaining with another function that also uses fetch
231+
const fungibleAsset = await builder.addBatchedCalls({
232+
function: '0x1::coin::coin_to_fungible_asset',
233+
functionArguments: [coin[0]],
234+
typeArguments: ['0x1::aptos_coin::AptosCoin'],
235+
// Again, not providing moduleAbi or moduleBytecodes
236+
options: {
237+
allowFetch: true,
238+
},
239+
});
240+
241+
// Final deposit call
242+
await builder.addBatchedCalls({
243+
function: '0x1::primary_fungible_store::deposit',
244+
functionArguments: ['0x1', fungibleAsset[0]],
245+
typeArguments: [],
246+
// Testing default behavior (allowFetch should be true by default)
247+
// options: { allowFetch: true } // This is the default, so we can omit it
248+
});
249+
250+
const tx = builder.build();
251+
expect(tx).toBeDefined();
252+
expect(tx.length).toBeGreaterThan(0);
253+
});
254+
255+
test('test composer fetch vs no-fetch behavior', async () => {
256+
// Test that fetch is required when allowFetch is false and no ABI/bytecode provided
257+
const builder = new AptosScriptComposer(new AptosConfig({ network: Network.TESTNET }));
258+
259+
// This should fail because allowFetch is false but no moduleAbi provided
260+
await expect(
261+
builder.addBatchedCalls({
262+
function: '0x1::coin::withdraw',
263+
functionArguments: [CallArgument.newSigner(0), 1000000],
264+
typeArguments: ['0x1::aptos_coin::AptosCoin'],
265+
options: {
266+
allowFetch: false,
267+
},
268+
})
269+
).rejects.toThrow('Module ABI is required when auto-fetch is disabled');
270+
271+
// This should also fail because allowFetch is false but no moduleBytecodes provided
272+
await expect(
273+
builder.addBatchedCalls({
274+
function: '0x1::coin::withdraw',
275+
functionArguments: [CallArgument.newSigner(0), 1000000],
276+
typeArguments: ['0x1::aptos_coin::AptosCoin'],
277+
moduleAbi: coin_module.abi,
278+
// Missing moduleBytecodes
279+
options: {
280+
allowFetch: false,
281+
},
282+
})
283+
).rejects.toThrow('Module bytecode is required when auto-fetch is disabled');
284+
});

0 commit comments

Comments
 (0)