@logrix/sdk 0.3.0 → 0.3.1
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/dist/commands/add.js +53 -29
- package/dist/commands/init.js +45 -29
- package/dist/index.js +1 -1
- package/package.json +1 -1
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/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}`);
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ const program = new commander_1.Command();
|
|
|
13
13
|
program
|
|
14
14
|
.name("logrix")
|
|
15
15
|
.description("Developer CLI for Logrix blockchain indexers")
|
|
16
|
-
.version("0.3.
|
|
16
|
+
.version("0.3.1");
|
|
17
17
|
program.addCommand((0, init_1.initCommand)());
|
|
18
18
|
program.addCommand((0, add_1.addCommand)());
|
|
19
19
|
program.addCommand((0, codegen_1.codegenCommand)());
|