@chainfoundry/chaincodec 0.1.0 → 0.1.2
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 +380 -0
- package/chaincodec.darwin-arm64.node +0 -0
- package/chaincodec.darwin-x64.node +0 -0
- package/chaincodec.linux-arm64-gnu.node +0 -0
- package/chaincodec.linux-x64-gnu.node +0 -0
- package/chaincodec.linux-x64-musl.node +0 -0
- package/chaincodec.win32-x64-msvc.node +0 -0
- package/package.json +14 -3
package/README.md
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
# @chainfoundry/chaincodec
|
|
2
|
+
|
|
3
|
+
Universal blockchain ABI decoder for Node.js — production-grade EVM event & function call decoding.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@chainfoundry/chaincodec)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Native Node.js bindings (via [napi-rs](https://napi.rs)) for the [chaincodec](https://crates.io/crates/chaincodec-evm) Rust library. Decode `eth_getLogs` entries, function calldata, and compute topic0 fingerprints — all at Rust speed with a TypeScript-first API.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @chainfoundry/chaincodec
|
|
16
|
+
# or
|
|
17
|
+
yarn add @chainfoundry/chaincodec
|
|
18
|
+
# or
|
|
19
|
+
pnpm add @chainfoundry/chaincodec
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
No build step required — pre-built native binaries are bundled for all major platforms.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Platform support
|
|
27
|
+
|
|
28
|
+
| Platform | Architecture | Supported |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| Linux (glibc) | x64 | ✅ |
|
|
31
|
+
| Linux (glibc) | arm64 | ✅ |
|
|
32
|
+
| Linux (musl / Alpine) | x64 | ✅ |
|
|
33
|
+
| macOS | x64 (Intel) | ✅ |
|
|
34
|
+
| macOS | arm64 (Apple Silicon) | ✅ |
|
|
35
|
+
| Windows | x64 | ✅ |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Quick start — decode an EVM event log
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { EvmDecoder, MemoryRegistry } from '@chainfoundry/chaincodec';
|
|
43
|
+
|
|
44
|
+
// 1. Load your schemas (CSDL YAML format)
|
|
45
|
+
const registry = new MemoryRegistry();
|
|
46
|
+
|
|
47
|
+
// Load from inline CSDL string
|
|
48
|
+
registry.loadCsdl(`
|
|
49
|
+
schema ERC20Transfer:
|
|
50
|
+
version: 1
|
|
51
|
+
description: "ERC-20 standard Transfer event"
|
|
52
|
+
chains: [ethereum, arbitrum, base, polygon, optimism]
|
|
53
|
+
event: Transfer
|
|
54
|
+
fingerprint: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
|
|
55
|
+
fields:
|
|
56
|
+
from: { type: address, indexed: true }
|
|
57
|
+
to: { type: address, indexed: true }
|
|
58
|
+
value: { type: uint256, indexed: false }
|
|
59
|
+
meta:
|
|
60
|
+
protocol: erc20
|
|
61
|
+
category: token
|
|
62
|
+
`);
|
|
63
|
+
|
|
64
|
+
// Or load from files on disk
|
|
65
|
+
// registry.loadFile('./schemas/erc20.csdl');
|
|
66
|
+
// registry.loadDirectory('./schemas');
|
|
67
|
+
|
|
68
|
+
// 2. Decode a raw log from eth_getLogs
|
|
69
|
+
const decoder = new EvmDecoder();
|
|
70
|
+
|
|
71
|
+
const rawLog = {
|
|
72
|
+
chain: 'ethereum',
|
|
73
|
+
txHash: '0xabc123...',
|
|
74
|
+
blockNumber: 19500000,
|
|
75
|
+
blockTimestamp: 1710000000,
|
|
76
|
+
logIndex: 0,
|
|
77
|
+
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC
|
|
78
|
+
topics: [
|
|
79
|
+
'0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // Transfer
|
|
80
|
+
'0x000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045', // from
|
|
81
|
+
'0x000000000000000000000000ab5801a7d398351b8be11c439e05c5b3259aec9b', // to
|
|
82
|
+
],
|
|
83
|
+
data: '0x00000000000000000000000000000000000000000000000000000000000f4240',
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const event = decoder.decodeEvent(rawLog, registry);
|
|
87
|
+
|
|
88
|
+
console.log(event.schema); // "ERC20Transfer"
|
|
89
|
+
console.log(event.fields.from); // { type: 'address', value: '0xd8da6bf2...' }
|
|
90
|
+
console.log(event.fields.to); // { type: 'address', value: '0xab5801a7...' }
|
|
91
|
+
console.log(event.fields.value); // { type: 'biguint', value: '1000000' }
|
|
92
|
+
console.log(event.fingerprint); // "0xddf252ad..."
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Decode function calldata
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { EvmCallDecoder } from '@chainfoundry/chaincodec';
|
|
101
|
+
import { readFileSync } from 'fs';
|
|
102
|
+
|
|
103
|
+
// Load ABI JSON (from Etherscan, Hardhat artifacts, Foundry out/, etc.)
|
|
104
|
+
const abiJson = readFileSync('./abi/erc20.json', 'utf-8');
|
|
105
|
+
const decoder = EvmCallDecoder.fromAbiJson(abiJson);
|
|
106
|
+
|
|
107
|
+
// Decode raw calldata from a transaction's `input` field
|
|
108
|
+
const calldata = '0xa9059cbb000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045000000000000000000000000000000000000000000000000000000000000000a';
|
|
109
|
+
const call = decoder.decodeCall(calldata);
|
|
110
|
+
|
|
111
|
+
console.log(call.functionName); // "transfer"
|
|
112
|
+
console.log(call.selector); // "0xa9059cbb"
|
|
113
|
+
|
|
114
|
+
for (const [name, value] of call.inputs) {
|
|
115
|
+
console.log(` ${name}:`, value);
|
|
116
|
+
}
|
|
117
|
+
// to: { type: 'address', value: '0xd8da6bf2...' }
|
|
118
|
+
// amount: { type: 'biguint', value: '10' }
|
|
119
|
+
|
|
120
|
+
// List all functions in the ABI
|
|
121
|
+
console.log(decoder.functionNames()); // ['transfer', 'approve', ...]
|
|
122
|
+
console.log(decoder.selectorFor('transfer')); // "0xa9059cbb"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## ABI encode a function call
|
|
128
|
+
|
|
129
|
+
`encodeCall` takes `argsJson` — a JSON string of `NormalizedValue[]`.
|
|
130
|
+
Each `NormalizedValue` has `{ type, value }` matching the Solidity type.
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { EvmEncoder } from '@chainfoundry/chaincodec';
|
|
134
|
+
|
|
135
|
+
const abiJson = JSON.stringify([{
|
|
136
|
+
name: 'transfer',
|
|
137
|
+
type: 'function',
|
|
138
|
+
inputs: [
|
|
139
|
+
{ name: 'to', type: 'address' },
|
|
140
|
+
{ name: 'amount', type: 'uint256' },
|
|
141
|
+
],
|
|
142
|
+
outputs: [{ name: '', type: 'bool' }],
|
|
143
|
+
stateMutability: 'nonpayable',
|
|
144
|
+
}]);
|
|
145
|
+
|
|
146
|
+
const encoder = EvmEncoder.fromAbiJson(abiJson);
|
|
147
|
+
|
|
148
|
+
const calldata = encoder.encodeCall(
|
|
149
|
+
'transfer',
|
|
150
|
+
JSON.stringify([
|
|
151
|
+
{ type: 'address', value: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' },
|
|
152
|
+
{ type: 'uint', value: 1000000 },
|
|
153
|
+
])
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
console.log(calldata);
|
|
157
|
+
// "0xa9059cbb000000000000000000000000d8da6bf2...000f4240"
|
|
158
|
+
// First 4 bytes (0xa9059cbb) = transfer(address,uint256) selector
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### NormalizedValue input types for encoding
|
|
162
|
+
|
|
163
|
+
| Solidity type | `NormalizedValue` JSON |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| `address` | `{ "type": "address", "value": "0x..." }` |
|
|
166
|
+
| `uint256` | `{ "type": "uint", "value": 1000000 }` |
|
|
167
|
+
| `uint256` (large) | `{ "type": "biguint", "value": "99999999999999999999" }` |
|
|
168
|
+
| `int256` | `{ "type": "int", "value": -100 }` |
|
|
169
|
+
| `bool` | `{ "type": "bool", "value": true }` |
|
|
170
|
+
| `bytes` | `{ "type": "bytes", "value": [0xde, 0xad, 0xbe, 0xef] }` |
|
|
171
|
+
| `string` | `{ "type": "str", "value": "hello" }` |
|
|
172
|
+
| `address[]` | `{ "type": "array", "value": [{ "type": "address", "value": "0x..." }] }` |
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Batch decode
|
|
177
|
+
|
|
178
|
+
Decode thousands of logs in parallel using Rayon (Rust's parallel iterator):
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { EvmDecoder, MemoryRegistry } from '@chainfoundry/chaincodec';
|
|
182
|
+
|
|
183
|
+
const registry = new MemoryRegistry();
|
|
184
|
+
registry.loadCsdl(/* your CSDL schemas */);
|
|
185
|
+
|
|
186
|
+
const decoder = new EvmDecoder();
|
|
187
|
+
|
|
188
|
+
// Decode a batch of raw logs — returns { events, errors }
|
|
189
|
+
const { events, errors } = decoder.decodeBatch(rawLogs, registry);
|
|
190
|
+
|
|
191
|
+
console.log(`decoded: ${events.length} events`);
|
|
192
|
+
console.log(`errors: ${errors.length}`);
|
|
193
|
+
|
|
194
|
+
for (const event of events) {
|
|
195
|
+
console.log(event.schema, event.fields);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
for (const { index, error } of errors) {
|
|
199
|
+
console.warn(`log[${index}] failed: ${error}`);
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Compute topic0 fingerprint
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
const decoder = new EvmDecoder();
|
|
209
|
+
|
|
210
|
+
const fp = decoder.fingerprint({
|
|
211
|
+
chain: 'ethereum',
|
|
212
|
+
txHash: '0x0',
|
|
213
|
+
blockNumber: 0,
|
|
214
|
+
blockTimestamp: 0,
|
|
215
|
+
logIndex: 0,
|
|
216
|
+
address: '0x0000000000000000000000000000000000000000',
|
|
217
|
+
topics: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'],
|
|
218
|
+
data: '0x',
|
|
219
|
+
});
|
|
220
|
+
console.log(fp); // "0xddf252ad..."
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## EIP-712 typed data
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
import { Eip712Parser } from '@chainfoundry/chaincodec';
|
|
229
|
+
|
|
230
|
+
const parser = new Eip712Parser();
|
|
231
|
+
|
|
232
|
+
const typedDataJson = JSON.stringify({
|
|
233
|
+
types: {
|
|
234
|
+
EIP712Domain: [
|
|
235
|
+
{ name: 'name', type: 'string' },
|
|
236
|
+
{ name: 'version', type: 'string' },
|
|
237
|
+
{ name: 'chainId', type: 'uint256' },
|
|
238
|
+
],
|
|
239
|
+
Transfer: [
|
|
240
|
+
{ name: 'to', type: 'address' },
|
|
241
|
+
{ name: 'amount', type: 'uint256' },
|
|
242
|
+
],
|
|
243
|
+
},
|
|
244
|
+
primaryType: 'Transfer',
|
|
245
|
+
domain: { name: 'MyToken', version: '1', chainId: 1 },
|
|
246
|
+
message: { to: '0xd8dA...', amount: '1000000' },
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const parsed = parser.parse(typedDataJson);
|
|
250
|
+
console.log(parsed.primary_type); // "Transfer" (snake_case from Rust serde)
|
|
251
|
+
|
|
252
|
+
const domainHash = parser.domainSeparator(typedDataJson);
|
|
253
|
+
console.log(domainHash); // "0x..."
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Using CommonJS
|
|
259
|
+
|
|
260
|
+
```javascript
|
|
261
|
+
const { EvmDecoder, MemoryRegistry, EvmCallDecoder, EvmEncoder, Eip712Parser } = require('@chainfoundry/chaincodec');
|
|
262
|
+
|
|
263
|
+
const registry = new MemoryRegistry();
|
|
264
|
+
registry.loadCsdl(csdlString);
|
|
265
|
+
|
|
266
|
+
const decoder = new EvmDecoder();
|
|
267
|
+
const event = decoder.decodeEvent(rawLog, registry);
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## API reference
|
|
273
|
+
|
|
274
|
+
### `MemoryRegistry`
|
|
275
|
+
|
|
276
|
+
| Method / Property | Signature | Description |
|
|
277
|
+
| --- | --- | --- |
|
|
278
|
+
| `constructor` | `new MemoryRegistry()` | Create empty registry |
|
|
279
|
+
| `loadCsdl` | `(csdl: string) => number` | Load schemas from CSDL YAML string; returns count loaded |
|
|
280
|
+
| `loadFile` | `(path: string) => number` | Load a single `.csdl` file |
|
|
281
|
+
| `loadDirectory` | `(path: string) => number` | Load all `.csdl` files in a directory |
|
|
282
|
+
| `schemaCount` | `readonly number` | Number of schemas registered |
|
|
283
|
+
| `schemaNames` | `() => string[]` | List all schema names |
|
|
284
|
+
|
|
285
|
+
### `EvmDecoder`
|
|
286
|
+
|
|
287
|
+
| Method | Signature | Description |
|
|
288
|
+
| --- | --- | --- |
|
|
289
|
+
| `constructor` | `new EvmDecoder()` | Create decoder |
|
|
290
|
+
| `decodeEvent` | `(raw: RawEvent, registry: MemoryRegistry) => DecodedEvent` | Decode a single EVM log |
|
|
291
|
+
| `decodeBatch` | `(raws: RawEvent[], registry: MemoryRegistry) => BatchDecodeResult` | Parallel-decode many logs |
|
|
292
|
+
| `fingerprint` | `(raw: RawEvent) => string` | Get topic0 fingerprint hex string |
|
|
293
|
+
|
|
294
|
+
### `EvmCallDecoder`
|
|
295
|
+
|
|
296
|
+
| Method | Signature | Description |
|
|
297
|
+
| --- | --- | --- |
|
|
298
|
+
| `fromAbiJson` | `(abiJson: string) => EvmCallDecoder` | Create from ABI JSON (static factory) |
|
|
299
|
+
| `decodeCall` | `(calldata: string, functionName?: string or null) => DecodedCall` | Decode calldata |
|
|
300
|
+
| `functionNames` | `() => string[]` | List all function names in ABI |
|
|
301
|
+
| `selectorFor` | `(functionName: string) => string or null` | Get 4-byte selector hex |
|
|
302
|
+
|
|
303
|
+
### `EvmEncoder`
|
|
304
|
+
|
|
305
|
+
| Method | Signature | Description |
|
|
306
|
+
| --- | --- | --- |
|
|
307
|
+
| `fromAbiJson` | `(abiJson: string) => EvmEncoder` | Create from ABI JSON (static factory) |
|
|
308
|
+
| `encodeCall` | `(functionName: string, argsJson: string) => string` | Encode; `argsJson` = `JSON.stringify(NormalizedValue[])`, returns `0x`-hex |
|
|
309
|
+
|
|
310
|
+
### `Eip712Parser`
|
|
311
|
+
|
|
312
|
+
| Method | Signature | Description |
|
|
313
|
+
| --- | --- | --- |
|
|
314
|
+
| `constructor` | `new Eip712Parser()` | Create parser |
|
|
315
|
+
| `parse` | `(json: string) => TypedData` | Parse EIP-712 typed data JSON |
|
|
316
|
+
| `domainSeparator` | `(json: string) => string` | Compute domain separator hash |
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## TypeScript types
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
interface RawEvent {
|
|
324
|
+
chain: string; // "ethereum" | "arbitrum" | "base" | "polygon" | "optimism" | numeric id
|
|
325
|
+
txHash: string;
|
|
326
|
+
blockNumber: number;
|
|
327
|
+
blockTimestamp: number; // Unix seconds
|
|
328
|
+
logIndex: number;
|
|
329
|
+
address: string; // contract address (hex, lowercase ok)
|
|
330
|
+
topics: string[]; // topics[0] = event signature hash
|
|
331
|
+
data: string; // hex with 0x prefix
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
interface DecodedEvent {
|
|
335
|
+
schema: string; // schema name, e.g. "ERC20Transfer"
|
|
336
|
+
schemaVersion: number;
|
|
337
|
+
chain: string;
|
|
338
|
+
txHash: string;
|
|
339
|
+
blockNumber: number;
|
|
340
|
+
blockTimestamp: number;
|
|
341
|
+
logIndex: number;
|
|
342
|
+
address: string;
|
|
343
|
+
fields: Record<string, NormalizedValue>; // decoded fields by name
|
|
344
|
+
fingerprint: string; // keccak256 of topics[0]
|
|
345
|
+
decodeErrors: Record<string, string>; // fields that failed to decode
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
interface DecodedCall {
|
|
349
|
+
functionName: string;
|
|
350
|
+
selector: string | null; // "0xaabbccdd"
|
|
351
|
+
inputs: Array<[string, NormalizedValue]>; // [name, value] pairs
|
|
352
|
+
decodeErrors: Record<string, string>;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
type NormalizedValue =
|
|
356
|
+
| { type: 'uint'; value: number }
|
|
357
|
+
| { type: 'biguint'; value: string } // large uint256 as decimal string
|
|
358
|
+
| { type: 'int'; value: number }
|
|
359
|
+
| { type: 'bigint'; value: string }
|
|
360
|
+
| { type: 'bool'; value: boolean }
|
|
361
|
+
| { type: 'bytes'; value: number[] }
|
|
362
|
+
| { type: 'str'; value: string }
|
|
363
|
+
| { type: 'address'; value: string } // "0x..." lowercase
|
|
364
|
+
| { type: 'hash256'; value: string }
|
|
365
|
+
| { type: 'timestamp'; value: number }
|
|
366
|
+
| { type: 'array'; value: NormalizedValue[] }
|
|
367
|
+
| { type: 'tuple'; value: Array<[string, NormalizedValue]> }
|
|
368
|
+
| { type: 'null' }
|
|
369
|
+
|
|
370
|
+
interface BatchDecodeResult {
|
|
371
|
+
events: DecodedEvent[];
|
|
372
|
+
errors: Array<{ index: number; error: string }>;
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## License
|
|
379
|
+
|
|
380
|
+
MIT — see [LICENSE](https://github.com/DarshanKumar89/chainkit/blob/main/LICENSE)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chainfoundry/chaincodec",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Universal blockchain ABI decoder — production-grade event & call decoding for Ethereum and EVM chains",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ethereum",
|
|
@@ -12,7 +12,17 @@
|
|
|
12
12
|
"chaincodec",
|
|
13
13
|
"arbitrum",
|
|
14
14
|
"base",
|
|
15
|
-
"polygon"
|
|
15
|
+
"polygon",
|
|
16
|
+
"optimism",
|
|
17
|
+
"solidity",
|
|
18
|
+
"defi",
|
|
19
|
+
"uniswap",
|
|
20
|
+
"napi",
|
|
21
|
+
"rust-napi",
|
|
22
|
+
"native-addon",
|
|
23
|
+
"eth-logs",
|
|
24
|
+
"event-decoder",
|
|
25
|
+
"call-decoder"
|
|
16
26
|
],
|
|
17
27
|
"license": "MIT",
|
|
18
28
|
"repository": {
|
|
@@ -36,7 +46,8 @@
|
|
|
36
46
|
"files": [
|
|
37
47
|
"index.d.ts",
|
|
38
48
|
"index.js",
|
|
39
|
-
"chaincodec.*.node"
|
|
49
|
+
"chaincodec.*.node",
|
|
50
|
+
"README.md"
|
|
40
51
|
],
|
|
41
52
|
"scripts": {
|
|
42
53
|
"build": "napi build --platform --release",
|