@movebridge/codegen 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
File without changes
@@ -0,0 +1,230 @@
1
+ // src/parser.ts
2
+ import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk";
3
+ import { NETWORK_CONFIG, Errors } from "@movebridge/core";
4
+ var ABIParser = class {
5
+ aptosClient;
6
+ constructor(network) {
7
+ const config = NETWORK_CONFIG[network];
8
+ const aptosConfig = new AptosConfig({
9
+ network: Network.CUSTOM,
10
+ fullnode: config.rpcUrl
11
+ });
12
+ this.aptosClient = new Aptos(aptosConfig);
13
+ }
14
+ /**
15
+ * Fetches the ABI for a module
16
+ * @param moduleAddress - Full module address (e.g., '0x1::coin')
17
+ * @returns Module ABI
18
+ */
19
+ async fetchABI(moduleAddress) {
20
+ const parts = moduleAddress.split("::");
21
+ if (parts.length !== 2) {
22
+ throw Errors.invalidArgument(
23
+ "moduleAddress",
24
+ "Expected format: 0xADDRESS::module_name"
25
+ );
26
+ }
27
+ const address = parts[0];
28
+ const moduleName = parts[1];
29
+ if (!address || !moduleName) {
30
+ throw Errors.invalidArgument(
31
+ "moduleAddress",
32
+ "Expected format: 0xADDRESS::module_name"
33
+ );
34
+ }
35
+ try {
36
+ const client = this.aptosClient;
37
+ const modules = await client.getAccountModules({
38
+ accountAddress: address
39
+ });
40
+ const targetModule = modules.find((m) => {
41
+ const abi2 = m.abi;
42
+ return abi2?.name === moduleName;
43
+ });
44
+ if (!targetModule || !targetModule.abi) {
45
+ throw Errors.abiFetchFailed(moduleAddress, "Module not found");
46
+ }
47
+ const abi = targetModule.abi;
48
+ const exposedFunctions = abi.exposed_functions.map((fn) => ({
49
+ name: fn.name,
50
+ visibility: fn.visibility,
51
+ isEntry: fn.is_entry,
52
+ isView: fn.is_view,
53
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
+ genericTypeParams: fn.generic_type_params.map((p) => ({
55
+ constraints: p.constraints
56
+ })),
57
+ params: fn.params,
58
+ return: fn.return
59
+ }));
60
+ return {
61
+ address,
62
+ name: moduleName,
63
+ exposedFunctions
64
+ };
65
+ } catch (error) {
66
+ if (error instanceof Error && error.message.includes("Module not found")) {
67
+ throw error;
68
+ }
69
+ throw Errors.abiFetchFailed(moduleAddress, "network", error);
70
+ }
71
+ }
72
+ /**
73
+ * Parses a Move type to TypeScript type
74
+ * @param moveType - Move type string
75
+ * @returns TypeScript type string
76
+ */
77
+ parseType(moveType) {
78
+ const typeMap = {
79
+ "bool": "boolean",
80
+ "u8": "number",
81
+ "u16": "number",
82
+ "u32": "number",
83
+ "u64": "string",
84
+ "u128": "string",
85
+ "u256": "string",
86
+ "address": "string",
87
+ "&signer": "void",
88
+ "signer": "void",
89
+ "0x1::string::String": "string"
90
+ };
91
+ if (typeMap[moveType]) {
92
+ return typeMap[moveType];
93
+ }
94
+ if (moveType.startsWith("vector<")) {
95
+ const innerType = moveType.slice(7, -1);
96
+ return `${this.parseType(innerType)}[]`;
97
+ }
98
+ if (/^T\d+$/.test(moveType)) {
99
+ return moveType;
100
+ }
101
+ return "unknown";
102
+ }
103
+ };
104
+
105
+ // src/generator.ts
106
+ var TypeGenerator = class {
107
+ parser;
108
+ constructor(parser) {
109
+ this.parser = parser;
110
+ }
111
+ /**
112
+ * Generates a TypeScript class from a module ABI
113
+ * @param abi - Module ABI
114
+ * @returns TypeScript code string
115
+ */
116
+ generateClass(abi) {
117
+ const className = this.toPascalCase(abi.name) + "Contract";
118
+ const lines = [];
119
+ lines.push("/**");
120
+ lines.push(` * Auto-generated TypeScript bindings for ${abi.address}::${abi.name}`);
121
+ lines.push(" * Generated by @movebridge/codegen");
122
+ lines.push(" */");
123
+ lines.push("");
124
+ lines.push("import type { Movement } from '@movebridge/core';");
125
+ lines.push("");
126
+ lines.push(`export class ${className} {`);
127
+ lines.push(` private readonly movement: Movement;`);
128
+ lines.push(` private readonly address = '${abi.address}';`);
129
+ lines.push(` private readonly module = '${abi.name}';`);
130
+ lines.push("");
131
+ lines.push(" /**");
132
+ lines.push(` * Creates a new ${className} instance`);
133
+ lines.push(" * @param movement - Movement SDK instance");
134
+ lines.push(" */");
135
+ lines.push(" constructor(movement: Movement) {");
136
+ lines.push(" this.movement = movement;");
137
+ lines.push(" }");
138
+ lines.push("");
139
+ for (const fn of abi.exposedFunctions) {
140
+ if (fn.visibility === "private") continue;
141
+ if (fn.params.length === 1 && fn.params[0] === "&signer") continue;
142
+ const method = this.generateMethod(fn);
143
+ lines.push(method);
144
+ lines.push("");
145
+ }
146
+ lines.push("}");
147
+ return lines.join("\n");
148
+ }
149
+ /**
150
+ * Generates a method from a function ABI
151
+ * @param fn - Function ABI
152
+ * @returns Method code string
153
+ */
154
+ generateMethod(fn) {
155
+ const lines = [];
156
+ const methodName = this.toCamelCase(fn.name);
157
+ const params = fn.params.filter((p) => p !== "&signer" && p !== "signer");
158
+ const paramNames = params.map((_, i) => `arg${i}`);
159
+ const paramTypes = params.map((p) => this.parser.parseType(p));
160
+ let genericParams = "";
161
+ if (fn.genericTypeParams.length > 0) {
162
+ const typeParams = fn.genericTypeParams.map((_, i) => `T${i} = unknown`);
163
+ genericParams = `<${typeParams.join(", ")}>`;
164
+ }
165
+ const paramList = paramNames.map((name, i) => `${name}: ${paramTypes[i]}`).join(", ");
166
+ const hasTypeArgs = fn.genericTypeParams.length > 0;
167
+ const typeArgsParam = hasTypeArgs ? ", typeArgs: string[]" : "";
168
+ let returnType;
169
+ if (fn.isView) {
170
+ if (fn.return.length === 0) {
171
+ returnType = "void";
172
+ } else if (fn.return.length === 1) {
173
+ returnType = this.parser.parseType(fn.return[0] ?? "unknown");
174
+ } else {
175
+ returnType = `[${fn.return.map((r) => this.parser.parseType(r ?? "unknown")).join(", ")}]`;
176
+ }
177
+ } else {
178
+ returnType = "string";
179
+ }
180
+ lines.push(" /**");
181
+ lines.push(` * ${fn.isView ? "View" : "Entry"} function: ${fn.name}`);
182
+ paramNames.forEach((name, i) => {
183
+ lines.push(` * @param ${name} - Parameter of type ${params[i]}`);
184
+ });
185
+ if (hasTypeArgs) {
186
+ lines.push(" * @param typeArgs - Type arguments");
187
+ }
188
+ lines.push(` * @returns ${fn.isView ? "Function result" : "Transaction hash"}`);
189
+ lines.push(" */");
190
+ const asyncKeyword = "async";
191
+ lines.push(
192
+ ` ${asyncKeyword} ${methodName}${genericParams}(${paramList}${typeArgsParam}): Promise<${returnType}> {`
193
+ );
194
+ const argsArray = paramNames.length > 0 ? `[${paramNames.join(", ")}]` : "[]";
195
+ const typeArgsArray = hasTypeArgs ? "typeArgs" : "[]";
196
+ if (fn.isView) {
197
+ lines.push(` const contract = this.movement.contract({`);
198
+ lines.push(` address: this.address,`);
199
+ lines.push(` module: this.module,`);
200
+ lines.push(` });`);
201
+ lines.push(` return contract.view<${returnType}>('${fn.name}', ${argsArray}, ${typeArgsArray});`);
202
+ } else {
203
+ lines.push(` const contract = this.movement.contract({`);
204
+ lines.push(` address: this.address,`);
205
+ lines.push(` module: this.module,`);
206
+ lines.push(` });`);
207
+ lines.push(` return contract.call('${fn.name}', ${argsArray}, ${typeArgsArray});`);
208
+ }
209
+ lines.push(" }");
210
+ return lines.join("\n");
211
+ }
212
+ /**
213
+ * Converts a snake_case string to PascalCase
214
+ */
215
+ toPascalCase(str) {
216
+ return str.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
217
+ }
218
+ /**
219
+ * Converts a snake_case string to camelCase
220
+ */
221
+ toCamelCase(str) {
222
+ const pascal = this.toPascalCase(str);
223
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
224
+ }
225
+ };
226
+
227
+ export {
228
+ ABIParser,
229
+ TypeGenerator
230
+ };
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,260 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // src/cli.ts
5
+ var import_commander = require("commander");
6
+ var import_promises = require("fs/promises");
7
+ var import_path = require("path");
8
+
9
+ // src/parser.ts
10
+ var import_ts_sdk = require("@aptos-labs/ts-sdk");
11
+ var import_core = require("@movebridge/core");
12
+ var ABIParser = class {
13
+ aptosClient;
14
+ constructor(network) {
15
+ const config = import_core.NETWORK_CONFIG[network];
16
+ const aptosConfig = new import_ts_sdk.AptosConfig({
17
+ network: import_ts_sdk.Network.CUSTOM,
18
+ fullnode: config.rpcUrl
19
+ });
20
+ this.aptosClient = new import_ts_sdk.Aptos(aptosConfig);
21
+ }
22
+ /**
23
+ * Fetches the ABI for a module
24
+ * @param moduleAddress - Full module address (e.g., '0x1::coin')
25
+ * @returns Module ABI
26
+ */
27
+ async fetchABI(moduleAddress) {
28
+ const parts = moduleAddress.split("::");
29
+ if (parts.length !== 2) {
30
+ throw import_core.Errors.invalidArgument(
31
+ "moduleAddress",
32
+ "Expected format: 0xADDRESS::module_name"
33
+ );
34
+ }
35
+ const address = parts[0];
36
+ const moduleName = parts[1];
37
+ if (!address || !moduleName) {
38
+ throw import_core.Errors.invalidArgument(
39
+ "moduleAddress",
40
+ "Expected format: 0xADDRESS::module_name"
41
+ );
42
+ }
43
+ try {
44
+ const client = this.aptosClient;
45
+ const modules = await client.getAccountModules({
46
+ accountAddress: address
47
+ });
48
+ const targetModule = modules.find((m) => {
49
+ const abi2 = m.abi;
50
+ return abi2?.name === moduleName;
51
+ });
52
+ if (!targetModule || !targetModule.abi) {
53
+ throw import_core.Errors.abiFetchFailed(moduleAddress, "Module not found");
54
+ }
55
+ const abi = targetModule.abi;
56
+ const exposedFunctions = abi.exposed_functions.map((fn) => ({
57
+ name: fn.name,
58
+ visibility: fn.visibility,
59
+ isEntry: fn.is_entry,
60
+ isView: fn.is_view,
61
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
62
+ genericTypeParams: fn.generic_type_params.map((p) => ({
63
+ constraints: p.constraints
64
+ })),
65
+ params: fn.params,
66
+ return: fn.return
67
+ }));
68
+ return {
69
+ address,
70
+ name: moduleName,
71
+ exposedFunctions
72
+ };
73
+ } catch (error) {
74
+ if (error instanceof Error && error.message.includes("Module not found")) {
75
+ throw error;
76
+ }
77
+ throw import_core.Errors.abiFetchFailed(moduleAddress, "network", error);
78
+ }
79
+ }
80
+ /**
81
+ * Parses a Move type to TypeScript type
82
+ * @param moveType - Move type string
83
+ * @returns TypeScript type string
84
+ */
85
+ parseType(moveType) {
86
+ const typeMap = {
87
+ "bool": "boolean",
88
+ "u8": "number",
89
+ "u16": "number",
90
+ "u32": "number",
91
+ "u64": "string",
92
+ "u128": "string",
93
+ "u256": "string",
94
+ "address": "string",
95
+ "&signer": "void",
96
+ "signer": "void",
97
+ "0x1::string::String": "string"
98
+ };
99
+ if (typeMap[moveType]) {
100
+ return typeMap[moveType];
101
+ }
102
+ if (moveType.startsWith("vector<")) {
103
+ const innerType = moveType.slice(7, -1);
104
+ return `${this.parseType(innerType)}[]`;
105
+ }
106
+ if (/^T\d+$/.test(moveType)) {
107
+ return moveType;
108
+ }
109
+ return "unknown";
110
+ }
111
+ };
112
+
113
+ // src/generator.ts
114
+ var TypeGenerator = class {
115
+ parser;
116
+ constructor(parser) {
117
+ this.parser = parser;
118
+ }
119
+ /**
120
+ * Generates a TypeScript class from a module ABI
121
+ * @param abi - Module ABI
122
+ * @returns TypeScript code string
123
+ */
124
+ generateClass(abi) {
125
+ const className = this.toPascalCase(abi.name) + "Contract";
126
+ const lines = [];
127
+ lines.push("/**");
128
+ lines.push(` * Auto-generated TypeScript bindings for ${abi.address}::${abi.name}`);
129
+ lines.push(" * Generated by @movebridge/codegen");
130
+ lines.push(" */");
131
+ lines.push("");
132
+ lines.push("import type { Movement } from '@movebridge/core';");
133
+ lines.push("");
134
+ lines.push(`export class ${className} {`);
135
+ lines.push(` private readonly movement: Movement;`);
136
+ lines.push(` private readonly address = '${abi.address}';`);
137
+ lines.push(` private readonly module = '${abi.name}';`);
138
+ lines.push("");
139
+ lines.push(" /**");
140
+ lines.push(` * Creates a new ${className} instance`);
141
+ lines.push(" * @param movement - Movement SDK instance");
142
+ lines.push(" */");
143
+ lines.push(" constructor(movement: Movement) {");
144
+ lines.push(" this.movement = movement;");
145
+ lines.push(" }");
146
+ lines.push("");
147
+ for (const fn of abi.exposedFunctions) {
148
+ if (fn.visibility === "private") continue;
149
+ if (fn.params.length === 1 && fn.params[0] === "&signer") continue;
150
+ const method = this.generateMethod(fn);
151
+ lines.push(method);
152
+ lines.push("");
153
+ }
154
+ lines.push("}");
155
+ return lines.join("\n");
156
+ }
157
+ /**
158
+ * Generates a method from a function ABI
159
+ * @param fn - Function ABI
160
+ * @returns Method code string
161
+ */
162
+ generateMethod(fn) {
163
+ const lines = [];
164
+ const methodName = this.toCamelCase(fn.name);
165
+ const params = fn.params.filter((p) => p !== "&signer" && p !== "signer");
166
+ const paramNames = params.map((_, i) => `arg${i}`);
167
+ const paramTypes = params.map((p) => this.parser.parseType(p));
168
+ let genericParams = "";
169
+ if (fn.genericTypeParams.length > 0) {
170
+ const typeParams = fn.genericTypeParams.map((_, i) => `T${i} = unknown`);
171
+ genericParams = `<${typeParams.join(", ")}>`;
172
+ }
173
+ const paramList = paramNames.map((name, i) => `${name}: ${paramTypes[i]}`).join(", ");
174
+ const hasTypeArgs = fn.genericTypeParams.length > 0;
175
+ const typeArgsParam = hasTypeArgs ? ", typeArgs: string[]" : "";
176
+ let returnType;
177
+ if (fn.isView) {
178
+ if (fn.return.length === 0) {
179
+ returnType = "void";
180
+ } else if (fn.return.length === 1) {
181
+ returnType = this.parser.parseType(fn.return[0] ?? "unknown");
182
+ } else {
183
+ returnType = `[${fn.return.map((r) => this.parser.parseType(r ?? "unknown")).join(", ")}]`;
184
+ }
185
+ } else {
186
+ returnType = "string";
187
+ }
188
+ lines.push(" /**");
189
+ lines.push(` * ${fn.isView ? "View" : "Entry"} function: ${fn.name}`);
190
+ paramNames.forEach((name, i) => {
191
+ lines.push(` * @param ${name} - Parameter of type ${params[i]}`);
192
+ });
193
+ if (hasTypeArgs) {
194
+ lines.push(" * @param typeArgs - Type arguments");
195
+ }
196
+ lines.push(` * @returns ${fn.isView ? "Function result" : "Transaction hash"}`);
197
+ lines.push(" */");
198
+ const asyncKeyword = "async";
199
+ lines.push(
200
+ ` ${asyncKeyword} ${methodName}${genericParams}(${paramList}${typeArgsParam}): Promise<${returnType}> {`
201
+ );
202
+ const argsArray = paramNames.length > 0 ? `[${paramNames.join(", ")}]` : "[]";
203
+ const typeArgsArray = hasTypeArgs ? "typeArgs" : "[]";
204
+ if (fn.isView) {
205
+ lines.push(` const contract = this.movement.contract({`);
206
+ lines.push(` address: this.address,`);
207
+ lines.push(` module: this.module,`);
208
+ lines.push(` });`);
209
+ lines.push(` return contract.view<${returnType}>('${fn.name}', ${argsArray}, ${typeArgsArray});`);
210
+ } else {
211
+ lines.push(` const contract = this.movement.contract({`);
212
+ lines.push(` address: this.address,`);
213
+ lines.push(` module: this.module,`);
214
+ lines.push(` });`);
215
+ lines.push(` return contract.call('${fn.name}', ${argsArray}, ${typeArgsArray});`);
216
+ }
217
+ lines.push(" }");
218
+ return lines.join("\n");
219
+ }
220
+ /**
221
+ * Converts a snake_case string to PascalCase
222
+ */
223
+ toPascalCase(str) {
224
+ return str.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
225
+ }
226
+ /**
227
+ * Converts a snake_case string to camelCase
228
+ */
229
+ toCamelCase(str) {
230
+ const pascal = this.toPascalCase(str);
231
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
232
+ }
233
+ };
234
+
235
+ // src/cli.ts
236
+ var program = new import_commander.Command();
237
+ program.name("movebridge-gen").description("Generate TypeScript bindings from Move modules").version("0.0.1").requiredOption("-a, --address <address>", "Module address (e.g., 0x1::coin)").requiredOption("-n, --network <network>", "Network (mainnet or testnet)").requiredOption("-o, --output <path>", "Output file path").action(async (options) => {
238
+ const { address, network, output } = options;
239
+ if (network !== "mainnet" && network !== "testnet") {
240
+ console.error('Error: Network must be "mainnet" or "testnet"');
241
+ process.exit(1);
242
+ }
243
+ try {
244
+ console.log(`Fetching ABI for ${address} on ${network}...`);
245
+ const parser = new ABIParser(network);
246
+ const abi = await parser.fetchABI(address);
247
+ console.log(`Found module: ${abi.name} with ${abi.exposedFunctions.length} functions`);
248
+ const generator = new TypeGenerator(parser);
249
+ const code = generator.generateClass(abi);
250
+ const outputDir = (0, import_path.dirname)(output);
251
+ await (0, import_promises.mkdir)(outputDir, { recursive: true });
252
+ await (0, import_promises.writeFile)(output, code, "utf-8");
253
+ console.log(`Generated: ${output}`);
254
+ console.log("Done!");
255
+ } catch (error) {
256
+ console.error("Error:", error instanceof Error ? error.message : error);
257
+ process.exit(1);
258
+ }
259
+ });
260
+ program.parse();
package/dist/cli.mjs ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ABIParser,
4
+ TypeGenerator
5
+ } from "./chunk-NMH4XS2A.mjs";
6
+
7
+ // src/cli.ts
8
+ import { Command } from "commander";
9
+ import { writeFile, mkdir } from "fs/promises";
10
+ import { dirname } from "path";
11
+ var program = new Command();
12
+ program.name("movebridge-gen").description("Generate TypeScript bindings from Move modules").version("0.0.1").requiredOption("-a, --address <address>", "Module address (e.g., 0x1::coin)").requiredOption("-n, --network <network>", "Network (mainnet or testnet)").requiredOption("-o, --output <path>", "Output file path").action(async (options) => {
13
+ const { address, network, output } = options;
14
+ if (network !== "mainnet" && network !== "testnet") {
15
+ console.error('Error: Network must be "mainnet" or "testnet"');
16
+ process.exit(1);
17
+ }
18
+ try {
19
+ console.log(`Fetching ABI for ${address} on ${network}...`);
20
+ const parser = new ABIParser(network);
21
+ const abi = await parser.fetchABI(address);
22
+ console.log(`Found module: ${abi.name} with ${abi.exposedFunctions.length} functions`);
23
+ const generator = new TypeGenerator(parser);
24
+ const code = generator.generateClass(abi);
25
+ const outputDir = dirname(output);
26
+ await mkdir(outputDir, { recursive: true });
27
+ await writeFile(output, code, "utf-8");
28
+ console.log(`Generated: ${output}`);
29
+ console.log("Done!");
30
+ } catch (error) {
31
+ console.error("Error:", error instanceof Error ? error.message : error);
32
+ process.exit(1);
33
+ }
34
+ });
35
+ program.parse();
@@ -0,0 +1,89 @@
1
+ import { NetworkType } from '@movebridge/core';
2
+
3
+ /**
4
+ * @movebridge/codegen - ABI Parser
5
+ * Fetches and parses Move module ABIs
6
+ */
7
+
8
+ /**
9
+ * Type parameter definition
10
+ */
11
+ interface TypeParam {
12
+ constraints: string[];
13
+ }
14
+ /**
15
+ * Function ABI definition
16
+ */
17
+ interface FunctionABI {
18
+ name: string;
19
+ visibility: 'public' | 'private' | 'friend';
20
+ isEntry: boolean;
21
+ isView: boolean;
22
+ genericTypeParams: TypeParam[];
23
+ params: string[];
24
+ return: string[];
25
+ }
26
+ /**
27
+ * Module ABI definition
28
+ */
29
+ interface ModuleABI {
30
+ address: string;
31
+ name: string;
32
+ exposedFunctions: FunctionABI[];
33
+ }
34
+ /**
35
+ * ABI Parser
36
+ * Fetches and parses Move module ABIs from the network
37
+ */
38
+ declare class ABIParser {
39
+ private aptosClient;
40
+ constructor(network: NetworkType);
41
+ /**
42
+ * Fetches the ABI for a module
43
+ * @param moduleAddress - Full module address (e.g., '0x1::coin')
44
+ * @returns Module ABI
45
+ */
46
+ fetchABI(moduleAddress: string): Promise<ModuleABI>;
47
+ /**
48
+ * Parses a Move type to TypeScript type
49
+ * @param moveType - Move type string
50
+ * @returns TypeScript type string
51
+ */
52
+ parseType(moveType: string): string;
53
+ }
54
+
55
+ /**
56
+ * @movebridge/codegen - Type Generator
57
+ * Generates TypeScript code from Move module ABIs
58
+ */
59
+
60
+ /**
61
+ * Type Generator
62
+ * Generates TypeScript classes from Move module ABIs
63
+ */
64
+ declare class TypeGenerator {
65
+ private parser;
66
+ constructor(parser: ABIParser);
67
+ /**
68
+ * Generates a TypeScript class from a module ABI
69
+ * @param abi - Module ABI
70
+ * @returns TypeScript code string
71
+ */
72
+ generateClass(abi: ModuleABI): string;
73
+ /**
74
+ * Generates a method from a function ABI
75
+ * @param fn - Function ABI
76
+ * @returns Method code string
77
+ */
78
+ private generateMethod;
79
+ /**
80
+ * Converts a snake_case string to PascalCase
81
+ */
82
+ private toPascalCase;
83
+ /**
84
+ * Converts a snake_case string to camelCase
85
+ */
86
+ private toCamelCase;
87
+ }
88
+
89
+ export { ABIParser, type FunctionABI, type ModuleABI, TypeGenerator, type TypeParam };
@@ -0,0 +1,89 @@
1
+ import { NetworkType } from '@movebridge/core';
2
+
3
+ /**
4
+ * @movebridge/codegen - ABI Parser
5
+ * Fetches and parses Move module ABIs
6
+ */
7
+
8
+ /**
9
+ * Type parameter definition
10
+ */
11
+ interface TypeParam {
12
+ constraints: string[];
13
+ }
14
+ /**
15
+ * Function ABI definition
16
+ */
17
+ interface FunctionABI {
18
+ name: string;
19
+ visibility: 'public' | 'private' | 'friend';
20
+ isEntry: boolean;
21
+ isView: boolean;
22
+ genericTypeParams: TypeParam[];
23
+ params: string[];
24
+ return: string[];
25
+ }
26
+ /**
27
+ * Module ABI definition
28
+ */
29
+ interface ModuleABI {
30
+ address: string;
31
+ name: string;
32
+ exposedFunctions: FunctionABI[];
33
+ }
34
+ /**
35
+ * ABI Parser
36
+ * Fetches and parses Move module ABIs from the network
37
+ */
38
+ declare class ABIParser {
39
+ private aptosClient;
40
+ constructor(network: NetworkType);
41
+ /**
42
+ * Fetches the ABI for a module
43
+ * @param moduleAddress - Full module address (e.g., '0x1::coin')
44
+ * @returns Module ABI
45
+ */
46
+ fetchABI(moduleAddress: string): Promise<ModuleABI>;
47
+ /**
48
+ * Parses a Move type to TypeScript type
49
+ * @param moveType - Move type string
50
+ * @returns TypeScript type string
51
+ */
52
+ parseType(moveType: string): string;
53
+ }
54
+
55
+ /**
56
+ * @movebridge/codegen - Type Generator
57
+ * Generates TypeScript code from Move module ABIs
58
+ */
59
+
60
+ /**
61
+ * Type Generator
62
+ * Generates TypeScript classes from Move module ABIs
63
+ */
64
+ declare class TypeGenerator {
65
+ private parser;
66
+ constructor(parser: ABIParser);
67
+ /**
68
+ * Generates a TypeScript class from a module ABI
69
+ * @param abi - Module ABI
70
+ * @returns TypeScript code string
71
+ */
72
+ generateClass(abi: ModuleABI): string;
73
+ /**
74
+ * Generates a method from a function ABI
75
+ * @param fn - Function ABI
76
+ * @returns Method code string
77
+ */
78
+ private generateMethod;
79
+ /**
80
+ * Converts a snake_case string to PascalCase
81
+ */
82
+ private toPascalCase;
83
+ /**
84
+ * Converts a snake_case string to camelCase
85
+ */
86
+ private toCamelCase;
87
+ }
88
+
89
+ export { ABIParser, type FunctionABI, type ModuleABI, TypeGenerator, type TypeParam };
package/dist/index.js ADDED
@@ -0,0 +1,257 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ABIParser: () => ABIParser,
24
+ TypeGenerator: () => TypeGenerator
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/parser.ts
29
+ var import_ts_sdk = require("@aptos-labs/ts-sdk");
30
+ var import_core = require("@movebridge/core");
31
+ var ABIParser = class {
32
+ aptosClient;
33
+ constructor(network) {
34
+ const config = import_core.NETWORK_CONFIG[network];
35
+ const aptosConfig = new import_ts_sdk.AptosConfig({
36
+ network: import_ts_sdk.Network.CUSTOM,
37
+ fullnode: config.rpcUrl
38
+ });
39
+ this.aptosClient = new import_ts_sdk.Aptos(aptosConfig);
40
+ }
41
+ /**
42
+ * Fetches the ABI for a module
43
+ * @param moduleAddress - Full module address (e.g., '0x1::coin')
44
+ * @returns Module ABI
45
+ */
46
+ async fetchABI(moduleAddress) {
47
+ const parts = moduleAddress.split("::");
48
+ if (parts.length !== 2) {
49
+ throw import_core.Errors.invalidArgument(
50
+ "moduleAddress",
51
+ "Expected format: 0xADDRESS::module_name"
52
+ );
53
+ }
54
+ const address = parts[0];
55
+ const moduleName = parts[1];
56
+ if (!address || !moduleName) {
57
+ throw import_core.Errors.invalidArgument(
58
+ "moduleAddress",
59
+ "Expected format: 0xADDRESS::module_name"
60
+ );
61
+ }
62
+ try {
63
+ const client = this.aptosClient;
64
+ const modules = await client.getAccountModules({
65
+ accountAddress: address
66
+ });
67
+ const targetModule = modules.find((m) => {
68
+ const abi2 = m.abi;
69
+ return abi2?.name === moduleName;
70
+ });
71
+ if (!targetModule || !targetModule.abi) {
72
+ throw import_core.Errors.abiFetchFailed(moduleAddress, "Module not found");
73
+ }
74
+ const abi = targetModule.abi;
75
+ const exposedFunctions = abi.exposed_functions.map((fn) => ({
76
+ name: fn.name,
77
+ visibility: fn.visibility,
78
+ isEntry: fn.is_entry,
79
+ isView: fn.is_view,
80
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
+ genericTypeParams: fn.generic_type_params.map((p) => ({
82
+ constraints: p.constraints
83
+ })),
84
+ params: fn.params,
85
+ return: fn.return
86
+ }));
87
+ return {
88
+ address,
89
+ name: moduleName,
90
+ exposedFunctions
91
+ };
92
+ } catch (error) {
93
+ if (error instanceof Error && error.message.includes("Module not found")) {
94
+ throw error;
95
+ }
96
+ throw import_core.Errors.abiFetchFailed(moduleAddress, "network", error);
97
+ }
98
+ }
99
+ /**
100
+ * Parses a Move type to TypeScript type
101
+ * @param moveType - Move type string
102
+ * @returns TypeScript type string
103
+ */
104
+ parseType(moveType) {
105
+ const typeMap = {
106
+ "bool": "boolean",
107
+ "u8": "number",
108
+ "u16": "number",
109
+ "u32": "number",
110
+ "u64": "string",
111
+ "u128": "string",
112
+ "u256": "string",
113
+ "address": "string",
114
+ "&signer": "void",
115
+ "signer": "void",
116
+ "0x1::string::String": "string"
117
+ };
118
+ if (typeMap[moveType]) {
119
+ return typeMap[moveType];
120
+ }
121
+ if (moveType.startsWith("vector<")) {
122
+ const innerType = moveType.slice(7, -1);
123
+ return `${this.parseType(innerType)}[]`;
124
+ }
125
+ if (/^T\d+$/.test(moveType)) {
126
+ return moveType;
127
+ }
128
+ return "unknown";
129
+ }
130
+ };
131
+
132
+ // src/generator.ts
133
+ var TypeGenerator = class {
134
+ parser;
135
+ constructor(parser) {
136
+ this.parser = parser;
137
+ }
138
+ /**
139
+ * Generates a TypeScript class from a module ABI
140
+ * @param abi - Module ABI
141
+ * @returns TypeScript code string
142
+ */
143
+ generateClass(abi) {
144
+ const className = this.toPascalCase(abi.name) + "Contract";
145
+ const lines = [];
146
+ lines.push("/**");
147
+ lines.push(` * Auto-generated TypeScript bindings for ${abi.address}::${abi.name}`);
148
+ lines.push(" * Generated by @movebridge/codegen");
149
+ lines.push(" */");
150
+ lines.push("");
151
+ lines.push("import type { Movement } from '@movebridge/core';");
152
+ lines.push("");
153
+ lines.push(`export class ${className} {`);
154
+ lines.push(` private readonly movement: Movement;`);
155
+ lines.push(` private readonly address = '${abi.address}';`);
156
+ lines.push(` private readonly module = '${abi.name}';`);
157
+ lines.push("");
158
+ lines.push(" /**");
159
+ lines.push(` * Creates a new ${className} instance`);
160
+ lines.push(" * @param movement - Movement SDK instance");
161
+ lines.push(" */");
162
+ lines.push(" constructor(movement: Movement) {");
163
+ lines.push(" this.movement = movement;");
164
+ lines.push(" }");
165
+ lines.push("");
166
+ for (const fn of abi.exposedFunctions) {
167
+ if (fn.visibility === "private") continue;
168
+ if (fn.params.length === 1 && fn.params[0] === "&signer") continue;
169
+ const method = this.generateMethod(fn);
170
+ lines.push(method);
171
+ lines.push("");
172
+ }
173
+ lines.push("}");
174
+ return lines.join("\n");
175
+ }
176
+ /**
177
+ * Generates a method from a function ABI
178
+ * @param fn - Function ABI
179
+ * @returns Method code string
180
+ */
181
+ generateMethod(fn) {
182
+ const lines = [];
183
+ const methodName = this.toCamelCase(fn.name);
184
+ const params = fn.params.filter((p) => p !== "&signer" && p !== "signer");
185
+ const paramNames = params.map((_, i) => `arg${i}`);
186
+ const paramTypes = params.map((p) => this.parser.parseType(p));
187
+ let genericParams = "";
188
+ if (fn.genericTypeParams.length > 0) {
189
+ const typeParams = fn.genericTypeParams.map((_, i) => `T${i} = unknown`);
190
+ genericParams = `<${typeParams.join(", ")}>`;
191
+ }
192
+ const paramList = paramNames.map((name, i) => `${name}: ${paramTypes[i]}`).join(", ");
193
+ const hasTypeArgs = fn.genericTypeParams.length > 0;
194
+ const typeArgsParam = hasTypeArgs ? ", typeArgs: string[]" : "";
195
+ let returnType;
196
+ if (fn.isView) {
197
+ if (fn.return.length === 0) {
198
+ returnType = "void";
199
+ } else if (fn.return.length === 1) {
200
+ returnType = this.parser.parseType(fn.return[0] ?? "unknown");
201
+ } else {
202
+ returnType = `[${fn.return.map((r) => this.parser.parseType(r ?? "unknown")).join(", ")}]`;
203
+ }
204
+ } else {
205
+ returnType = "string";
206
+ }
207
+ lines.push(" /**");
208
+ lines.push(` * ${fn.isView ? "View" : "Entry"} function: ${fn.name}`);
209
+ paramNames.forEach((name, i) => {
210
+ lines.push(` * @param ${name} - Parameter of type ${params[i]}`);
211
+ });
212
+ if (hasTypeArgs) {
213
+ lines.push(" * @param typeArgs - Type arguments");
214
+ }
215
+ lines.push(` * @returns ${fn.isView ? "Function result" : "Transaction hash"}`);
216
+ lines.push(" */");
217
+ const asyncKeyword = "async";
218
+ lines.push(
219
+ ` ${asyncKeyword} ${methodName}${genericParams}(${paramList}${typeArgsParam}): Promise<${returnType}> {`
220
+ );
221
+ const argsArray = paramNames.length > 0 ? `[${paramNames.join(", ")}]` : "[]";
222
+ const typeArgsArray = hasTypeArgs ? "typeArgs" : "[]";
223
+ if (fn.isView) {
224
+ lines.push(` const contract = this.movement.contract({`);
225
+ lines.push(` address: this.address,`);
226
+ lines.push(` module: this.module,`);
227
+ lines.push(` });`);
228
+ lines.push(` return contract.view<${returnType}>('${fn.name}', ${argsArray}, ${typeArgsArray});`);
229
+ } else {
230
+ lines.push(` const contract = this.movement.contract({`);
231
+ lines.push(` address: this.address,`);
232
+ lines.push(` module: this.module,`);
233
+ lines.push(` });`);
234
+ lines.push(` return contract.call('${fn.name}', ${argsArray}, ${typeArgsArray});`);
235
+ }
236
+ lines.push(" }");
237
+ return lines.join("\n");
238
+ }
239
+ /**
240
+ * Converts a snake_case string to PascalCase
241
+ */
242
+ toPascalCase(str) {
243
+ return str.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
244
+ }
245
+ /**
246
+ * Converts a snake_case string to camelCase
247
+ */
248
+ toCamelCase(str) {
249
+ const pascal = this.toPascalCase(str);
250
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
251
+ }
252
+ };
253
+ // Annotate the CommonJS export names for ESM import in node:
254
+ 0 && (module.exports = {
255
+ ABIParser,
256
+ TypeGenerator
257
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,8 @@
1
+ import {
2
+ ABIParser,
3
+ TypeGenerator
4
+ } from "./chunk-NMH4XS2A.mjs";
5
+ export {
6
+ ABIParser,
7
+ TypeGenerator
8
+ };
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@movebridge/codegen",
3
+ "version": "0.0.1",
4
+ "description": "Code generation tool for type-safe Movement contract interactions",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "movebridge-gen": "./dist/cli.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.mjs",
15
+ "require": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "dependencies": {
22
+ "@aptos-labs/ts-sdk": "^1.26.0",
23
+ "commander": "^11.1.0",
24
+ "@movebridge/core": "0.0.1"
25
+ },
26
+ "devDependencies": {
27
+ "tsup": "^8.0.1",
28
+ "typescript": "^5.3.2"
29
+ },
30
+ "keywords": [
31
+ "movement",
32
+ "aptos",
33
+ "codegen",
34
+ "typescript",
35
+ "cli"
36
+ ],
37
+ "author": "Aqila Rifti",
38
+ "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/AqilaRifti/MoveBridge",
42
+ "directory": "packages/codegen"
43
+ },
44
+ "homepage": "https://github.com/AqilaRifti/MoveBridge#readme",
45
+ "bugs": {
46
+ "url": "https://github.com/AqilaRifti/MoveBridge/issues"
47
+ },
48
+ "scripts": {
49
+ "build": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --clean",
50
+ "dev": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --watch",
51
+ "clean": "rm -rf dist",
52
+ "typecheck": "tsc --noEmit",
53
+ "test": "vitest run",
54
+ "test:watch": "vitest"
55
+ }
56
+ }