@awarizon/cli 1.0.0

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.
Files changed (2) hide show
  1. package/dist/index.mjs +253 -0
  2. package/package.json +30 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,253 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/generate.ts
7
+ import fs from "fs-extra";
8
+ import path from "path";
9
+
10
+ // src/codegen.ts
11
+ import { parseABI, generateAllMethodSignatures } from "@awarizon/abi-engine";
12
+ function generateContractClient(contractName, address, abi) {
13
+ const parsed = parseABI(abi);
14
+ const methods = generateAllMethodSignatures(parsed);
15
+ const readMethods = methods.filter((m) => m.kind === "read");
16
+ const writeMethods = methods.filter((m) => m.kind !== "read");
17
+ const methodDocs = methods.map((m) => {
18
+ const tag = m.kind === "read" ? "@read" : m.kind === "payable" ? "@write @payable" : "@write";
19
+ return ` /**
20
+ * ${tag}
21
+ * @signature ${m.signature}
22
+ */
23
+ ${m.signature};`;
24
+ }).join("\n\n");
25
+ const methodImpls = methods.map((m) => {
26
+ const params = m.params.map((p) => p.name).join(", ");
27
+ const args = params ? `[${params}]` : "[]";
28
+ if (m.kind === "read") {
29
+ return ` async ${m.name}(${m.params.map((p) => `${p.name}: ${p.type}`).join(", ")}) {
30
+ return this._contract.${m.name}(${params});
31
+ }`;
32
+ }
33
+ return ` async ${m.name}(${m.params.map((p) => `${p.name}: ${p.type}`).join(", ")}) {
34
+ return this._contract.${m.name}(${params});
35
+ }`;
36
+ }).join("\n\n");
37
+ return `// Auto-generated by @awarizon/cli \u2014 do not edit manually
38
+ // Contract: ${contractName}
39
+ // Address: ${address}
40
+ // Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
41
+
42
+ import { AwarizonWeb3 } from "@awarizon/web3";
43
+ import type { ContractInstance, TransactionResult } from "@awarizon/web3";
44
+
45
+ const CONTRACT_ADDRESS = "${address}" as const;
46
+
47
+ const ABI = ${JSON.stringify(abi, null, 2)} as const;
48
+
49
+ // \u2500\u2500\u2500 Typed interface \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
50
+
51
+ export interface ${contractName}Contract {
52
+ ${methodDocs}
53
+
54
+ on(event: string, callback: (log: unknown) => void): () => void;
55
+ estimateGas(method: string, ...args: unknown[]): Promise<bigint>;
56
+ }
57
+
58
+ // \u2500\u2500\u2500 Client class \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
59
+
60
+ export class ${contractName}Client {
61
+ private _contract!: ContractInstance;
62
+
63
+ private constructor() {}
64
+
65
+ static async create(sdk: AwarizonWeb3): Promise<${contractName}Client> {
66
+ const client = new ${contractName}Client();
67
+ client._contract = await sdk.contract({ address: CONTRACT_ADDRESS, abi: ABI });
68
+ return client;
69
+ }
70
+
71
+ ${methodImpls}
72
+
73
+ on(event: string, callback: (log: unknown) => void) {
74
+ return this._contract.on(event, callback);
75
+ }
76
+
77
+ async estimateGas(method: string, ...args: unknown[]) {
78
+ return this._contract.estimateGas(method, ...args);
79
+ }
80
+ }
81
+ `;
82
+ }
83
+ function generateReactHooks(contractName, address, abi) {
84
+ const parsed = parseABI(abi);
85
+ const methods = generateAllMethodSignatures(parsed);
86
+ const readHooks = parsed.readFunctions.map((fn) => {
87
+ const meta = methods.find((m) => m.name === fn.name);
88
+ const paramList = meta.params.map((p) => `${p.name}: ${p.type}`).join(", ");
89
+ const paramNames = meta.params.map((p) => p.name).join(", ");
90
+ const argsExpr = paramNames ? `[${paramNames}]` : "[]";
91
+ return `/**
92
+ * Hook to read \`${fn.name}\` from ${contractName}.
93
+ * @example
94
+ * const { data } = useRead${capitalize(fn.name)}(${paramNames})
95
+ */
96
+ export function useRead${capitalize(fn.name)}(${paramList}) {
97
+ return useReadContract({
98
+ address: "${address}",
99
+ abi: ${contractName.toUpperCase()}_ABI as any,
100
+ method: "${fn.name}",
101
+ args: ${argsExpr},
102
+ });
103
+ }`;
104
+ }).join("\n\n");
105
+ const writeHooks = parsed.writeFunctions.map((fn) => {
106
+ return `/**
107
+ * Hook to call \`${fn.name}\` on ${contractName}.
108
+ * @example
109
+ * const { write } = useWrite${capitalize(fn.name)}()
110
+ * await write(${parsed.writeFunctions.find((f) => f.name === fn.name)?.inputs.map((i) => i.name).join(", ")})
111
+ */
112
+ export function useWrite${capitalize(fn.name)}() {
113
+ return useWriteContract({
114
+ address: "${address}",
115
+ abi: ${contractName.toUpperCase()}_ABI as any,
116
+ method: "${fn.name}",
117
+ });
118
+ }`;
119
+ }).join("\n\n");
120
+ const eventSubscriptions = parsed.events.map((ev) => {
121
+ return `/**
122
+ * Hook to subscribe to the \`${ev.name}\` event on ${contractName}.
123
+ */
124
+ export function use${contractName}${capitalize(ev.name)}Event(
125
+ callback: (log: unknown) => void,
126
+ enabled = true,
127
+ ) {
128
+ const { contract } = useContract({ address: "${address}", abi: ${contractName.toUpperCase()}_ABI as any });
129
+
130
+ useEffect(() => {
131
+ if (!enabled || !contract) return;
132
+ const unsubscribe = contract.on("${ev.name}", callback);
133
+ return unsubscribe;
134
+ }, [contract, enabled, callback]);
135
+ }`;
136
+ }).join("\n\n");
137
+ return `// Auto-generated by @awarizon/cli \u2014 do not edit manually
138
+ // React hooks for: ${contractName}
139
+ // Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
140
+
141
+ import { useEffect } from "react";
142
+ import {
143
+ useContract,
144
+ useReadContract,
145
+ useWriteContract,
146
+ } from "@awarizon/react";
147
+
148
+ const ${contractName.toUpperCase()}_ABI = ${JSON.stringify(abi, null, 2)} as const;
149
+
150
+ // \u2500\u2500\u2500 Read hooks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
151
+
152
+ ${readHooks}
153
+
154
+ // \u2500\u2500\u2500 Write hooks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
155
+
156
+ ${writeHooks}
157
+
158
+ // \u2500\u2500\u2500 Event hooks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
159
+
160
+ ${eventSubscriptions}
161
+ `;
162
+ }
163
+ function capitalize(s) {
164
+ return s.charAt(0).toUpperCase() + s.slice(1);
165
+ }
166
+
167
+ // src/commands/generate.ts
168
+ function registerGenerateCommand(program2) {
169
+ program2.command("generate").alias("gen").description("Generate a typed contract client and React hooks from an ABI file").requiredOption("-a, --abi <path>", "Path to ABI JSON file").requiredOption("-n, --name <name>", "Contract name in PascalCase (e.g. StakingPool)").requiredOption("--address <address>", "Deployed contract address (0x...)").option("-o, --out <dir>", "Output directory", "./src/contracts").option("--no-react", "Skip React hooks generation").action(async (opts) => {
170
+ const chalk = (await import("chalk")).default;
171
+ const ora = (await import("ora")).default;
172
+ const spinner = ora("Reading ABI\u2026").start();
173
+ try {
174
+ const abiPath = path.resolve(process.cwd(), opts.abi);
175
+ if (!fs.existsSync(abiPath)) {
176
+ spinner.fail(chalk.red(`ABI file not found: ${abiPath}`));
177
+ process.exit(1);
178
+ }
179
+ const raw = await fs.readJson(abiPath);
180
+ const abi = Array.isArray(raw) ? raw : raw.abi;
181
+ if (!Array.isArray(abi)) {
182
+ spinner.fail(chalk.red('Invalid ABI: expected an array or an object with an "abi" field.'));
183
+ process.exit(1);
184
+ }
185
+ const outDir = path.resolve(process.cwd(), opts.out);
186
+ await fs.ensureDir(outDir);
187
+ spinner.text = `Generating ${opts.name}Client\u2026`;
188
+ const clientCode = generateContractClient(opts.name, opts.address, abi);
189
+ const clientFile = path.join(outDir, `${opts.name}Client.ts`);
190
+ await fs.writeFile(clientFile, clientCode, "utf-8");
191
+ let hooksFile = null;
192
+ if (opts.react !== false) {
193
+ spinner.text = `Generating React hooks for ${opts.name}\u2026`;
194
+ const hooksCode = generateReactHooks(opts.name, opts.address, abi);
195
+ hooksFile = path.join(outDir, `use${opts.name}.ts`);
196
+ await fs.writeFile(hooksFile, hooksCode, "utf-8");
197
+ }
198
+ spinner.succeed(chalk.green(`Generated ${opts.name} contract client`));
199
+ console.log("");
200
+ console.log(chalk.bold(" Output files:"));
201
+ console.log(` ${chalk.cyan("\u2192")} ${clientFile}`);
202
+ if (hooksFile) console.log(` ${chalk.cyan("\u2192")} ${hooksFile}`);
203
+ console.log("");
204
+ console.log(chalk.bold(" Usage:"));
205
+ console.log(` ${chalk.gray("// Instantiate")}`);
206
+ console.log(` ${chalk.white(`const client = await ${opts.name}Client.create(sdk)`)}`);
207
+ console.log("");
208
+ } catch (err) {
209
+ spinner.fail(chalk.red(`Generation failed: ${err.message}`));
210
+ process.exit(1);
211
+ }
212
+ });
213
+ }
214
+
215
+ // src/index.ts
216
+ var program = new Command();
217
+ program.name("awarizon").description(
218
+ "Awarizon Web3 CLI \u2014 generate typed contract clients and React hooks from ABI files"
219
+ ).version("1.0.0");
220
+ registerGenerateCommand(program);
221
+ program.command("info").description("Print SDK version and supported chains").action(async () => {
222
+ const chalk = (await import("chalk")).default;
223
+ console.log("");
224
+ console.log(chalk.bold.cyan(" @awarizon/web3 \u2014 EVM Blockchain SDK"));
225
+ console.log(` ${"\u2500".repeat(40)}`);
226
+ console.log(` ${chalk.bold("Version:")} 1.0.0`);
227
+ console.log(` ${chalk.bold("Engine:")} viem`);
228
+ console.log("");
229
+ console.log(` ${chalk.bold("Supported chains:")}`);
230
+ const chains = [
231
+ "mainnet / ethereum",
232
+ "base",
233
+ "base-sepolia",
234
+ "polygon",
235
+ "arbitrum",
236
+ "optimism",
237
+ "bsc",
238
+ "avalanche",
239
+ "sepolia",
240
+ "linea",
241
+ "scroll",
242
+ "zora",
243
+ "mantle",
244
+ "celo"
245
+ ];
246
+ for (const c of chains) {
247
+ console.log(` ${chalk.cyan("\u2022")} ${c}`);
248
+ }
249
+ console.log("");
250
+ console.log(` ${chalk.dim("https://github.com/awarizon/web3")}`);
251
+ console.log("");
252
+ });
253
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@awarizon/cli",
3
+ "version": "1.0.0",
4
+ "description": "CLI for Awarizon Web3 SDK — generate typed contract clients and React hooks",
5
+ "bin": { "awarizon": "./dist/index.js" },
6
+ "main": "./dist/index.js",
7
+ "files": ["dist"],
8
+ "scripts": {
9
+ "build": "tsup",
10
+ "dev": "tsup --watch",
11
+ "test": "vitest run",
12
+ "typecheck": "tsc --noEmit",
13
+ "clean": "rimraf dist"
14
+ },
15
+ "dependencies": {
16
+ "@awarizon/abi-engine": "workspace:*",
17
+ "commander": "^12.0.0",
18
+ "chalk": "^5.3.0",
19
+ "ora": "^8.0.0",
20
+ "fs-extra": "^11.2.0"
21
+ },
22
+ "devDependencies": {
23
+ "typescript": "^5.5.0",
24
+ "tsup": "^8.2.0",
25
+ "vitest": "^2.0.0",
26
+ "@types/fs-extra": "^11.0.0",
27
+ "viem": "^2.21.0",
28
+ "rimraf": "^5.0.0"
29
+ }
30
+ }