Skip to content

Commit 49494df

Browse files
Add ABI JSON generation tool for entry and view functions
Adds utility functions to fetch on-chain module ABIs and produce JSON-serializable representations of entry/view function ABIs: - generateModuleAbiJSON: fetches all entry/view ABIs for a module - generateEntryFunctionAbiJSON: fetches a single entry function ABI - generateViewFunctionAbiJSON: fetches a single view function ABI - parseEntryFunctionAbiJSON: hydrates JSON back to EntryFunctionABI - parseViewFunctionAbiJSON: hydrates JSON back to ViewFunctionABI This allows users to pre-generate ABI JSON, store it, and use it at runtime without remote ABI fetching overhead. Includes unit tests (12 cases) and e2e tests (13 cases) covering generation, parsing, round-trip serialization, and transaction building. Co-authored-by: Greg Nazario <greg@gnazar.io>
1 parent 4d3fb37 commit 49494df

File tree

5 files changed

+734
-0
lines changed

5 files changed

+734
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ All notable changes to the Aptos TypeScript SDK will be captured in this file. T
1717
## Added
1818

1919
- Add e2e tests for external signer flow (build → getSigningMessage → sign externally → submit) to verify the flow works correctly with the latest SDK version
20+
- Add ABI JSON generation tool: `generateModuleAbiJSON`, `generateEntryFunctionAbiJSON`, `generateViewFunctionAbiJSON` to fetch on-chain module ABIs and produce JSON-serializable output; `parseEntryFunctionAbiJSON` and `parseViewFunctionAbiJSON` to hydrate them back into SDK-native `EntryFunctionABI`/`ViewFunctionABI` types
2021
- Add MultiKey (K-of-N mixed key types) transfer example (`examples/typescript/multikey_transfer.ts`)
2122
- Add MultiEd25519 (K-of-N Ed25519) transfer example (`examples/typescript/multi_ed25519_transfer.ts`)
2223

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
// Copyright © Aptos Foundation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { AptosConfig } from "../../api/aptosConfig";
5+
import { AccountAddressInput } from "../../core";
6+
import { getModule } from "../../internal/account";
7+
import { MoveFunction, MoveFunctionGenericTypeParam } from "../../types";
8+
import { parseTypeTag } from "../typeTag/parser";
9+
import { TypeTag } from "../typeTag";
10+
import { EntryFunctionABI, ViewFunctionABI } from "../types";
11+
import { findFirstNonSignerArg } from "./helpers";
12+
13+
/**
14+
* JSON-serializable representation of an entry function ABI.
15+
* Uses string type tags instead of TypeTag objects so the output
16+
* can be serialized with `JSON.stringify` and stored or embedded in source code.
17+
*
18+
* @group Implementation
19+
* @category Transactions
20+
*/
21+
export type EntryFunctionAbiJSON = {
22+
typeParameters: Array<{ constraints: string[] }>;
23+
parameters: string[];
24+
signers?: number;
25+
};
26+
27+
/**
28+
* JSON-serializable representation of a view function ABI.
29+
* Uses string type tags instead of TypeTag objects so the output
30+
* can be serialized with `JSON.stringify` and stored or embedded in source code.
31+
*
32+
* @group Implementation
33+
* @category Transactions
34+
*/
35+
export type ViewFunctionAbiJSON = {
36+
typeParameters: Array<{ constraints: string[] }>;
37+
parameters: string[];
38+
returnTypes: string[];
39+
};
40+
41+
/**
42+
* JSON-serializable representation of all entry and view function ABIs in a module.
43+
*
44+
* @group Implementation
45+
* @category Transactions
46+
*/
47+
export type ModuleAbiJSON = {
48+
address: string;
49+
name: string;
50+
entryFunctions: Record<string, EntryFunctionAbiJSON>;
51+
viewFunctions: Record<string, ViewFunctionAbiJSON>;
52+
};
53+
54+
function serializeGenericTypeParams(params: Array<MoveFunctionGenericTypeParam>): Array<{ constraints: string[] }> {
55+
return params.map((p) => ({ constraints: [...p.constraints] }));
56+
}
57+
58+
function moveFunctionToEntryAbiJSON(func: MoveFunction): EntryFunctionAbiJSON {
59+
const numSigners = findFirstNonSignerArg(func);
60+
const parameters = func.params.slice(numSigners);
61+
62+
return {
63+
typeParameters: serializeGenericTypeParams(func.generic_type_params),
64+
parameters,
65+
...(numSigners > 0 ? { signers: numSigners } : {}),
66+
};
67+
}
68+
69+
function moveFunctionToViewAbiJSON(func: MoveFunction): ViewFunctionAbiJSON {
70+
return {
71+
typeParameters: serializeGenericTypeParams(func.generic_type_params),
72+
parameters: [...func.params],
73+
returnTypes: [...func.return],
74+
};
75+
}
76+
77+
/**
78+
* Fetches a module's ABI from on-chain state and generates JSON-serializable
79+
* ABIs for all of its entry functions and view functions.
80+
*
81+
* The returned object can be serialized with `JSON.stringify` and later
82+
* converted back into SDK-native `EntryFunctionABI` / `ViewFunctionABI`
83+
* objects using {@link parseEntryFunctionAbiJSON} and {@link parseViewFunctionAbiJSON}.
84+
*
85+
* @example
86+
* ```typescript
87+
* import { Aptos, AptosConfig, Network, generateModuleAbiJSON } from "@aptos-labs/ts-sdk";
88+
*
89+
* const config = new AptosConfig({ network: Network.MAINNET });
90+
* const aptos = new Aptos(config);
91+
*
92+
* const moduleAbi = await generateModuleAbiJSON({
93+
* aptosConfig: aptos.config,
94+
* accountAddress: "0x1",
95+
* moduleName: "coin",
96+
* });
97+
*
98+
* console.log(JSON.stringify(moduleAbi, null, 2));
99+
* ```
100+
*
101+
* @param args.aptosConfig - The Aptos configuration (network, endpoints, etc.).
102+
* @param args.accountAddress - The on-chain address that published the module.
103+
* @param args.moduleName - The name of the Move module.
104+
* @returns A JSON-serializable object containing entry and view function ABIs.
105+
* @group Implementation
106+
* @category Transactions
107+
*/
108+
export async function generateModuleAbiJSON(args: {
109+
aptosConfig: AptosConfig;
110+
accountAddress: AccountAddressInput;
111+
moduleName: string;
112+
}): Promise<ModuleAbiJSON> {
113+
const { aptosConfig, accountAddress, moduleName } = args;
114+
115+
const moduleBytecode = await getModule({ aptosConfig, accountAddress, moduleName });
116+
const moduleAbi = moduleBytecode.abi;
117+
118+
if (!moduleAbi) {
119+
throw new Error(`No ABI found for module '${String(accountAddress)}::${moduleName}'`);
120+
}
121+
122+
const entryFunctions: Record<string, EntryFunctionAbiJSON> = {};
123+
const viewFunctions: Record<string, ViewFunctionAbiJSON> = {};
124+
125+
for (const func of moduleAbi.exposed_functions) {
126+
if (func.is_entry) {
127+
entryFunctions[func.name] = moveFunctionToEntryAbiJSON(func);
128+
}
129+
if (func.is_view) {
130+
viewFunctions[func.name] = moveFunctionToViewAbiJSON(func);
131+
}
132+
}
133+
134+
return {
135+
address: moduleAbi.address,
136+
name: moduleAbi.name,
137+
entryFunctions,
138+
viewFunctions,
139+
};
140+
}
141+
142+
/**
143+
* Fetches the ABI for a single entry function from on-chain state and
144+
* returns a JSON-serializable representation.
145+
*
146+
* @param args.aptosConfig - The Aptos configuration.
147+
* @param args.accountAddress - The module's account address.
148+
* @param args.moduleName - The Move module name.
149+
* @param args.functionName - The entry function name.
150+
* @returns A JSON-serializable entry function ABI.
151+
* @throws If the module or function cannot be found, or if the function is not an entry function.
152+
* @group Implementation
153+
* @category Transactions
154+
*/
155+
export async function generateEntryFunctionAbiJSON(args: {
156+
aptosConfig: AptosConfig;
157+
accountAddress: AccountAddressInput;
158+
moduleName: string;
159+
functionName: string;
160+
}): Promise<EntryFunctionAbiJSON> {
161+
const { aptosConfig, accountAddress, moduleName, functionName } = args;
162+
163+
const moduleBytecode = await getModule({ aptosConfig, accountAddress, moduleName });
164+
const moduleAbi = moduleBytecode.abi;
165+
166+
if (!moduleAbi) {
167+
throw new Error(`No ABI found for module '${String(accountAddress)}::${moduleName}'`);
168+
}
169+
170+
const func = moduleAbi.exposed_functions.find((f) => f.name === functionName);
171+
if (!func) {
172+
throw new Error(`Could not find function '${functionName}' in module '${String(accountAddress)}::${moduleName}'`);
173+
}
174+
if (!func.is_entry) {
175+
throw new Error(`'${String(accountAddress)}::${moduleName}::${functionName}' is not an entry function`);
176+
}
177+
178+
return moveFunctionToEntryAbiJSON(func);
179+
}
180+
181+
/**
182+
* Fetches the ABI for a single view function from on-chain state and
183+
* returns a JSON-serializable representation.
184+
*
185+
* @param args.aptosConfig - The Aptos configuration.
186+
* @param args.accountAddress - The module's account address.
187+
* @param args.moduleName - The Move module name.
188+
* @param args.functionName - The view function name.
189+
* @returns A JSON-serializable view function ABI.
190+
* @throws If the module or function cannot be found, or if the function is not a view function.
191+
* @group Implementation
192+
* @category Transactions
193+
*/
194+
export async function generateViewFunctionAbiJSON(args: {
195+
aptosConfig: AptosConfig;
196+
accountAddress: AccountAddressInput;
197+
moduleName: string;
198+
functionName: string;
199+
}): Promise<ViewFunctionAbiJSON> {
200+
const { aptosConfig, accountAddress, moduleName, functionName } = args;
201+
202+
const moduleBytecode = await getModule({ aptosConfig, accountAddress, moduleName });
203+
const moduleAbi = moduleBytecode.abi;
204+
205+
if (!moduleAbi) {
206+
throw new Error(`No ABI found for module '${String(accountAddress)}::${moduleName}'`);
207+
}
208+
209+
const func = moduleAbi.exposed_functions.find((f) => f.name === functionName);
210+
if (!func) {
211+
throw new Error(`Could not find function '${functionName}' in module '${String(accountAddress)}::${moduleName}'`);
212+
}
213+
if (!func.is_view) {
214+
throw new Error(`'${String(accountAddress)}::${moduleName}::${functionName}' is not a view function`);
215+
}
216+
217+
return moveFunctionToViewAbiJSON(func);
218+
}
219+
220+
function parseTypeTags(tags: string[]): TypeTag[] {
221+
return tags.map((t) => parseTypeTag(t, { allowGenerics: true }));
222+
}
223+
224+
/**
225+
* Converts a JSON-serializable entry function ABI back into an
226+
* SDK-native `EntryFunctionABI` with parsed `TypeTag` objects.
227+
*
228+
* Use this to hydrate ABIs that were previously generated with
229+
* {@link generateModuleAbiJSON} or {@link generateEntryFunctionAbiJSON}.
230+
*
231+
* @example
232+
* ```typescript
233+
* import { parseEntryFunctionAbiJSON, EntryFunctionAbiJSON } from "@aptos-labs/ts-sdk";
234+
*
235+
* const json: EntryFunctionAbiJSON = {
236+
* typeParameters: [{ constraints: [] }],
237+
* parameters: ["address", "u64"],
238+
* signers: 1,
239+
* };
240+
*
241+
* const abi = parseEntryFunctionAbiJSON(json);
242+
* // abi.parameters => [TypeTagAddress, TypeTagU64]
243+
* ```
244+
*
245+
* @param json - A JSON-serializable entry function ABI.
246+
* @returns An `EntryFunctionABI` with parsed `TypeTag` parameters.
247+
* @group Implementation
248+
* @category Transactions
249+
*/
250+
export function parseEntryFunctionAbiJSON(json: EntryFunctionAbiJSON): EntryFunctionABI {
251+
return {
252+
typeParameters: json.typeParameters.map((tp) => ({
253+
constraints: [...tp.constraints] as EntryFunctionABI["typeParameters"][0]["constraints"],
254+
})),
255+
parameters: parseTypeTags(json.parameters),
256+
...(json.signers != null ? { signers: json.signers } : {}),
257+
};
258+
}
259+
260+
/**
261+
* Converts a JSON-serializable view function ABI back into an
262+
* SDK-native `ViewFunctionABI` with parsed `TypeTag` objects.
263+
*
264+
* Use this to hydrate ABIs that were previously generated with
265+
* {@link generateModuleAbiJSON} or {@link generateViewFunctionAbiJSON}.
266+
*
267+
* @example
268+
* ```typescript
269+
* import { parseViewFunctionAbiJSON, ViewFunctionAbiJSON } from "@aptos-labs/ts-sdk";
270+
*
271+
* const json: ViewFunctionAbiJSON = {
272+
* typeParameters: [{ constraints: [] }],
273+
* parameters: ["address"],
274+
* returnTypes: ["u64"],
275+
* };
276+
*
277+
* const abi = parseViewFunctionAbiJSON(json);
278+
* // abi.parameters => [TypeTagAddress]
279+
* // abi.returnTypes => [TypeTagU64]
280+
* ```
281+
*
282+
* @param json - A JSON-serializable view function ABI.
283+
* @returns A `ViewFunctionABI` with parsed `TypeTag` parameters and return types.
284+
* @group Implementation
285+
* @category Transactions
286+
*/
287+
export function parseViewFunctionAbiJSON(json: ViewFunctionAbiJSON): ViewFunctionABI {
288+
return {
289+
typeParameters: json.typeParameters.map((tp) => ({
290+
constraints: [...tp.constraints] as ViewFunctionABI["typeParameters"][0]["constraints"],
291+
})),
292+
parameters: parseTypeTags(json.parameters),
293+
returnTypes: parseTypeTags(json.returnTypes),
294+
};
295+
}

src/transactions/transactionBuilder/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright © Aptos Foundation
22
// SPDX-License-Identifier: Apache-2.0
33

4+
export * from "./abiJson";
45
export * from "./helpers";
56
export * from "./transactionBuilder";
67
export * from "./remoteAbi";

0 commit comments

Comments
 (0)