@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.
@@ -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 (mainnet) or Sourcify as fallback.
45
- * Returns the parsed ABI array, or null if both fail.
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, etherscanKey) {
74
+ async function fetchAbi(address, chainId = 1, apiKey) {
48
75
  const addr = address.toLowerCase();
49
- // Try Etherscan first if API key provided
50
- if (etherscanKey) {
51
- try {
52
- const url = `https://api.etherscan.io/api?module=contract&action=getabi&address=${addr}&apikey=${etherscanKey}`;
53
- const resp = await fetch(url);
54
- const json = await resp.json();
55
- if (json.status === "1" && json.result) {
56
- const parsed = JSON.parse(json.result);
57
- if (Array.isArray(parsed) && parsed.length > 0) {
58
- console.log(` Fetched ABI from Etherscan for ${address}`);
59
- return parsed;
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
- // Try Sourcify (no key required)
91
+ catch {
92
+ // fall through to Sourcify
93
+ }
94
+ // Try Sourcify (supports any chainId, no API key required)
68
95
  try {
69
- // Sourcify returns verified contracts at chainId 1 (mainnet)
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 Etherscan / Sourcify")
95
- .option("--etherscan-key <key>", "Etherscan API key for ABI fetch (optional)")
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
- // Auto-fetch from Etherscan / Sourcify
182
- console.log(` Fetching ABI for ${contractAddress} ...`);
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
  }
@@ -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
- const NETWORKS = {
43
- "ethereum": { name: "ethereum", chainId: 1, rpcUrl: "https://eth.llamarpc.com" },
44
- "arbitrum-one": { name: "arbitrum-one", chainId: 42161, rpcUrl: "https://arb1.arbitrum.io/rpc" },
45
- "base": { name: "base", chainId: 8453, rpcUrl: "https://mainnet.base.org" },
46
- "polygon": { name: "polygon", chainId: 137, rpcUrl: "https://polygon-rpc.com" },
47
- "arbitrum-sepolia": { name: "arbitrum-sepolia", chainId: 421614, rpcUrl: "https://sepolia-rollup.arbitrum.io/rpc" },
48
- "sepolia": { name: "sepolia", chainId: 11155111, rpcUrl: "https://rpc.sepolia.org" },
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: `${net.name} (chain ${net.chainId})`,
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: network.name,
188
+ networkName: networkKey,
165
189
  chainId: network.chainId,
166
190
  rpcUrl,
167
191
  contractName,
168
192
  contractAddress,
169
- startBlock: parseInt(startBlockStr.trim(), 10),
193
+ startBlock,
170
194
  };
171
- console.log(`Scaffolding in ./${projectName}/ ...\n`);
172
- // Directory structure
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
- fs.mkdirSync(path.join(targetDir, "build"), { recursive: true });
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
- // Placeholder ABI
181
- fs.writeFileSync(path.join(targetDir, "abis", `${answers.contractName}.json`), JSON.stringify(templates_1.ERC20_ABI, null, 2));
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.0");
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)());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logrix/sdk",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Official CLI and AssemblyScript runtime library for Logrix blockchain indexers",
5
5
  "main": "index.ts",
6
6
  "bin": {