@chainfoundry/chaincodec 0.1.1 → 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 +240 -106
- 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 +1 -1
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ No build step required — pre-built native binaries are bundled for all major p
|
|
|
26
26
|
## Platform support
|
|
27
27
|
|
|
28
28
|
| Platform | Architecture | Supported |
|
|
29
|
-
|
|
29
|
+
| --- | --- | --- |
|
|
30
30
|
| Linux (glibc) | x64 | ✅ |
|
|
31
31
|
| Linux (glibc) | arm64 | ✅ |
|
|
32
32
|
| Linux (musl / Alpine) | x64 | ✅ |
|
|
@@ -36,14 +36,34 @@ No build step required — pre-built native binaries are bundled for all major p
|
|
|
36
36
|
|
|
37
37
|
---
|
|
38
38
|
|
|
39
|
-
## Quick start
|
|
39
|
+
## Quick start — decode an EVM event log
|
|
40
40
|
|
|
41
41
|
```typescript
|
|
42
42
|
import { EvmDecoder, MemoryRegistry } from '@chainfoundry/chaincodec';
|
|
43
43
|
|
|
44
|
-
// 1. Load schemas
|
|
44
|
+
// 1. Load your schemas (CSDL YAML format)
|
|
45
45
|
const registry = new MemoryRegistry();
|
|
46
|
-
|
|
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');
|
|
47
67
|
|
|
48
68
|
// 2. Decode a raw log from eth_getLogs
|
|
49
69
|
const decoder = new EvmDecoder();
|
|
@@ -51,8 +71,8 @@ const decoder = new EvmDecoder();
|
|
|
51
71
|
const rawLog = {
|
|
52
72
|
chain: 'ethereum',
|
|
53
73
|
txHash: '0xabc123...',
|
|
54
|
-
blockNumber:
|
|
55
|
-
blockTimestamp:
|
|
74
|
+
blockNumber: 19500000,
|
|
75
|
+
blockTimestamp: 1710000000,
|
|
56
76
|
logIndex: 0,
|
|
57
77
|
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC
|
|
58
78
|
topics: [
|
|
@@ -63,16 +83,13 @@ const rawLog = {
|
|
|
63
83
|
data: '0x00000000000000000000000000000000000000000000000000000000000f4240',
|
|
64
84
|
};
|
|
65
85
|
|
|
66
|
-
const
|
|
67
|
-
const schema = registry.getByFingerprint(fp);
|
|
86
|
+
const event = decoder.decodeEvent(rawLog, registry);
|
|
68
87
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
console.log(event.fields.value); // "1000000" (1 USDC)
|
|
75
|
-
}
|
|
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..."
|
|
76
93
|
```
|
|
77
94
|
|
|
78
95
|
---
|
|
@@ -84,159 +101,276 @@ import { EvmCallDecoder } from '@chainfoundry/chaincodec';
|
|
|
84
101
|
import { readFileSync } from 'fs';
|
|
85
102
|
|
|
86
103
|
// Load ABI JSON (from Etherscan, Hardhat artifacts, Foundry out/, etc.)
|
|
87
|
-
const abiJson = readFileSync('./abi/
|
|
104
|
+
const abiJson = readFileSync('./abi/erc20.json', 'utf-8');
|
|
88
105
|
const decoder = EvmCallDecoder.fromAbiJson(abiJson);
|
|
89
106
|
|
|
90
107
|
// Decode raw calldata from a transaction's `input` field
|
|
91
|
-
const calldata = '
|
|
108
|
+
const calldata = '0xa9059cbb000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045000000000000000000000000000000000000000000000000000000000000000a';
|
|
92
109
|
const call = decoder.decodeCall(calldata);
|
|
93
110
|
|
|
94
|
-
console.log(call.functionName);
|
|
95
|
-
|
|
96
|
-
|
|
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);
|
|
97
116
|
}
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
//
|
|
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"
|
|
102
123
|
```
|
|
103
124
|
|
|
104
125
|
---
|
|
105
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
|
+
|
|
106
176
|
## Batch decode
|
|
107
177
|
|
|
178
|
+
Decode thousands of logs in parallel using Rayon (Rust's parallel iterator):
|
|
179
|
+
|
|
108
180
|
```typescript
|
|
109
|
-
import {
|
|
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}`);
|
|
110
193
|
|
|
111
|
-
const
|
|
112
|
-
|
|
194
|
+
for (const event of events) {
|
|
195
|
+
console.log(event.schema, event.fields);
|
|
196
|
+
}
|
|
113
197
|
|
|
114
|
-
const
|
|
198
|
+
for (const { index, error } of errors) {
|
|
199
|
+
console.warn(`log[${index}] failed: ${error}`);
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
115
204
|
|
|
116
|
-
|
|
205
|
+
## Compute topic0 fingerprint
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
const decoder = new EvmDecoder();
|
|
209
|
+
|
|
210
|
+
const fp = decoder.fingerprint({
|
|
117
211
|
chain: 'ethereum',
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
+
],
|
|
123
243
|
},
|
|
244
|
+
primaryType: 'Transfer',
|
|
245
|
+
domain: { name: 'MyToken', version: '1', chainId: 1 },
|
|
246
|
+
message: { to: '0xd8dA...', amount: '1000000' },
|
|
124
247
|
});
|
|
125
248
|
|
|
126
|
-
|
|
127
|
-
console.log(
|
|
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..."
|
|
128
254
|
```
|
|
129
255
|
|
|
130
256
|
---
|
|
131
257
|
|
|
132
|
-
##
|
|
258
|
+
## Using CommonJS
|
|
133
259
|
|
|
134
|
-
```
|
|
135
|
-
|
|
260
|
+
```javascript
|
|
261
|
+
const { EvmDecoder, MemoryRegistry, EvmCallDecoder, EvmEncoder, Eip712Parser } = require('@chainfoundry/chaincodec');
|
|
136
262
|
|
|
137
|
-
const
|
|
138
|
-
|
|
263
|
+
const registry = new MemoryRegistry();
|
|
264
|
+
registry.loadCsdl(csdlString);
|
|
139
265
|
|
|
140
|
-
const
|
|
141
|
-
|
|
266
|
+
const decoder = new EvmDecoder();
|
|
267
|
+
const event = decoder.decodeEvent(rawLog, registry);
|
|
142
268
|
```
|
|
143
269
|
|
|
144
270
|
---
|
|
145
271
|
|
|
146
272
|
## API reference
|
|
147
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
|
+
|
|
148
285
|
### `EvmDecoder`
|
|
149
286
|
|
|
150
|
-
| Method | Description |
|
|
151
|
-
|
|
152
|
-
| `
|
|
153
|
-
| `decodeEvent(
|
|
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 |
|
|
154
293
|
|
|
155
294
|
### `EvmCallDecoder`
|
|
156
295
|
|
|
157
|
-
| Method | Description |
|
|
158
|
-
|
|
159
|
-
| `
|
|
160
|
-
| `decodeCall(calldata,
|
|
161
|
-
| `
|
|
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 |
|
|
162
302
|
|
|
163
|
-
### `
|
|
303
|
+
### `EvmEncoder`
|
|
164
304
|
|
|
165
|
-
| Method | Description |
|
|
166
|
-
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `getByFingerprint(fp)` | Look up schema by topic0 hash |
|
|
170
|
-
| `getByName(name)` | Look up schema by name |
|
|
171
|
-
| `allSchemas()` | Return all registered schemas |
|
|
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 |
|
|
172
309
|
|
|
173
|
-
### `
|
|
310
|
+
### `Eip712Parser`
|
|
174
311
|
|
|
175
|
-
| Method | Description |
|
|
176
|
-
|
|
177
|
-
| `
|
|
178
|
-
| `
|
|
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 |
|
|
179
317
|
|
|
180
318
|
---
|
|
181
319
|
|
|
182
320
|
## TypeScript types
|
|
183
321
|
|
|
184
322
|
```typescript
|
|
185
|
-
interface
|
|
186
|
-
chain: string;
|
|
323
|
+
interface RawEvent {
|
|
324
|
+
chain: string; // "ethereum" | "arbitrum" | "base" | "polygon" | "optimism" | numeric id
|
|
187
325
|
txHash: string;
|
|
188
|
-
blockNumber:
|
|
189
|
-
blockTimestamp:
|
|
326
|
+
blockNumber: number;
|
|
327
|
+
blockTimestamp: number; // Unix seconds
|
|
190
328
|
logIndex: number;
|
|
191
|
-
address: string;
|
|
192
|
-
topics: string[];
|
|
193
|
-
data: string;
|
|
329
|
+
address: string; // contract address (hex, lowercase ok)
|
|
330
|
+
topics: string[]; // topics[0] = event signature hash
|
|
331
|
+
data: string; // hex with 0x prefix
|
|
194
332
|
}
|
|
195
333
|
|
|
196
334
|
interface DecodedEvent {
|
|
197
|
-
|
|
335
|
+
schema: string; // schema name, e.g. "ERC20Transfer"
|
|
336
|
+
schemaVersion: number;
|
|
198
337
|
chain: string;
|
|
199
|
-
blockNumber: bigint;
|
|
200
338
|
txHash: string;
|
|
339
|
+
blockNumber: number;
|
|
340
|
+
blockTimestamp: number;
|
|
201
341
|
logIndex: number;
|
|
202
|
-
|
|
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
|
|
203
346
|
}
|
|
204
347
|
|
|
205
348
|
interface DecodedCall {
|
|
206
349
|
functionName: string;
|
|
207
|
-
|
|
350
|
+
selector: string | null; // "0xaabbccdd"
|
|
351
|
+
inputs: Array<[string, NormalizedValue]>; // [name, value] pairs
|
|
352
|
+
decodeErrors: Record<string, string>;
|
|
208
353
|
}
|
|
209
|
-
```
|
|
210
354
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
```typescript
|
|
231
|
-
import { join } from 'path';
|
|
232
|
-
import { createRequire } from 'module';
|
|
233
|
-
|
|
234
|
-
const require = createRequire(import.meta.url);
|
|
235
|
-
const schemasDir = join(
|
|
236
|
-
require.resolve('@chainfoundry/chaincodec/package.json'),
|
|
237
|
-
'../schemas'
|
|
238
|
-
);
|
|
239
|
-
registry.loadDirectory(schemasDir);
|
|
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
|
+
}
|
|
240
374
|
```
|
|
241
375
|
|
|
242
376
|
---
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|