@logrix/sdk 0.3.0 → 0.3.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 +26 -6
- package/dist/commands/add.js +53 -29
- package/dist/commands/build.js +14 -3
- package/dist/commands/codegen.js +22 -14
- package/dist/commands/init.js +45 -29
- package/dist/commands/status.js +140 -0
- package/dist/index.js +3 -1
- package/index.ts +2 -1
- package/package.json +2 -1
- package/src/runtime/index.ts +52 -0
- package/src/testing/index.ts +29 -0
package/README.md
CHANGED
|
@@ -24,11 +24,11 @@ npm test
|
|
|
24
24
|
|
|
25
25
|
### `logrix init [name]`
|
|
26
26
|
|
|
27
|
-
Scaffolds a new indexer project. Run interactively or pass flags for CI/CD automation.
|
|
27
|
+
Scaffolds a new indexer project with built-in presets for 26+ popular mainnets and testnets. Run interactively or pass flags for CI/CD automation.
|
|
28
28
|
|
|
29
29
|
Options:
|
|
30
30
|
- `-y, --yes` -- non-interactive mode using sensible defaults
|
|
31
|
-
- `--network <name>` -- network preset (`arbitrum-one`, `ethereum`, `base`, `polygon`, `sepolia
|
|
31
|
+
- `--network <name>` -- network preset (`arbitrum-one`, `ethereum`, `base`, `optimism`, `polygon`, `bsc`, `avalanche`, `sepolia`, etc.)
|
|
32
32
|
- `--rpc <url>` -- custom RPC endpoint
|
|
33
33
|
- `--contract-name <name>` -- contract name
|
|
34
34
|
- `--address <address>` -- target contract 0x address
|
|
@@ -51,6 +51,23 @@ my-indexer/
|
|
|
51
51
|
|
|
52
52
|
---
|
|
53
53
|
|
|
54
|
+
### `logrix status`
|
|
55
|
+
|
|
56
|
+
Checks live JSON-RPC connectivity, measures round-trip latency, verifies chain ID match, checks latest block height, and verifies that target smart contracts are deployed with on-chain bytecode.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx logrix status
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Output includes:
|
|
63
|
+
- Network Name & Chain ID verification
|
|
64
|
+
- RPC reachability and response latency (ms)
|
|
65
|
+
- Latest chain block number
|
|
66
|
+
- Contract deployment verification (`eth_getCode` bytecode inspection)
|
|
67
|
+
- Sync scope and remaining block delta
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
54
71
|
### `logrix add contract <name>`
|
|
55
72
|
|
|
56
73
|
Adds an additional smart contract to an existing `logrix.yaml` project for multi-contract indexing.
|
|
@@ -62,7 +79,7 @@ npx logrix add contract SecondaryToken \
|
|
|
62
79
|
--abi ./my-abis/SecondaryToken.json \
|
|
63
80
|
--start-block 1000000
|
|
64
81
|
|
|
65
|
-
# Add with automatic ABI fetch from
|
|
82
|
+
# Add with automatic ABI fetch from block explorers / Sourcify
|
|
66
83
|
npx logrix add contract DaiToken \
|
|
67
84
|
--address 0x6b175474e89094c44da98b954eedeac495271d0f \
|
|
68
85
|
--fetch-abi
|
|
@@ -71,8 +88,8 @@ npx logrix add contract DaiToken \
|
|
|
71
88
|
Options:
|
|
72
89
|
- `--address <address>` -- contract address
|
|
73
90
|
- `--abi <path>` -- path to local ABI JSON
|
|
74
|
-
- `--fetch-abi` -- auto-fetch verified contract ABI from Etherscan
|
|
75
|
-
- `--etherscan-key <key>` -- optional
|
|
91
|
+
- `--fetch-abi` -- auto-fetch verified contract ABI from block explorers (Etherscan, Arbiscan, Basescan, Polygonscan, etc.) and Sourcify
|
|
92
|
+
- `--etherscan-key <key>` -- optional block explorer API key for rate limits
|
|
76
93
|
- `--start-block <block>` -- initial indexing block
|
|
77
94
|
|
|
78
95
|
---
|
|
@@ -96,7 +113,7 @@ npx logrix codegen
|
|
|
96
113
|
```
|
|
97
114
|
|
|
98
115
|
Outputs:
|
|
99
|
-
- `src/generated/events.ts` -- typed event and parameter classes (namespaced per contract), multi-contract address constants, and
|
|
116
|
+
- `src/generated/events.ts` -- typed event and parameter classes (namespaced per contract), multi-contract address constants, router helpers, and dynamic ABI decoding (`string`, `bytes`)
|
|
100
117
|
- `src/generated/schema.ts` -- entity classes with static `.load(id)`, `.remove(id)`, and `.save()`, handling `@derivedFrom` relationships
|
|
101
118
|
|
|
102
119
|
---
|
|
@@ -165,6 +182,9 @@ Imported via `import { ... } from "@logrix/sdk"` in AssemblyScript handlers:
|
|
|
165
182
|
| `Bytes` | Hex byte wrapper: `.fromHexString()`, `.fromUTF8()`, `.toHexString()`, `.toByteArray()`, `.length` |
|
|
166
183
|
| `crypto.keccak256(data)` | Computes standard EVM Keccak-256 hash |
|
|
167
184
|
| `formatUnits(val, decimals)` | Formats raw BigInt value into human-readable decimal string (default: 18) |
|
|
185
|
+
| `decodeDynamicString(data, idx)` | Decodes dynamic string parameter from raw ABI event data |
|
|
186
|
+
| `decodeDynamicBytes(data, idx)` | Decodes dynamic bytes parameter from raw ABI event data |
|
|
187
|
+
| `createMockEventLog(config)` | Creates mock EventLog instance for testing mapping logic |
|
|
168
188
|
| `log` | Structured logging: `log.info()`, `log.warning()`, `log.error()`, `log.debug()` |
|
|
169
189
|
| `allocate(size)` | Guest memory allocator (must be re-exported by `mapping.ts`) |
|
|
170
190
|
|
package/dist/commands/add.js
CHANGED
|
@@ -40,40 +40,66 @@ const fs = __importStar(require("fs"));
|
|
|
40
40
|
const path = __importStar(require("path"));
|
|
41
41
|
const yaml_1 = require("yaml");
|
|
42
42
|
const templates_1 = require("../templates");
|
|
43
|
+
const EXPLORER_APIS = {
|
|
44
|
+
1: "https://api.etherscan.io/api",
|
|
45
|
+
42161: "https://api.arbiscan.io/api",
|
|
46
|
+
10: "https://api-optimistic.etherscan.io/api",
|
|
47
|
+
8453: "https://api.basescan.org/api",
|
|
48
|
+
137: "https://api.polygonscan.com/api",
|
|
49
|
+
56: "https://api.bscscan.com/api",
|
|
50
|
+
43114: "https://api.snowtrace.io/api",
|
|
51
|
+
59144: "https://api.lineascan.build/api",
|
|
52
|
+
534352: "https://api.scrollscan.com/api",
|
|
53
|
+
81457: "https://api.blastscan.io/api",
|
|
54
|
+
100: "https://api.gnosisscan.io/api",
|
|
55
|
+
250: "https://api.ftmscan.com/api",
|
|
56
|
+
42220: "https://api.celoscan.io/api",
|
|
57
|
+
1101: "https://api-zkevm.polygonscan.com/api",
|
|
58
|
+
11155111: "https://api-sepolia.etherscan.io/api",
|
|
59
|
+
17000: "https://api-holesky.etherscan.io/api",
|
|
60
|
+
421614: "https://api-sepolia.arbiscan.io/api",
|
|
61
|
+
84532: "https://api-sepolia.basescan.org/api",
|
|
62
|
+
11155420: "https://api-sepolia-optimistic.etherscan.io/api",
|
|
63
|
+
80002: "https://api-amoy.polygonscan.com/api",
|
|
64
|
+
97: "https://api-testnet.bscscan.com/api",
|
|
65
|
+
43113: "https://api-testnet.snowtrace.io/api",
|
|
66
|
+
59141: "https://api-sepolia.lineascan.build/api",
|
|
67
|
+
534351: "https://api-sepolia.scrollscan.com/api",
|
|
68
|
+
168587773: "https://api-sepolia.blastscan.io/api",
|
|
69
|
+
};
|
|
43
70
|
/**
|
|
44
|
-
* Fetch ABI from Etherscan
|
|
45
|
-
*
|
|
71
|
+
* Fetch ABI from Etherscan/Etherscan-compatible chain explorer or Sourcify as fallback.
|
|
72
|
+
* Supports all 26+ major EVM mainnets and testnets.
|
|
46
73
|
*/
|
|
47
|
-
async function fetchAbi(address,
|
|
74
|
+
async function fetchAbi(address, chainId = 1, apiKey) {
|
|
48
75
|
const addr = address.toLowerCase();
|
|
49
|
-
// Try Etherscan
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
76
|
+
// Try Etherscan-compatible explorer API
|
|
77
|
+
const baseExplorer = EXPLORER_APIS[chainId] || EXPLORER_APIS[1];
|
|
78
|
+
try {
|
|
79
|
+
const keyParam = apiKey ? `&apikey=${apiKey}` : "";
|
|
80
|
+
const url = `${baseExplorer}?module=contract&action=getabi&address=${addr}${keyParam}`;
|
|
81
|
+
const resp = await fetch(url);
|
|
82
|
+
const json = await resp.json();
|
|
83
|
+
if (json.status === "1" && json.result) {
|
|
84
|
+
const parsed = JSON.parse(json.result);
|
|
85
|
+
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
86
|
+
console.log(` Fetched verified ABI from block explorer for ${address}`);
|
|
87
|
+
return parsed;
|
|
61
88
|
}
|
|
62
89
|
}
|
|
63
|
-
catch {
|
|
64
|
-
// fall through
|
|
65
|
-
}
|
|
66
90
|
}
|
|
67
|
-
|
|
91
|
+
catch {
|
|
92
|
+
// fall through to Sourcify
|
|
93
|
+
}
|
|
94
|
+
// Try Sourcify (supports any chainId, no API key required)
|
|
68
95
|
try {
|
|
69
|
-
|
|
70
|
-
const url = `https://repo.sourcify.dev/contracts/full_match/1/${addr}/metadata.json`;
|
|
96
|
+
const url = `https://repo.sourcify.dev/contracts/full_match/${chainId}/${addr}/metadata.json`;
|
|
71
97
|
const resp = await fetch(url);
|
|
72
98
|
if (resp.ok) {
|
|
73
99
|
const json = await resp.json();
|
|
74
100
|
const abi = json?.output?.abi;
|
|
75
101
|
if (Array.isArray(abi) && abi.length > 0) {
|
|
76
|
-
console.log(` Fetched ABI from Sourcify for ${address}`);
|
|
102
|
+
console.log(` Fetched verified ABI from Sourcify for ${address}`);
|
|
77
103
|
return abi;
|
|
78
104
|
}
|
|
79
105
|
}
|
|
@@ -91,8 +117,8 @@ function addCommand() {
|
|
|
91
117
|
.argument("[name]", "Name of the component")
|
|
92
118
|
.option("--address <address>", "Contract address")
|
|
93
119
|
.option("--abi <path>", "Path to ABI JSON file")
|
|
94
|
-
.option("--fetch-abi", "Auto-fetch ABI from
|
|
95
|
-
.option("--etherscan-key <key>", "
|
|
120
|
+
.option("--fetch-abi", "Auto-fetch ABI from block explorer / Sourcify")
|
|
121
|
+
.option("--etherscan-key <key>", "Explorer API key for ABI fetch (optional)")
|
|
96
122
|
.option("--start-block <block>", "Starting block number", "0")
|
|
97
123
|
.action(async (type, nameArg, opts) => {
|
|
98
124
|
const projectDir = process.cwd();
|
|
@@ -116,6 +142,7 @@ function addCommand() {
|
|
|
116
142
|
if (!config.dataSources) {
|
|
117
143
|
config.dataSources = [];
|
|
118
144
|
}
|
|
145
|
+
const chainId = config.network?.chainId ?? 1;
|
|
119
146
|
// 1. Resolve contract name
|
|
120
147
|
let contractName = nameArg;
|
|
121
148
|
if (!contractName) {
|
|
@@ -168,7 +195,6 @@ function addCommand() {
|
|
|
168
195
|
fs.mkdirSync(abisDir, { recursive: true });
|
|
169
196
|
const targetAbiFile = path.join(abisDir, `${contractName}.json`);
|
|
170
197
|
if (opts.abi) {
|
|
171
|
-
// Explicit local file
|
|
172
198
|
const sourceAbi = path.resolve(projectDir, opts.abi);
|
|
173
199
|
if (!fs.existsSync(sourceAbi)) {
|
|
174
200
|
console.error(`Error: Specified ABI file not found: ${opts.abi}`);
|
|
@@ -178,9 +204,8 @@ function addCommand() {
|
|
|
178
204
|
console.log(` Copied ABI from ${opts.abi} to abis/${contractName}.json`);
|
|
179
205
|
}
|
|
180
206
|
else if (opts.fetchAbi) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const abi = await fetchAbi(contractAddress, opts.etherscanKey);
|
|
207
|
+
console.log(` Fetching ABI for ${contractAddress} (Chain ID: ${chainId}) ...`);
|
|
208
|
+
const abi = await fetchAbi(contractAddress, chainId, opts.etherscanKey);
|
|
184
209
|
if (abi) {
|
|
185
210
|
fs.writeFileSync(targetAbiFile, JSON.stringify(abi, null, 2));
|
|
186
211
|
console.log(` Saved fetched ABI to abis/${contractName}.json`);
|
|
@@ -191,7 +216,6 @@ function addCommand() {
|
|
|
191
216
|
}
|
|
192
217
|
}
|
|
193
218
|
else if (!fs.existsSync(targetAbiFile)) {
|
|
194
|
-
// Default placeholder
|
|
195
219
|
fs.writeFileSync(targetAbiFile, JSON.stringify(templates_1.ERC20_ABI, null, 2));
|
|
196
220
|
console.log(` Created default ERC-20 placeholder ABI at abis/${contractName}.json`);
|
|
197
221
|
}
|
package/dist/commands/build.js
CHANGED
|
@@ -55,6 +55,13 @@ function buildCommand() {
|
|
|
55
55
|
console.error(`Error: entry file not found: ${opts.entry}`);
|
|
56
56
|
process.exit(1);
|
|
57
57
|
}
|
|
58
|
+
// Check if generated bindings exist
|
|
59
|
+
const generatedSchema = path.join(projectDir, "src", "generated", "schema.ts");
|
|
60
|
+
const generatedEvents = path.join(projectDir, "src", "generated", "events.ts");
|
|
61
|
+
if (!fs.existsSync(generatedSchema) || !fs.existsSync(generatedEvents)) {
|
|
62
|
+
console.warn("Notice: Generated bindings (src/generated/) not found or incomplete.");
|
|
63
|
+
console.warn("Tip: Run `logrix codegen` before `logrix build` to generate typed classes.\n");
|
|
64
|
+
}
|
|
58
65
|
// Ensure output directory exists
|
|
59
66
|
fs.mkdirSync(path.dirname(outFile), { recursive: true });
|
|
60
67
|
// Prefer logrix.yaml mapping.file if present
|
|
@@ -81,7 +88,7 @@ function buildCommand() {
|
|
|
81
88
|
ascBin = "asc"; // global
|
|
82
89
|
}
|
|
83
90
|
const optimizeFlag = opts.debug ? "" : "--optimize";
|
|
84
|
-
const
|
|
91
|
+
const ascCmd = [
|
|
85
92
|
ascBin,
|
|
86
93
|
resolvedEntry,
|
|
87
94
|
"--config", "tsconfig.json",
|
|
@@ -91,14 +98,18 @@ function buildCommand() {
|
|
|
91
98
|
]
|
|
92
99
|
.filter(Boolean)
|
|
93
100
|
.join(" ");
|
|
94
|
-
console.log(`Running: ${
|
|
101
|
+
console.log(`Running: ${ascCmd}\n`);
|
|
95
102
|
try {
|
|
96
|
-
(0, child_process_1.execSync)(
|
|
103
|
+
(0, child_process_1.execSync)(ascCmd, { cwd: projectDir, stdio: "inherit" });
|
|
97
104
|
const stat = fs.statSync(outFile);
|
|
98
105
|
console.log(`\nBuild complete: ${path.relative(projectDir, outFile)} (${stat.size} bytes)`);
|
|
99
106
|
}
|
|
100
107
|
catch {
|
|
101
108
|
console.error("\nBuild failed.");
|
|
109
|
+
console.error("Troubleshooting tips:");
|
|
110
|
+
console.error(" 1. Verify AssemblyScript types in src/mapping.ts (e.g. use BigInt / Address / Bytes from @logrix/sdk)");
|
|
111
|
+
console.error(" 2. Run `logrix codegen` to refresh generated bindings");
|
|
112
|
+
console.error(" 3. Run `logrix validate` to check configuration syntax\n");
|
|
102
113
|
process.exit(1);
|
|
103
114
|
}
|
|
104
115
|
});
|
package/dist/commands/codegen.js
CHANGED
|
@@ -57,7 +57,7 @@ function solTypeToAs(solType) {
|
|
|
57
57
|
function generateEventsTs(dataSources, projectDir) {
|
|
58
58
|
const lines = [
|
|
59
59
|
"// Auto-generated by `logrix codegen`. Do not edit.",
|
|
60
|
-
'import { EventLog, BigInt, Address, Bytes } from "@logrix/sdk";',
|
|
60
|
+
'import { EventLog, BigInt, Address, Bytes, decodeDynamicString, decodeDynamicBytes } from "@logrix/sdk";',
|
|
61
61
|
"",
|
|
62
62
|
];
|
|
63
63
|
// Contract address constants for multi-contract indexers
|
|
@@ -138,23 +138,31 @@ function generateEventsTs(dataSources, projectDir) {
|
|
|
138
138
|
topicIdx++;
|
|
139
139
|
}
|
|
140
140
|
else {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
lines.push(` const offset = ${nonIndexedIdx * 64};`);
|
|
144
|
-
lines.push(` const rawWord = dataHex.length >= offset + 64 ? "0x" + dataHex.slice(offset, offset + 64) : (offset == 0 ? raw.data : "");`);
|
|
145
|
-
if (asType === "BigInt") {
|
|
146
|
-
lines.push(` this.${inp.name} = BigInt.fromString(rawWord);`);
|
|
147
|
-
}
|
|
148
|
-
else if (asType === "Address") {
|
|
149
|
-
lines.push(` this.${inp.name} = Address.fromString(rawWord);`);
|
|
141
|
+
if (inp.type === "string") {
|
|
142
|
+
lines.push(` this.${inp.name} = decodeDynamicString(raw.data, ${nonIndexedIdx});`);
|
|
150
143
|
}
|
|
151
|
-
else if (
|
|
152
|
-
lines.push(`
|
|
144
|
+
else if (inp.type === "bytes") {
|
|
145
|
+
lines.push(` this.${inp.name} = decodeDynamicBytes(raw.data, ${nonIndexedIdx});`);
|
|
153
146
|
}
|
|
154
147
|
else {
|
|
155
|
-
lines.push(`
|
|
148
|
+
lines.push(` {`);
|
|
149
|
+
lines.push(` const dataHex = raw.data.startsWith("0x") ? raw.data.slice(2) : raw.data;`);
|
|
150
|
+
lines.push(` const offset = ${nonIndexedIdx * 64};`);
|
|
151
|
+
lines.push(` const rawWord = dataHex.length >= offset + 64 ? "0x" + dataHex.slice(offset, offset + 64) : (offset == 0 ? raw.data : "");`);
|
|
152
|
+
if (asType === "BigInt") {
|
|
153
|
+
lines.push(` this.${inp.name} = BigInt.fromString(rawWord);`);
|
|
154
|
+
}
|
|
155
|
+
else if (asType === "Address") {
|
|
156
|
+
lines.push(` this.${inp.name} = Address.fromString(rawWord);`);
|
|
157
|
+
}
|
|
158
|
+
else if (asType === "Bytes") {
|
|
159
|
+
lines.push(` this.${inp.name} = Bytes.fromHexString(rawWord);`);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
lines.push(` this.${inp.name} = rawWord;`);
|
|
163
|
+
}
|
|
164
|
+
lines.push(` }`);
|
|
156
165
|
}
|
|
157
|
-
lines.push(` }`);
|
|
158
166
|
nonIndexedIdx++;
|
|
159
167
|
}
|
|
160
168
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -33,19 +33,42 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.NETWORKS = void 0;
|
|
36
37
|
exports.initCommand = initCommand;
|
|
37
38
|
const commander_1 = require("commander");
|
|
38
39
|
const prompts_1 = require("@inquirer/prompts");
|
|
39
40
|
const fs = __importStar(require("fs"));
|
|
40
41
|
const path = __importStar(require("path"));
|
|
41
42
|
const templates_1 = require("../templates");
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
43
|
+
exports.NETWORKS = {
|
|
44
|
+
// Mainnets
|
|
45
|
+
"ethereum": { name: "Ethereum Mainnet", chainId: 1, rpcUrl: "https://eth.llamarpc.com", category: "mainnet" },
|
|
46
|
+
"arbitrum-one": { name: "Arbitrum One", chainId: 42161, rpcUrl: "https://arb1.arbitrum.io/rpc", category: "mainnet" },
|
|
47
|
+
"base": { name: "Base Mainnet", chainId: 8453, rpcUrl: "https://mainnet.base.org", category: "mainnet" },
|
|
48
|
+
"optimism": { name: "Optimism Mainnet", chainId: 10, rpcUrl: "https://mainnet.optimism.io", category: "mainnet" },
|
|
49
|
+
"polygon": { name: "Polygon PoS", chainId: 137, rpcUrl: "https://polygon-rpc.com", category: "mainnet" },
|
|
50
|
+
"bsc": { name: "BNB Smart Chain", chainId: 56, rpcUrl: "https://binance.llamarpc.com", category: "mainnet" },
|
|
51
|
+
"avalanche": { name: "Avalanche C-Chain", chainId: 43114, rpcUrl: "https://api.avax.network/ext/bc/C/rpc", category: "mainnet" },
|
|
52
|
+
"linea": { name: "Linea Mainnet", chainId: 59144, rpcUrl: "https://rpc.linea.build", category: "mainnet" },
|
|
53
|
+
"scroll": { name: "Scroll Mainnet", chainId: 534352, rpcUrl: "https://rpc.scroll.io", category: "mainnet" },
|
|
54
|
+
"blast": { name: "Blast Mainnet", chainId: 81457, rpcUrl: "https://rpc.blast.io", category: "mainnet" },
|
|
55
|
+
"zksync": { name: "ZKsync Era", chainId: 324, rpcUrl: "https://mainnet.era.zksync.io", category: "mainnet" },
|
|
56
|
+
"gnosis": { name: "Gnosis Chain", chainId: 100, rpcUrl: "https://rpc.gnosischain.com", category: "mainnet" },
|
|
57
|
+
"fantom": { name: "Fantom Opera", chainId: 250, rpcUrl: "https://rpcapi.fantom.network", category: "mainnet" },
|
|
58
|
+
"celo": { name: "Celo Mainnet", chainId: 42220, rpcUrl: "https://forno.celo.org", category: "mainnet" },
|
|
59
|
+
"polygon-zkevm": { name: "Polygon zkEVM", chainId: 1101, rpcUrl: "https://zkevm-rpc.com", category: "mainnet" },
|
|
60
|
+
// Testnets
|
|
61
|
+
"sepolia": { name: "Ethereum Sepolia", chainId: 11155111, rpcUrl: "https://rpc.sepolia.org", category: "testnet" },
|
|
62
|
+
"holesky": { name: "Ethereum Holesky", chainId: 17000, rpcUrl: "https://ethereum-holesky-rpc.publicnode.com", category: "testnet" },
|
|
63
|
+
"arbitrum-sepolia": { name: "Arbitrum Sepolia", chainId: 421614, rpcUrl: "https://sepolia-rollup.arbitrum.io/rpc", category: "testnet" },
|
|
64
|
+
"base-sepolia": { name: "Base Sepolia", chainId: 84532, rpcUrl: "https://sepolia.base.org", category: "testnet" },
|
|
65
|
+
"optimism-sepolia": { name: "Optimism Sepolia", chainId: 11155420, rpcUrl: "https://sepolia.optimism.io", category: "testnet" },
|
|
66
|
+
"polygon-amoy": { name: "Polygon Amoy", chainId: 80002, rpcUrl: "https://rpc-amoy.polygon.technology", category: "testnet" },
|
|
67
|
+
"bsc-testnet": { name: "BNB Smart Chain Testnet", chainId: 97, rpcUrl: "https://data-seed-prebsc-1-s1.binance.org:8545", category: "testnet" },
|
|
68
|
+
"avalanche-fuji": { name: "Avalanche Fuji", chainId: 43113, rpcUrl: "https://api.avax-test.network/ext/bc/C/rpc", category: "testnet" },
|
|
69
|
+
"linea-sepolia": { name: "Linea Sepolia", chainId: 59141, rpcUrl: "https://rpc.sepolia.linea.build", category: "testnet" },
|
|
70
|
+
"scroll-sepolia": { name: "Scroll Sepolia", chainId: 534351, rpcUrl: "https://sepolia-rpc.scroll.io", category: "testnet" },
|
|
71
|
+
"blast-sepolia": { name: "Blast Sepolia", chainId: 168587773, rpcUrl: "https://sepolia.blast.io", category: "testnet" },
|
|
49
72
|
};
|
|
50
73
|
function initCommand() {
|
|
51
74
|
const cmd = new commander_1.Command("init");
|
|
@@ -53,7 +76,7 @@ function initCommand() {
|
|
|
53
76
|
.description("Scaffold a new Logrix indexer project")
|
|
54
77
|
.argument("[name]", "Project directory name")
|
|
55
78
|
.option("-y, --yes", "Skip prompts and use defaults (non-interactive)")
|
|
56
|
-
.option("--network <network>", "Network preset name (e.g. arbitrum-one, ethereum, base)")
|
|
79
|
+
.option("--network <network>", "Network preset name (e.g. arbitrum-one, ethereum, base, sepolia)")
|
|
57
80
|
.option("--rpc <url>", "Custom RPC endpoint URL")
|
|
58
81
|
.option("--contract-name <name>", "Contract name")
|
|
59
82
|
.option("--address <address>", "Contract target address")
|
|
@@ -83,22 +106,22 @@ function initCommand() {
|
|
|
83
106
|
}
|
|
84
107
|
// Network selection
|
|
85
108
|
let networkKey = opts.network;
|
|
86
|
-
if (!networkKey || !NETWORKS[networkKey]) {
|
|
109
|
+
if (!networkKey || !exports.NETWORKS[networkKey]) {
|
|
87
110
|
if (isNonInteractive) {
|
|
88
111
|
networkKey = "arbitrum-one";
|
|
89
112
|
}
|
|
90
113
|
else {
|
|
91
114
|
networkKey = await (0, prompts_1.select)({
|
|
92
115
|
message: "Network:",
|
|
93
|
-
choices: Object.entries(NETWORKS).map(([key, net]) => ({
|
|
94
|
-
name:
|
|
116
|
+
choices: Object.entries(exports.NETWORKS).map(([key, net]) => ({
|
|
117
|
+
name: `[${net.category.toUpperCase()}] ${net.name} (Chain ID: ${net.chainId})`,
|
|
95
118
|
value: key,
|
|
96
119
|
})),
|
|
97
120
|
default: "arbitrum-one",
|
|
98
121
|
});
|
|
99
122
|
}
|
|
100
123
|
}
|
|
101
|
-
const network = NETWORKS[networkKey] || NETWORKS["arbitrum-one"];
|
|
124
|
+
const network = exports.NETWORKS[networkKey] || exports.NETWORKS["arbitrum-one"];
|
|
102
125
|
// RPC URL
|
|
103
126
|
let rpcUrl = opts.rpc;
|
|
104
127
|
if (!rpcUrl) {
|
|
@@ -140,7 +163,7 @@ function initCommand() {
|
|
|
140
163
|
contractAddress = await (0, prompts_1.input)({
|
|
141
164
|
message: "Contract address:",
|
|
142
165
|
default: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
|
143
|
-
validate: (v) => /^0x[0-9a-fA-F]{40}$/.test(v.trim()) || "Must be a valid 0x address",
|
|
166
|
+
validate: (v) => /^0x[0-9a-fA-F]{40}$/.test(v.trim()) || "Must be a valid 0x hex address",
|
|
144
167
|
});
|
|
145
168
|
}
|
|
146
169
|
}
|
|
@@ -153,41 +176,34 @@ function initCommand() {
|
|
|
153
176
|
}
|
|
154
177
|
else {
|
|
155
178
|
startBlockStr = await (0, prompts_1.input)({
|
|
156
|
-
message: "Start block:",
|
|
179
|
+
message: "Start block (indexing starting point):",
|
|
157
180
|
default: "0",
|
|
158
181
|
validate: (v) => /^\d+$/.test(v.trim()) || "Must be a non-negative integer",
|
|
159
182
|
});
|
|
160
183
|
}
|
|
161
184
|
}
|
|
185
|
+
const startBlock = parseInt(startBlockStr.trim(), 10);
|
|
162
186
|
const answers = {
|
|
163
187
|
projectName,
|
|
164
|
-
networkName:
|
|
188
|
+
networkName: networkKey,
|
|
165
189
|
chainId: network.chainId,
|
|
166
190
|
rpcUrl,
|
|
167
191
|
contractName,
|
|
168
192
|
contractAddress,
|
|
169
|
-
startBlock
|
|
193
|
+
startBlock,
|
|
170
194
|
};
|
|
171
|
-
console.log(
|
|
172
|
-
//
|
|
195
|
+
console.log(`\nScaffolding in ./${projectName}/ ...\n`);
|
|
196
|
+
// Create directories
|
|
173
197
|
fs.mkdirSync(path.join(targetDir, "abis"), { recursive: true });
|
|
174
198
|
fs.mkdirSync(path.join(targetDir, "src"), { recursive: true });
|
|
175
|
-
|
|
176
|
-
// logrix.yaml
|
|
199
|
+
// Write files
|
|
177
200
|
fs.writeFileSync(path.join(targetDir, "logrix.yaml"), (0, templates_1.logrixYamlTemplate)(answers));
|
|
178
|
-
// schema.graphql
|
|
179
201
|
fs.writeFileSync(path.join(targetDir, "schema.graphql"), (0, templates_1.schemaGraphqlTemplate)());
|
|
180
|
-
|
|
181
|
-
fs.writeFileSync(path.join(targetDir, "
|
|
182
|
-
// src/mapping.ts
|
|
183
|
-
fs.writeFileSync(path.join(targetDir, "src", "mapping.ts"), (0, templates_1.mappingTemplate)(answers.contractName));
|
|
184
|
-
// package.json
|
|
202
|
+
fs.writeFileSync(path.join(targetDir, "abis", `${contractName}.json`), JSON.stringify(templates_1.ERC20_ABI, null, 2));
|
|
203
|
+
fs.writeFileSync(path.join(targetDir, "src", "mapping.ts"), (0, templates_1.mappingTemplate)(contractName));
|
|
185
204
|
fs.writeFileSync(path.join(targetDir, "package.json"), JSON.stringify((0, templates_1.packageJsonTemplate)(projectName), null, 2));
|
|
186
|
-
// tsconfig.json (AS config)
|
|
187
205
|
fs.writeFileSync(path.join(targetDir, "tsconfig.json"), JSON.stringify((0, templates_1.tsconfigTemplate)(), null, 2));
|
|
188
|
-
// .gitignore
|
|
189
206
|
fs.writeFileSync(path.join(targetDir, ".gitignore"), (0, templates_1.gitignoreTemplate)());
|
|
190
|
-
// README.md
|
|
191
207
|
fs.writeFileSync(path.join(targetDir, "README.md"), (0, templates_1.readmeTemplate)(projectName));
|
|
192
208
|
console.log("Done. Next steps:\n");
|
|
193
209
|
console.log(` cd ${projectName}`);
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.statusCommand = statusCommand;
|
|
37
|
+
const commander_1 = require("commander");
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const yaml_1 = require("yaml");
|
|
41
|
+
async function jsonRpcCall(url, method, params = []) {
|
|
42
|
+
const start = Date.now();
|
|
43
|
+
try {
|
|
44
|
+
const res = await fetch(url, {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: { "Content-Type": "application/json" },
|
|
47
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
|
|
48
|
+
});
|
|
49
|
+
const latencyMs = Date.now() - start;
|
|
50
|
+
if (!res.ok) {
|
|
51
|
+
return { error: `HTTP ${res.status}: ${res.statusText}`, latencyMs };
|
|
52
|
+
}
|
|
53
|
+
const json = await res.json();
|
|
54
|
+
return { result: json.result, error: json.error, latencyMs };
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
return { error: e.message, latencyMs: Date.now() - start };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function statusCommand() {
|
|
61
|
+
const cmd = new commander_1.Command("status");
|
|
62
|
+
cmd
|
|
63
|
+
.description("Check RPC connectivity, network block height, and contract deployment status")
|
|
64
|
+
.action(async () => {
|
|
65
|
+
const projectDir = process.cwd();
|
|
66
|
+
const configPath = path.join(projectDir, "logrix.yaml");
|
|
67
|
+
if (!fs.existsSync(configPath)) {
|
|
68
|
+
console.error("Error: logrix.yaml not found. Run this command from your project root.");
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
let config;
|
|
72
|
+
try {
|
|
73
|
+
config = (0, yaml_1.parse)(fs.readFileSync(configPath, "utf-8"));
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
console.error(`Error parsing logrix.yaml: ${e.message}`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
const networkName = config.network?.name || "unspecified";
|
|
80
|
+
const configuredChainId = config.network?.chainId;
|
|
81
|
+
const rpcUrl = config.network?.rpcUrl;
|
|
82
|
+
console.log("\nLogrix Indexer Status");
|
|
83
|
+
console.log("============================================================");
|
|
84
|
+
console.log(`Network Name: ${networkName}`);
|
|
85
|
+
console.log(`Configured Chain: ${configuredChainId ?? "Not set"}`);
|
|
86
|
+
console.log(`RPC Endpoint: ${rpcUrl || "None"}`);
|
|
87
|
+
if (!rpcUrl) {
|
|
88
|
+
console.error("\nError: No RPC URL configured in logrix.yaml.");
|
|
89
|
+
console.log("============================================================\n");
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
// 1. Query block height
|
|
93
|
+
process.stdout.write("Connecting to RPC endpoint ... ");
|
|
94
|
+
const blockRes = await jsonRpcCall(rpcUrl, "eth_blockNumber");
|
|
95
|
+
if (blockRes.error || !blockRes.result) {
|
|
96
|
+
console.log("FAILED");
|
|
97
|
+
console.error(`RPC Error: ${JSON.stringify(blockRes.error)} (${blockRes.latencyMs} ms)`);
|
|
98
|
+
console.log("============================================================\n");
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
const latestBlock = parseInt(blockRes.result, 16);
|
|
102
|
+
console.log(`ONLINE (${blockRes.latencyMs} ms)`);
|
|
103
|
+
console.log(`Latest Block: #${latestBlock.toLocaleString()}`);
|
|
104
|
+
// 2. Query chain ID
|
|
105
|
+
const chainRes = await jsonRpcCall(rpcUrl, "eth_chainId");
|
|
106
|
+
if (chainRes.result) {
|
|
107
|
+
const reportedChainId = parseInt(chainRes.result, 16);
|
|
108
|
+
const match = configuredChainId === reportedChainId;
|
|
109
|
+
console.log(`Chain ID Check: Reported ${reportedChainId} (${match ? "VERIFIED MATCH" : "MISMATCH WARNING"})`);
|
|
110
|
+
}
|
|
111
|
+
// 3. Inspect contracts
|
|
112
|
+
const dataSources = config.dataSources || [];
|
|
113
|
+
console.log(`\nContracts Monitored (${dataSources.length}):`);
|
|
114
|
+
console.log("------------------------------------------------------------");
|
|
115
|
+
for (const ds of dataSources) {
|
|
116
|
+
const addr = ds.source?.address || "";
|
|
117
|
+
const name = ds.name || "Unnamed";
|
|
118
|
+
const startBlock = ds.source?.startBlock ?? 0;
|
|
119
|
+
if (!addr) {
|
|
120
|
+
console.log(` - ${name}: [NO ADDRESS SPECIFIED]`);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const codeRes = await jsonRpcCall(rpcUrl, "eth_getCode", [addr, "latest"]);
|
|
124
|
+
const code = codeRes.result || "";
|
|
125
|
+
const isDeployed = code.length > 2 && code !== "0x" && code !== "0x0";
|
|
126
|
+
const byteSize = isDeployed ? (code.length - 2) / 2 : 0;
|
|
127
|
+
console.log(` - Name: ${name}`);
|
|
128
|
+
console.log(` Address: ${addr}`);
|
|
129
|
+
console.log(` Deployed: ${isDeployed ? `YES (${byteSize.toLocaleString()} bytes bytecode)` : "NO (Empty bytecode / Not found)"}`);
|
|
130
|
+
console.log(` Start Block: #${startBlock.toLocaleString()}`);
|
|
131
|
+
if (latestBlock > startBlock) {
|
|
132
|
+
const lag = latestBlock - startBlock;
|
|
133
|
+
console.log(` Sync Scope: #${startBlock} -> #${latestBlock} (${lag.toLocaleString()} blocks to process)`);
|
|
134
|
+
}
|
|
135
|
+
console.log("");
|
|
136
|
+
}
|
|
137
|
+
console.log("============================================================\n");
|
|
138
|
+
});
|
|
139
|
+
return cmd;
|
|
140
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -9,11 +9,12 @@ const add_1 = require("./commands/add");
|
|
|
9
9
|
const validate_1 = require("./commands/validate");
|
|
10
10
|
const export_values_1 = require("./commands/export_values");
|
|
11
11
|
const test_1 = require("./commands/test");
|
|
12
|
+
const status_1 = require("./commands/status");
|
|
12
13
|
const program = new commander_1.Command();
|
|
13
14
|
program
|
|
14
15
|
.name("logrix")
|
|
15
16
|
.description("Developer CLI for Logrix blockchain indexers")
|
|
16
|
-
.version("0.3.
|
|
17
|
+
.version("0.3.2");
|
|
17
18
|
program.addCommand((0, init_1.initCommand)());
|
|
18
19
|
program.addCommand((0, add_1.addCommand)());
|
|
19
20
|
program.addCommand((0, codegen_1.codegenCommand)());
|
|
@@ -21,4 +22,5 @@ program.addCommand((0, validate_1.validateCommand)());
|
|
|
21
22
|
program.addCommand((0, build_1.buildCommand)());
|
|
22
23
|
program.addCommand((0, export_values_1.exportValuesCommand)());
|
|
23
24
|
program.addCommand((0, test_1.testCommand)());
|
|
25
|
+
program.addCommand((0, status_1.statusCommand)());
|
|
24
26
|
program.parse(process.argv);
|
package/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
// Root
|
|
1
|
+
// Root entry point -- re-exports runtime and testing utilities.
|
|
2
2
|
// asc resolves @logrix/sdk to this file when building user handler projects.
|
|
3
3
|
export * from "./src/runtime/index";
|
|
4
|
+
export * from "./src/testing/index";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logrix/sdk",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Official CLI and AssemblyScript runtime library for Logrix blockchain indexers",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"bin": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"files": [
|
|
14
14
|
"dist",
|
|
15
15
|
"src/runtime",
|
|
16
|
+
"src/testing",
|
|
16
17
|
"index.ts",
|
|
17
18
|
"tsconfig.as.json",
|
|
18
19
|
"README.md"
|
package/src/runtime/index.ts
CHANGED
|
@@ -258,6 +258,58 @@ export class Bytes {
|
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
// Dynamic ABI Offset Decoding Helpers (Solidity Head/Tail)
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
function parseHexI32(hex: string): i32 {
|
|
266
|
+
let val: i32 = 0;
|
|
267
|
+
for (let i = 0; i < hex.length; i++) {
|
|
268
|
+
val = (val << 4) | parseHexChar(hex.charCodeAt(i));
|
|
269
|
+
}
|
|
270
|
+
return val;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function decodeDynamicString(dataHex: string, paramIndex: i32): string {
|
|
274
|
+
let clean = dataHex.startsWith("0x") ? dataHex.slice(2) : dataHex;
|
|
275
|
+
const headOffset = paramIndex * 64;
|
|
276
|
+
if (clean.length < headOffset + 64) return "";
|
|
277
|
+
const ptrWord = clean.slice(headOffset, headOffset + 64);
|
|
278
|
+
const offsetBytes = parseHexI32(ptrWord);
|
|
279
|
+
const offsetHex = offsetBytes * 2;
|
|
280
|
+
if (clean.length < offsetHex + 64) return "";
|
|
281
|
+
const lenWord = clean.slice(offsetHex, offsetHex + 64);
|
|
282
|
+
const byteLen = parseHexI32(lenWord);
|
|
283
|
+
const contentHexStart = offsetHex + 64;
|
|
284
|
+
const contentHexEnd = contentHexStart + byteLen * 2;
|
|
285
|
+
if (clean.length < contentHexEnd) return "";
|
|
286
|
+
const contentHex = clean.slice(contentHexStart, contentHexEnd);
|
|
287
|
+
const bytes = new Uint8Array(byteLen);
|
|
288
|
+
for (let i = 0; i < byteLen; i++) {
|
|
289
|
+
const high = parseHexChar(contentHex.charCodeAt(i * 2));
|
|
290
|
+
const low = parseHexChar(contentHex.charCodeAt(i * 2 + 1));
|
|
291
|
+
bytes[i] = ((high << 4) | low) as u8;
|
|
292
|
+
}
|
|
293
|
+
return String.UTF8.decode(bytes.buffer);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function decodeDynamicBytes(dataHex: string, paramIndex: i32): Bytes {
|
|
297
|
+
let clean = dataHex.startsWith("0x") ? dataHex.slice(2) : dataHex;
|
|
298
|
+
const headOffset = paramIndex * 64;
|
|
299
|
+
if (clean.length < headOffset + 64) return Bytes.fromHexString("0x");
|
|
300
|
+
const ptrWord = clean.slice(headOffset, headOffset + 64);
|
|
301
|
+
const offsetBytes = parseHexI32(ptrWord);
|
|
302
|
+
const offsetHex = offsetBytes * 2;
|
|
303
|
+
if (clean.length < offsetHex + 64) return Bytes.fromHexString("0x");
|
|
304
|
+
const lenWord = clean.slice(offsetHex, offsetHex + 64);
|
|
305
|
+
const byteLen = parseHexI32(lenWord);
|
|
306
|
+
const contentHexStart = offsetHex + 64;
|
|
307
|
+
const contentHexEnd = contentHexStart + byteLen * 2;
|
|
308
|
+
if (clean.length < contentHexEnd) return Bytes.fromHexString("0x");
|
|
309
|
+
const contentHex = clean.slice(contentHexStart, contentHexEnd);
|
|
310
|
+
return Bytes.fromHexString("0x" + contentHex);
|
|
311
|
+
}
|
|
312
|
+
|
|
261
313
|
// ---------------------------------------------------------------------------
|
|
262
314
|
// Keccak-256 Implementation & Crypto namespace
|
|
263
315
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { EventLog } from "../runtime";
|
|
2
|
+
|
|
3
|
+
export interface MockEventLogConfig {
|
|
4
|
+
address?: string;
|
|
5
|
+
blockNumber?: number;
|
|
6
|
+
blockHash?: string;
|
|
7
|
+
transactionHash?: string;
|
|
8
|
+
logIndex?: number;
|
|
9
|
+
blockTimestamp?: number;
|
|
10
|
+
topics?: string[];
|
|
11
|
+
data?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Construct an EventLog instance pre-populated with test values.
|
|
16
|
+
* Useful for writing local unit tests for AssemblyScript/TypeScript mapping handlers.
|
|
17
|
+
*/
|
|
18
|
+
export function createMockEventLog(config: MockEventLogConfig = {}): EventLog {
|
|
19
|
+
const log = new EventLog();
|
|
20
|
+
log.address = config.address || "0x0000000000000000000000000000000000000000";
|
|
21
|
+
log.block_number = BigInt(config.blockNumber ?? 1) as any;
|
|
22
|
+
log.block_hash = config.blockHash || "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
23
|
+
log.transaction_hash = config.transactionHash || "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
|
|
24
|
+
log.log_index = (config.logIndex ?? 0) as any;
|
|
25
|
+
log.block_timestamp = BigInt(config.blockTimestamp ?? 1700000000) as any;
|
|
26
|
+
log.topics = config.topics || [];
|
|
27
|
+
log.data = config.data || "0x";
|
|
28
|
+
return log;
|
|
29
|
+
}
|