sol-classer 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.
- package/README.md +89 -0
- package/dist/generator.d.ts +11 -0
- package/dist/generator.js +286 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +52 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +44 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Classer: ABI to TypeScript/JavaScript Class Generator
|
|
2
|
+
|
|
3
|
+
Classer is a CLI tool that automatically generates strongly-typed TypeScript or vanilla JavaScript classes from Ethereum ABI JSON/JS/TS files. It streamlines contract interaction by providing:
|
|
4
|
+
|
|
5
|
+
- **Strict Typing**: TypeScript types for all function inputs and outputs (optional).
|
|
6
|
+
- **Read/Write Separation**: Clear distinction between view functions and state-modifying transactions.
|
|
7
|
+
- **Provider Flexibility**: Support for both RPC (server-side) and `window.ethereum` (browser).
|
|
8
|
+
- **ERC20 Helpers**: Automatic injection of `getBalance` and `transferTokens` helpers for ERC20 contracts.
|
|
9
|
+
- **Robustness**: Automatic contract initialization, `setSigner` helpers, and error handling wrapping.
|
|
10
|
+
- **Ethers.js v6 Integration**: Built on top of the latest Ethers.js for robust interaction.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
To use it, you can install it globally or use `npx`:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g sol-classer
|
|
18
|
+
# OR
|
|
19
|
+
npx sol-classer [options]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
Basic usage:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
sol-classer \
|
|
28
|
+
--abi <path_to_abi> \
|
|
29
|
+
--name <ClassName> \
|
|
30
|
+
--address <ContractAddress> \
|
|
31
|
+
[options]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Options
|
|
35
|
+
|
|
36
|
+
- `-a, --abi <path>`: **Required**. Path to the ABI file (`.json`, `.js`, `.ts`).
|
|
37
|
+
- `-n, --name <name>`: **Required**. Name of the class to generate.
|
|
38
|
+
- `-x, --address <address>`: **Required**. The default contract address.
|
|
39
|
+
- `-o, --output <path>`: Optional. Output file path. Defaults to `<ClassName>.[ts|js]`.
|
|
40
|
+
- `--provider <type>`: Optional. `'rpc'` (default) or `'window'`.
|
|
41
|
+
- `--lang <language>`: Optional. `'ts'` (default) or `'js'`.
|
|
42
|
+
- `--rpc <variable>`: Optional. Env var for RPC URL (only for RPC provider). Defaults to `NEXT_PUBLIC_RPC`.
|
|
43
|
+
|
|
44
|
+
### Examples
|
|
45
|
+
|
|
46
|
+
**1. Standard TypeScript Class (RPC Provider)**
|
|
47
|
+
```bash
|
|
48
|
+
sol-classer \
|
|
49
|
+
--abi test/samples/ERC20.json \
|
|
50
|
+
--name MyToken \
|
|
51
|
+
--address 0x123... \
|
|
52
|
+
--provider rpc \
|
|
53
|
+
--lang ts
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**2. Browser/Frontend Class (Window Provider)**
|
|
57
|
+
This generates a class that connects to `window.ethereum` and handles wallet connection.
|
|
58
|
+
```bash
|
|
59
|
+
sol-classer \
|
|
60
|
+
--abi ABI/ABI.js \
|
|
61
|
+
--name FrontendToken \
|
|
62
|
+
--address 0x456... \
|
|
63
|
+
--provider window
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Usage in Frontend:**
|
|
67
|
+
```typescript
|
|
68
|
+
const token = new FrontendToken();
|
|
69
|
+
// Auto-initializes on first call, or call explicitly
|
|
70
|
+
await token.setSigner();
|
|
71
|
+
await token.transfer("0xUser", "10");
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**3. JavaScript Class (No Types)**
|
|
75
|
+
```bash
|
|
76
|
+
sol-classer \
|
|
77
|
+
--abi test/samples/ERC20.json \
|
|
78
|
+
--name LegacyToken \
|
|
79
|
+
--address 0x789... \
|
|
80
|
+
--lang js
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Generated Code Features
|
|
84
|
+
|
|
85
|
+
- **Automatic Initialization**: All methods call `initializeContract()` internally to ensure provider/signer availability.
|
|
86
|
+
- **Error Handling**: All contract calls are wrapped in `try-catch` blocks with informative error logging.
|
|
87
|
+
- **Extensible**: The generated class can be easily extended or modified.
|
|
88
|
+
- **Isomorphic**: Works in Node.js (RPC) and Browser (Window) environments.
|
|
89
|
+
- **Zero Config**: Just pass the ABI and you are good to go.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type ProviderType = 'rpc' | 'window';
|
|
2
|
+
export type Language = 'ts' | 'js';
|
|
3
|
+
export declare function solidityTypeToTs(type: string): string;
|
|
4
|
+
export declare function generateClass({ abi, className, contractAddress, rpcEnvVar, providerType, language }: {
|
|
5
|
+
abi: any[];
|
|
6
|
+
className: string;
|
|
7
|
+
contractAddress: string;
|
|
8
|
+
rpcEnvVar?: string;
|
|
9
|
+
providerType?: ProviderType;
|
|
10
|
+
language?: Language;
|
|
11
|
+
}): string;
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.solidityTypeToTs = solidityTypeToTs;
|
|
4
|
+
exports.generateClass = generateClass;
|
|
5
|
+
function solidityTypeToTs(type) {
|
|
6
|
+
if (type.startsWith("uint") || type.startsWith("int")) {
|
|
7
|
+
return "string | number | bigint";
|
|
8
|
+
}
|
|
9
|
+
if (type === "address")
|
|
10
|
+
return "string";
|
|
11
|
+
if (type === "bool")
|
|
12
|
+
return "boolean";
|
|
13
|
+
if (type === "string")
|
|
14
|
+
return "string";
|
|
15
|
+
if (type.startsWith("bytes"))
|
|
16
|
+
return "string";
|
|
17
|
+
if (type === "tuple")
|
|
18
|
+
return "any"; // TODO: Handle tuples better if needed
|
|
19
|
+
if (type.endsWith("[]"))
|
|
20
|
+
return "any[]"; // Arrays
|
|
21
|
+
return "any";
|
|
22
|
+
}
|
|
23
|
+
function isReadFunction(fn) {
|
|
24
|
+
return (fn.stateMutability === "view" ||
|
|
25
|
+
fn.stateMutability === "pure");
|
|
26
|
+
}
|
|
27
|
+
function isWriteFunction(fn) {
|
|
28
|
+
return (fn.stateMutability === "nonpayable" ||
|
|
29
|
+
fn.stateMutability === "payable");
|
|
30
|
+
}
|
|
31
|
+
function generateParams(inputs, language) {
|
|
32
|
+
if (!inputs || inputs.length === 0)
|
|
33
|
+
return "";
|
|
34
|
+
return inputs
|
|
35
|
+
.map((i, idx) => {
|
|
36
|
+
const name = i.name || `arg${idx}`;
|
|
37
|
+
return language === 'ts'
|
|
38
|
+
? `${name}: ${solidityTypeToTs(i.type)}`
|
|
39
|
+
: name;
|
|
40
|
+
})
|
|
41
|
+
.join(", ");
|
|
42
|
+
}
|
|
43
|
+
function generateReadMethod(fn, language) {
|
|
44
|
+
const params = generateParams(fn.inputs, language);
|
|
45
|
+
const argNames = fn.inputs ? fn.inputs.map((i, idx) => i.name || `arg${idx}`).join(", ") : "";
|
|
46
|
+
const returnType = language === 'ts' ? ": Promise<any>" : "";
|
|
47
|
+
return `
|
|
48
|
+
async ${fn.name}(${params})${returnType} {
|
|
49
|
+
try {
|
|
50
|
+
await this.initializeContract();
|
|
51
|
+
return await this.contract.${fn.name}(${argNames});
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error("Failed to read ${fn.name}:", error);
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
`;
|
|
58
|
+
}
|
|
59
|
+
function generateWriteMethod(fn, language) {
|
|
60
|
+
const params = generateParams(fn.inputs, language);
|
|
61
|
+
const argNames = fn.inputs ? fn.inputs.map((i, idx) => i.name || `arg${idx}`).join(", ") : "";
|
|
62
|
+
const returnType = language === 'ts' ? ": Promise<ethers.ContractTransactionResponse>" : "";
|
|
63
|
+
return `
|
|
64
|
+
async ${fn.name}(${params})${returnType} {
|
|
65
|
+
try {
|
|
66
|
+
await this.initializeContract();
|
|
67
|
+
if (!this.contract.runner) {
|
|
68
|
+
throw new Error("Signer required for ${fn.name}. Ensure you have connected a wallet.");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const tx = await this.contract.${fn.name}(${argNames});
|
|
72
|
+
await tx.wait();
|
|
73
|
+
return tx;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error("Failed to execute ${fn.name}:", error);
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
function isERC20(abi) {
|
|
82
|
+
const names = abi
|
|
83
|
+
.filter((x) => x.type === "function")
|
|
84
|
+
.map((x) => x.name);
|
|
85
|
+
return (names.includes("decimals") &&
|
|
86
|
+
names.includes("symbol") &&
|
|
87
|
+
names.includes("balanceOf") &&
|
|
88
|
+
names.includes("transfer"));
|
|
89
|
+
}
|
|
90
|
+
function generateClass({ abi, className, contractAddress, rpcEnvVar = "NEXT_PUBLIC_RPC", providerType = 'rpc', language = 'ts' }) {
|
|
91
|
+
const readFns = abi.filter((x) => x.type === "function" && isReadFunction(x));
|
|
92
|
+
const writeFns = abi.filter((x) => x.type === "function" && isWriteFunction(x));
|
|
93
|
+
const isErc20Token = isERC20(abi);
|
|
94
|
+
const isTs = language === 'ts';
|
|
95
|
+
let erc20Methods = "";
|
|
96
|
+
if (isErc20Token) {
|
|
97
|
+
const balanceReturnType = isTs ? ": Promise<string>" : "";
|
|
98
|
+
const txReturnType = isTs ? ": Promise<ethers.ContractTransactionResponse>" : "";
|
|
99
|
+
const addressType = isTs ? ": string" : "";
|
|
100
|
+
const amountType = isTs ? ": string" : "";
|
|
101
|
+
erc20Methods = `
|
|
102
|
+
// -------------------------
|
|
103
|
+
// ERC20 Helpers
|
|
104
|
+
// -------------------------
|
|
105
|
+
|
|
106
|
+
async getBalance(address${addressType})${balanceReturnType} {
|
|
107
|
+
try {
|
|
108
|
+
await this.initializeContract();
|
|
109
|
+
const balance = await this.contract.balanceOf(address);
|
|
110
|
+
const decimals = await this.contract.decimals();
|
|
111
|
+
return ethers.formatUnits(balance, decimals);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error("Failed to get balance:", error);
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async transferTokens(to${addressType}, amount${amountType})${txReturnType} {
|
|
119
|
+
try {
|
|
120
|
+
await this.initializeContract();
|
|
121
|
+
if (!this.contract.runner) {
|
|
122
|
+
throw new Error("Signer required for transferTokens");
|
|
123
|
+
}
|
|
124
|
+
const decimals = await this.contract.decimals();
|
|
125
|
+
const parsedAmount = ethers.parseUnits(amount, decimals);
|
|
126
|
+
const tx = await this.contract.transfer(to, parsedAmount);
|
|
127
|
+
await tx.wait();
|
|
128
|
+
return tx;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error("Failed to transfer tokens:", error);
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
`;
|
|
135
|
+
}
|
|
136
|
+
// Type Definitions
|
|
137
|
+
const fieldTypes = isTs ? `
|
|
138
|
+
private provider: ethers.Provider | ethers.BrowserProvider | null;
|
|
139
|
+
private signer: ethers.Signer | null;
|
|
140
|
+
private contract: ethers.Contract | null;
|
|
141
|
+
private contractAddress: string;
|
|
142
|
+
` : "";
|
|
143
|
+
// Constructor logic
|
|
144
|
+
let constructorCode = "";
|
|
145
|
+
let methodsCode = "";
|
|
146
|
+
if (providerType === 'rpc') {
|
|
147
|
+
const ctorParams = isTs
|
|
148
|
+
? `contractAddress: string = "${contractAddress}", privateKey?: string, rpcUrl?: string`
|
|
149
|
+
: `contractAddress = "${contractAddress}", privateKey, rpcUrl`;
|
|
150
|
+
constructorCode = `
|
|
151
|
+
constructor(${ctorParams}) {
|
|
152
|
+
this.contractAddress = contractAddress;
|
|
153
|
+
this.contract = null;
|
|
154
|
+
this.signer = null;
|
|
155
|
+
|
|
156
|
+
const rpc = rpcUrl || process.env.${rpcEnvVar};
|
|
157
|
+
if (!rpc) {
|
|
158
|
+
throw new Error("RPC URL not found. Please provide it or set ${rpcEnvVar}");
|
|
159
|
+
}
|
|
160
|
+
this.provider = new ethers.JsonRpcProvider(rpc);
|
|
161
|
+
|
|
162
|
+
if (privateKey) {
|
|
163
|
+
this.signer = new ethers.Wallet(privateKey, this.provider);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
`;
|
|
167
|
+
methodsCode = `
|
|
168
|
+
async initializeContract()${isTs ? ": Promise<void>" : ""} {
|
|
169
|
+
if (this.contract) return;
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
// If we have a signer (private key), use it. Otherwise use provider (read-only)
|
|
173
|
+
const runner = this.signer || this.provider;
|
|
174
|
+
this.contract = new ethers.Contract(
|
|
175
|
+
this.contractAddress,
|
|
176
|
+
ABI,
|
|
177
|
+
runner
|
|
178
|
+
);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error("Failed to initialize contract:", error);
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
`;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
// Window / Browser provider
|
|
188
|
+
const ctorParams = isTs
|
|
189
|
+
? `contractAddress: string = "${contractAddress}"`
|
|
190
|
+
: `contractAddress = "${contractAddress}"`;
|
|
191
|
+
constructorCode = `
|
|
192
|
+
constructor(${ctorParams}) {
|
|
193
|
+
this.contractAddress = contractAddress;
|
|
194
|
+
this.provider = null;
|
|
195
|
+
this.signer = null;
|
|
196
|
+
this.contract = null;
|
|
197
|
+
}
|
|
198
|
+
`;
|
|
199
|
+
methodsCode = `
|
|
200
|
+
async init()${isTs ? ": Promise<void>" : ""} {
|
|
201
|
+
if (typeof window !== "undefined" && (window as any).ethereum) {
|
|
202
|
+
this.provider = new ethers.BrowserProvider((window as any).ethereum);
|
|
203
|
+
} else {
|
|
204
|
+
throw new Error("window.ethereum is not available");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async setSigner()${isTs ? ": Promise<void>" : ""} {
|
|
209
|
+
try {
|
|
210
|
+
if (!this.provider) await this.init();
|
|
211
|
+
|
|
212
|
+
// @ts-ignore
|
|
213
|
+
this.signer = await this.provider.getSigner();
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.error("Failed to set signer:", error);
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async getSignerAddress()${isTs ? ": Promise<string>" : ""} {
|
|
221
|
+
try {
|
|
222
|
+
if (!this.signer) await this.setSigner();
|
|
223
|
+
// @ts-ignore
|
|
224
|
+
return await this.signer.getAddress();
|
|
225
|
+
} catch (error) {
|
|
226
|
+
console.error("Failed to get signer address:", error);
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async initializeContract()${isTs ? ": Promise<void>" : ""} {
|
|
232
|
+
try {
|
|
233
|
+
if (!this.provider) await this.init();
|
|
234
|
+
|
|
235
|
+
if (!this.signer) {
|
|
236
|
+
// Try to get signer if possible, otherwise implementation might fail for usage requiring signer
|
|
237
|
+
// But we don't force it here to allow read-only if the user hasn't connected wallet yet?
|
|
238
|
+
// Actually, for consistency with 'setSigner', let's try to get it.
|
|
239
|
+
try {
|
|
240
|
+
// @ts-ignore
|
|
241
|
+
this.signer = await this.provider.getSigner();
|
|
242
|
+
} catch (e) {
|
|
243
|
+
console.warn("Could not get signer, defaulting to read-only provider if possible");
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (!this.contract) {
|
|
248
|
+
const runner = this.signer || this.provider;
|
|
249
|
+
this.contract = new ethers.Contract(
|
|
250
|
+
this.contractAddress,
|
|
251
|
+
ABI,
|
|
252
|
+
runner
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.error("Failed to initialize contract:", error);
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
`;
|
|
261
|
+
}
|
|
262
|
+
return `
|
|
263
|
+
import { ethers } from "ethers";
|
|
264
|
+
|
|
265
|
+
const ABI = ${JSON.stringify(abi, null, 2)};
|
|
266
|
+
|
|
267
|
+
export class ${className} {
|
|
268
|
+
${fieldTypes}
|
|
269
|
+
${constructorCode}
|
|
270
|
+
|
|
271
|
+
${methodsCode}
|
|
272
|
+
|
|
273
|
+
${erc20Methods}
|
|
274
|
+
|
|
275
|
+
// -------------------------
|
|
276
|
+
// Write functions
|
|
277
|
+
// -------------------------
|
|
278
|
+
${writeFns.map(fn => generateWriteMethod(fn, language)).join("\n")}
|
|
279
|
+
|
|
280
|
+
// -------------------------
|
|
281
|
+
// Read functions
|
|
282
|
+
// -------------------------
|
|
283
|
+
${readFns.map(fn => generateReadMethod(fn, language)).join("\n")}
|
|
284
|
+
}
|
|
285
|
+
`;
|
|
286
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const utils_1 = require("./utils");
|
|
10
|
+
const generator_1 = require("./generator");
|
|
11
|
+
const program = new commander_1.Command();
|
|
12
|
+
program
|
|
13
|
+
.version("1.0.0")
|
|
14
|
+
.description("Generate TypeScript class from ABI JSON")
|
|
15
|
+
.requiredOption("-a, --abi <path>", "Path to ABI JSON file")
|
|
16
|
+
.requiredOption("-n, --name <name>", "Name of the class to generate")
|
|
17
|
+
.requiredOption("-x, --address <address>", "Contract address")
|
|
18
|
+
.option("-o, --output <path>", "Output file path")
|
|
19
|
+
.option("--provider <type>", "Provider type: 'rpc' | 'window'", "rpc")
|
|
20
|
+
.option("--lang <language>", "Output language: 'ts' | 'js'", "ts")
|
|
21
|
+
.action(async (options) => {
|
|
22
|
+
try {
|
|
23
|
+
const abiPath = path_1.default.resolve(process.cwd(), options.abi);
|
|
24
|
+
const abi = await (0, utils_1.loadAbi)(abiPath);
|
|
25
|
+
// Validate options
|
|
26
|
+
if (!['rpc', 'window'].includes(options.provider)) {
|
|
27
|
+
throw new Error("Invalid provider type. Must be 'rpc' or 'window'.");
|
|
28
|
+
}
|
|
29
|
+
if (!['ts', 'js'].includes(options.lang)) {
|
|
30
|
+
throw new Error("Invalid language. Must be 'ts' or 'js'.");
|
|
31
|
+
}
|
|
32
|
+
const classCode = (0, generator_1.generateClass)({
|
|
33
|
+
abi,
|
|
34
|
+
className: options.name,
|
|
35
|
+
contractAddress: options.address,
|
|
36
|
+
rpcEnvVar: options.rpc,
|
|
37
|
+
providerType: options.provider,
|
|
38
|
+
language: options.lang
|
|
39
|
+
});
|
|
40
|
+
const extension = options.lang === 'js' ? 'js' : 'ts';
|
|
41
|
+
const outputPath = options.output
|
|
42
|
+
? path_1.default.resolve(process.cwd(), options.output)
|
|
43
|
+
: path_1.default.join(process.cwd(), `${options.name}.${extension}`);
|
|
44
|
+
await (0, utils_1.writefile)(outputPath, classCode);
|
|
45
|
+
console.log(`Successfully generated ${options.name} at ${outputPath}`);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.error("Error generating class:", error);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
program.parse(process.argv);
|
package/dist/utils.d.ts
ADDED
package/dist/utils.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadAbi = loadAbi;
|
|
7
|
+
exports.writefile = writefile;
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
async function loadAbi(filePath) {
|
|
11
|
+
const ext = path_1.default.extname(filePath).toLowerCase();
|
|
12
|
+
if (ext === ".json") {
|
|
13
|
+
return await fs_extra_1.default.readJson(filePath);
|
|
14
|
+
}
|
|
15
|
+
else if (ext === ".js" || ext === ".ts") {
|
|
16
|
+
const absolutePath = path_1.default.resolve(filePath);
|
|
17
|
+
// Use jiti to handle both CJS and ESM, and TS files
|
|
18
|
+
const createJiti = require("jiti");
|
|
19
|
+
const jiti = createJiti(__filename);
|
|
20
|
+
const module = jiti(absolutePath);
|
|
21
|
+
// Try to find the ABI array
|
|
22
|
+
let candidate = module.default || module.ABI || module.abi || module.Abi;
|
|
23
|
+
// If default is an object containing ABI
|
|
24
|
+
if (module.default && !Array.isArray(module.default)) {
|
|
25
|
+
candidate = module.default.ABI || module.default.abi || module.default.Abi || candidate;
|
|
26
|
+
}
|
|
27
|
+
if (candidate && Array.isArray(candidate))
|
|
28
|
+
return candidate;
|
|
29
|
+
// If the module itself is the array (unlikely for named export but possible for default)
|
|
30
|
+
if (Array.isArray(module))
|
|
31
|
+
return module;
|
|
32
|
+
// Specific check for the 'ABI' key as seen in logs
|
|
33
|
+
if (Array.isArray(module.ABI))
|
|
34
|
+
return module.ABI;
|
|
35
|
+
throw new Error(`Could not find ABI array in ${filePath}. Loaded keys: ${Object.keys(module).join(", ")}`);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
throw new Error(`Unsupported file extension: ${ext}. Use .json, .js, or .ts`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function writefile(filePath, content) {
|
|
42
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(filePath));
|
|
43
|
+
await fs_extra_1.default.writeFile(filePath, content);
|
|
44
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sol-classer",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sol-classer": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"prepublishOnly": "npm run build",
|
|
15
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [],
|
|
18
|
+
"author": "larsalaxsanderson@gmail.com",
|
|
19
|
+
"license": "ISC",
|
|
20
|
+
"type": "commonjs",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"commander": "^14.0.3",
|
|
23
|
+
"ethers": "^6.16.0",
|
|
24
|
+
"fs-extra": "^11.3.3",
|
|
25
|
+
"jiti": "^2.6.1"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/fs-extra": "^11.0.4",
|
|
29
|
+
"@types/node": "^25.2.3",
|
|
30
|
+
"ts-node": "^10.9.2",
|
|
31
|
+
"typescript": "^5.9.3"
|
|
32
|
+
}
|
|
33
|
+
}
|