@alchemy/cli 0.2.2 → 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/README.md CHANGED
@@ -18,6 +18,23 @@ Or run without installing globally:
18
18
  npx @alchemy/cli <command>
19
19
  ```
20
20
 
21
+ ## Shell Completions
22
+
23
+ Enable Tab completion for all commands and subcommands:
24
+
25
+ ```bash
26
+ # zsh (add to ~/.zshrc)
27
+ echo 'eval "$(alchemy completions zsh)"' >> ~/.zshrc
28
+ source ~/.zshrc
29
+
30
+ # bash (add to ~/.bashrc)
31
+ echo 'eval "$(alchemy completions bash)"' >> ~/.bashrc
32
+ source ~/.bashrc
33
+
34
+ # fish
35
+ alchemy completions fish > ~/.config/fish/completions/alchemy.fish
36
+ ```
37
+
21
38
  ## Getting Started
22
39
 
23
40
  ### Authentication Quick Start
@@ -47,7 +64,7 @@ For setup commands, env vars, and resolution order, see [Authentication Referenc
47
64
  After auth is configured, use the CLI differently depending on who is driving it:
48
65
 
49
66
  - **Humans (interactive terminal):** start with `alchemy` and use the terminal UI/setup flow; this is the recommended path for human usage
50
- - **Agents/scripts (automation):** always use `--json` and prefer non-interactive execution (`--no-interactive`)
67
+ - **Agents/scripts (automation):** use `--json --no-interactive` to guarantee JSON output and disable prompts (JSON is auto-enabled when piped, but `--json` is a safe default)
51
68
 
52
69
  Quick usage examples:
53
70
 
@@ -86,7 +103,10 @@ Use `alchemy help` or `alchemy help <command>` for generated command help.
86
103
  |---|---|---|
87
104
  | `balance [address]` (`bal [address]`) | Gets ETH balance for an address | `alchemy bal 0x...` |
88
105
  | `tx [hash]` | Gets transaction + receipt by hash | `alchemy tx 0x...` |
106
+ | `receipt [hash]` | Gets transaction receipt (status, gas, logs) | `alchemy receipt 0x...` |
89
107
  | `block <number>` | Gets block details (`latest`, decimal, or hex) | `alchemy block latest` |
108
+ | `gas` | Gets current gas prices (base fee + priority fee) | `alchemy gas -n polygon-mainnet` |
109
+ | `logs` | Queries event logs (`eth_getLogs`) | `alchemy logs --address 0x... --from-block 18000000 --to-block 18000010` |
90
110
  | `rpc <method> [params...]` | Makes raw JSON-RPC call | `alchemy rpc eth_blockNumber` |
91
111
  | `trace <method> [params...]` | Calls Trace API methods | `alchemy trace call '{"to":"0x..."}' '["trace"]' latest` |
92
112
  | `debug <method> [params...]` | Calls Debug API methods | `alchemy debug traceTransaction "0x..."` |
@@ -136,8 +156,7 @@ Use `alchemy help` or `alchemy help <command>` for generated command help.
136
156
 
137
157
  | Command | What it does | Example |
138
158
  |---|---|---|
139
- | `network list` | Lists supported RPC networks | `alchemy network list --configured` |
140
- | `chains list` | Lists Admin API chain enums | `alchemy chains list` |
159
+ | `network list` | Lists RPC network IDs for use with `--network` (e.g. `eth-mainnet`) | `alchemy network list --configured` |
141
160
  | `solana rpc <method> [params...]` | Calls Solana JSON-RPC methods | `alchemy solana rpc getBalance '"<pubkey>"'` |
142
161
  | `solana das <method> [params...]` | Calls Solana DAS methods | `alchemy solana das getAssetsByOwner '{"ownerAddress":"<pubkey>"}'` |
143
162
 
@@ -147,6 +166,7 @@ Use `alchemy help` or `alchemy help <command>` for generated command help.
147
166
  |---|---|---|
148
167
  | `(no command)` | Starts interactive REPL mode (TTY only) | `alchemy` |
149
168
  | `apps list` | Lists apps (supports pagination/filtering) | `alchemy apps list --all` |
169
+ | `apps chains` | Lists Admin API chain identifiers (e.g. `ETH_MAINNET`) | `alchemy apps chains` |
150
170
  | `apps get <id>` | Gets app details | `alchemy apps get <app-id>` |
151
171
  | `apps create` | Creates app | `alchemy apps create --name "My App" --networks eth-mainnet` |
152
172
  | `apps update <id>` | Updates app name/description | `alchemy apps update <app-id> --name "New Name"` |
@@ -161,6 +181,7 @@ Use `alchemy help` or `alchemy help <command>` for generated command help.
161
181
  | `config get <key>` | Gets one config value | `alchemy config get network` |
162
182
  | `config list` | Lists all config values | `alchemy config list` |
163
183
  | `config reset [key]` | Resets one or all config values | `alchemy config reset --yes` |
184
+ | `completions <shell>` | Generates shell completion scripts (bash/zsh/fish) | `eval "$(alchemy completions zsh)"` |
164
185
  | `agent-prompt` | Emits complete agent/automation usage instructions | `alchemy --json agent-prompt` |
165
186
  | `version` | Prints CLI version | `alchemy version` |
166
187
 
@@ -184,7 +205,7 @@ These apply to all commands.
184
205
 
185
206
  | Flag | Env var | Description |
186
207
  |---|---|---|
187
- | `--json` | — | Force JSON output |
208
+ | `--json` | — | Force JSON output (auto-enabled when piped) |
188
209
  | `-q, --quiet` | — | Suppress non-essential output |
189
210
  | `--verbose` | — | Enable verbose output |
190
211
  | `--no-color` | `NO_COLOR` | Disable color output |
@@ -321,11 +342,10 @@ Use `--no-interactive` to disable REPL/prompts in automation.
321
342
 
322
343
  ## Output Modes
323
344
 
324
- - TTY: formatted human output
325
- - Non-TTY: JSON output (script-friendly)
326
- - `-v`, `--version`: prints the CLI version
327
- - `--json`: forces JSON output in any context
328
- - `--verbose` or `alchemy config set verbose true`: includes richer payload output on supported commands
345
+ - **TTY (terminal):** formatted human output (tables, colors, spinners)
346
+ - **Non-TTY (piped/redirected):** JSON output automatically — no flag needed
347
+ - `--json`: forces JSON output even in a terminal
348
+ - `--verbose` or `alchemy config set verbose true`: logs request/response details (method, URL, status, timing) to stderr
329
349
 
330
350
  ## Error Format
331
351
 
@@ -4,10 +4,13 @@ if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
4
4
  // src/lib/colors.ts
5
5
  var forceColor = "FORCE_COLOR" in process.env && process.env.FORCE_COLOR !== "0";
6
6
  var noColor = !forceColor && ("NO_COLOR" in process.env || process.env.TERM === "dumb");
7
+ function setNoColor(value) {
8
+ noColor = value;
9
+ }
7
10
  var identity = (s) => s;
8
- var esc = (code) => noColor ? identity : (s) => `\x1B[${code}m${s}\x1B[0m`;
9
- var rgb = (r, g, b) => noColor ? identity : (s) => `\x1B[38;2;${r};${g};${b}m${s}\x1B[39m`;
10
- var bgRgb = (r, g, b) => noColor ? identity : (s) => `\x1B[48;2;${r};${g};${b}m${s}\x1B[49m`;
11
+ var esc = (code) => (s) => noColor ? s : `\x1B[${code}m${s}\x1B[0m`;
12
+ var rgb = (r, g, b) => (s) => noColor ? s : `\x1B[38;2;${r};${g};${b}m${s}\x1B[39m`;
13
+ var bgRgb = (r, g, b) => (s) => noColor ? s : `\x1B[48;2;${r};${g};${b}m${s}\x1B[49m`;
11
14
 
12
15
  // src/lib/redact.ts
13
16
  var SENSITIVE_ERROR_CODES = /* @__PURE__ */ new Set([
@@ -475,7 +478,8 @@ function getHome() {
475
478
  }
476
479
  function configPath() {
477
480
  if (process.env.ALCHEMY_CONFIG) return process.env.ALCHEMY_CONFIG;
478
- return join(getHome(), ".config", "alchemy", "config.json");
481
+ const configHome = process.env.XDG_CONFIG_HOME || join(getHome(), ".config");
482
+ return join(configHome, "alchemy", "config.json");
479
483
  }
480
484
  function configDir() {
481
485
  return dirname(configPath());
@@ -647,8 +651,9 @@ async function runListPrompt(opts) {
647
651
  stdin.setRawMode(previousRawMode);
648
652
  stdin.removeListener("keypress", onKeypress);
649
653
  restoreKeypressListeners();
650
- stdin.pause();
651
- stdin.unref?.();
654
+ if (!previousRawMode) {
655
+ stdin.pause();
656
+ }
652
657
  };
653
658
  const commitSingleLine = (text) => {
654
659
  if (opts.commitLabel === null) return;
@@ -1109,7 +1114,7 @@ function semverLT(a, b) {
1109
1114
  return false;
1110
1115
  }
1111
1116
  function currentVersion() {
1112
- return true ? "0.2.2" : "0.0.0";
1117
+ return true ? "0.3.1" : "0.0.0";
1113
1118
  }
1114
1119
  function toUpdateStatus(latestVersion, checkedAt) {
1115
1120
  const current = currentVersion();
@@ -1168,10 +1173,12 @@ ${getUpdateNoticeLines(latest).join("\n")}
1168
1173
 
1169
1174
  export {
1170
1175
  noColor,
1176
+ setNoColor,
1171
1177
  identity,
1172
1178
  esc,
1173
1179
  rgb,
1174
1180
  bgRgb,
1181
+ redactSensitiveText,
1175
1182
  quiet,
1176
1183
  verbose,
1177
1184
  timeout,
@@ -1214,6 +1221,7 @@ export {
1214
1221
  promptAutocomplete,
1215
1222
  promptMultiselect,
1216
1223
  green,
1224
+ red,
1217
1225
  dim,
1218
1226
  bold,
1219
1227
  yellow,
@@ -36,12 +36,14 @@ import {
36
36
  promptMultiselect,
37
37
  promptSelect,
38
38
  promptText,
39
+ redactSensitiveText,
39
40
  save,
40
41
  timeout,
41
42
  toMap,
43
+ verbose,
42
44
  withSpinner,
43
45
  yellow
44
- } from "./chunk-2WI4JODY.js";
46
+ } from "./chunk-6XTLILDF.js";
45
47
 
46
48
  // src/lib/client-utils.ts
47
49
  function isLocalhost(hostname) {
@@ -73,10 +75,15 @@ function parseBaseURLOverride(envVarName) {
73
75
  }
74
76
  return parsed;
75
77
  }
78
+ var BREADCRUMB_HEADER = "alchemy-cli";
76
79
  async function fetchWithTimeout(url, init) {
77
80
  try {
78
81
  return await fetch(url, {
79
82
  ...init,
83
+ headers: {
84
+ ...init.headers,
85
+ "x-alchemy-client-breadcrumb": BREADCRUMB_HEADER
86
+ },
80
87
  ...timeout && { signal: AbortSignal.timeout(timeout) }
81
88
  });
82
89
  } catch (err) {
@@ -269,6 +276,92 @@ var AdminClient = class _AdminClient {
269
276
  }
270
277
  };
271
278
 
279
+ // src/lib/ens.ts
280
+ import { keccak_256 } from "@noble/hashes/sha3.js";
281
+ var UNIVERSAL_RESOLVER = "0xeEeEEEeE14D718C2B47D9923Deab1335E144EeEe";
282
+ var RESOLVE_SELECTOR = "9061b923";
283
+ var ADDR_SELECTOR = "3b3b57de";
284
+ function bytesToHex(bytes) {
285
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
286
+ }
287
+ function pad32(hex) {
288
+ return hex.padStart(64, "0");
289
+ }
290
+ function namehash(name) {
291
+ let node = new Uint8Array(32);
292
+ if (!name) return node;
293
+ const labels = name.split(".");
294
+ for (let i = labels.length - 1; i >= 0; i--) {
295
+ const labelHash = keccak_256(new TextEncoder().encode(labels[i]));
296
+ const combined = new Uint8Array(64);
297
+ combined.set(node, 0);
298
+ combined.set(labelHash, 32);
299
+ node = keccak_256(combined);
300
+ }
301
+ return node;
302
+ }
303
+ function dnsEncode(name) {
304
+ const labels = name.split(".");
305
+ const parts = [];
306
+ for (const label of labels) {
307
+ const encoded = new TextEncoder().encode(label);
308
+ parts.push(encoded.length);
309
+ parts.push(...encoded);
310
+ }
311
+ parts.push(0);
312
+ return new Uint8Array(parts);
313
+ }
314
+ function buildResolveCalldata(name) {
315
+ const dnsName = dnsEncode(name);
316
+ const node = namehash(name);
317
+ const innerHex = ADDR_SELECTOR + bytesToHex(node);
318
+ const innerLen = 36;
319
+ const dnsHex = bytesToHex(dnsName);
320
+ const nameLen = dnsName.length;
321
+ const namePad = Math.ceil(nameLen / 32) * 32;
322
+ const innerPad = Math.ceil(innerLen / 32) * 32;
323
+ const nameOffset = 64;
324
+ const dataOffset = nameOffset + 32 + namePad;
325
+ let hex = RESOLVE_SELECTOR;
326
+ hex += pad32(nameOffset.toString(16));
327
+ hex += pad32(dataOffset.toString(16));
328
+ hex += pad32(nameLen.toString(16));
329
+ hex += dnsHex.padEnd(namePad * 2, "0");
330
+ hex += pad32(innerLen.toString(16));
331
+ hex += innerHex.padEnd(innerPad * 2, "0");
332
+ return "0x" + hex;
333
+ }
334
+ function isENSName(value) {
335
+ return value.endsWith(".eth") && value.length > 4 && !value.startsWith("0x");
336
+ }
337
+ async function resolveENS(name, client) {
338
+ if (!client.network.startsWith("eth-")) {
339
+ throw errInvalidArgs(
340
+ `ENS resolution is only supported on Ethereum networks. Current network: ${client.network}`
341
+ );
342
+ }
343
+ const calldata = buildResolveCalldata(name.toLowerCase());
344
+ const result = await client.call("eth_call", [
345
+ { to: UNIVERSAL_RESOLVER, data: calldata },
346
+ "latest"
347
+ ]);
348
+ if (!result || result === "0x" || result.length < 130) {
349
+ throw errInvalidArgs(`ENS name "${name}" could not be resolved.`);
350
+ }
351
+ const raw = result.slice(2);
352
+ const dataOffset = parseInt(raw.slice(0, 64), 16) * 2;
353
+ const dataLen = parseInt(raw.slice(dataOffset, dataOffset + 64), 16);
354
+ const dataHex = raw.slice(dataOffset + 64, dataOffset + 64 + dataLen * 2);
355
+ if (dataHex.length < 64) {
356
+ throw errInvalidArgs(`ENS name "${name}" could not be resolved.`);
357
+ }
358
+ const address = "0x" + dataHex.slice(24, 64);
359
+ if (address === "0x0000000000000000000000000000000000000000") {
360
+ throw errInvalidArgs(`ENS name "${name}" is not registered or has no address set.`);
361
+ }
362
+ return address;
363
+ }
364
+
272
365
  // src/lib/validators.ts
273
366
  function splitCommaList(input) {
274
367
  return input.split(",").map((s) => s.trim()).filter(Boolean);
@@ -297,6 +390,13 @@ function validateAddress(address) {
297
390
  );
298
391
  }
299
392
  }
393
+ async function resolveAddress(input, client) {
394
+ if (isENSName(input)) {
395
+ return resolveENS(input, client);
396
+ }
397
+ validateAddress(input);
398
+ return input;
399
+ }
300
400
  function validateTxHash(hash) {
301
401
  if (!TX_HASH_RE.test(hash)) {
302
402
  throw errInvalidArgs(
@@ -551,13 +651,13 @@ function registerConfig(program) {
551
651
  if (normalized !== "true" && normalized !== "false") {
552
652
  throw errInvalidArgs("verbose must be 'true' or 'false'");
553
653
  }
554
- const verbose = normalized === "true";
654
+ const verbose2 = normalized === "true";
555
655
  const cfg = load();
556
- save({ ...cfg, verbose });
656
+ save({ ...cfg, verbose: verbose2 });
557
657
  printHuman(
558
- `${green("\u2713")} Set verbose default to ${verbose}
658
+ `${green("\u2713")} Set verbose default to ${verbose2}
559
659
  `,
560
- { key: "verbose", value: String(verbose), status: "set" }
660
+ { key: "verbose", value: String(verbose2), status: "set" }
561
661
  );
562
662
  } catch (err) {
563
663
  exitWithError(err);
@@ -593,13 +693,28 @@ function registerConfig(program) {
593
693
  });
594
694
  cmd.command("get <key>").description("Get a config value (api-key, access-key, app, network, verbose, wallet-key-file, x402)").action((key) => {
595
695
  const cfg = load();
596
- const value = get(cfg, key);
696
+ let value = get(cfg, key);
697
+ let isDefault = false;
698
+ if (value === void 0) {
699
+ const defaults = {
700
+ network: "eth-mainnet",
701
+ verbose: "false",
702
+ x402: "false"
703
+ };
704
+ const normalizedKey = KEY_MAP[key] ?? key;
705
+ const defaultValue = defaults[normalizedKey] ?? defaults[key];
706
+ if (defaultValue !== void 0) {
707
+ value = defaultValue;
708
+ isDefault = true;
709
+ }
710
+ }
597
711
  if (value === void 0) {
598
712
  exitWithError(errNotFound(`config key '${key}'`));
599
713
  }
600
714
  const isSecret = key === "api-key" || key === "api_key" || key === "access-key" || key === "access_key";
601
715
  const display = isSecret ? maskIf(value) : value;
602
- printHuman(display + "\n", { key, value: display });
716
+ const humanDisplay = isDefault ? `${display} ${dim("(default)")}` : display;
717
+ printHuman(humanDisplay + "\n", { key, value: display, ...isDefault && { default: true } });
603
718
  });
604
719
  cmd.command("list").description("List all config values").action(() => {
605
720
  const cfg = load();
@@ -761,6 +876,12 @@ var Client = class _Client {
761
876
  }
762
877
  return null;
763
878
  }
879
+ verboseLog(message) {
880
+ if (verbose) {
881
+ process.stderr.write(`[verbose] ${message}
882
+ `);
883
+ }
884
+ }
764
885
  async doFetch(url, init) {
765
886
  return fetchWithTimeout(url, init);
766
887
  }
@@ -771,6 +892,14 @@ var Client = class _Client {
771
892
  params,
772
893
  id: 1
773
894
  };
895
+ const redactedURL = redactSensitiveText(this.rpcURL());
896
+ this.verboseLog(`\u2192 POST ${redactedURL}`);
897
+ this.verboseLog(` method: ${method}`);
898
+ const hasParams = Array.isArray(params) ? params.length > 0 : Object.keys(params).length > 0;
899
+ if (hasParams) {
900
+ this.verboseLog(` params: ${JSON.stringify(params)}`);
901
+ }
902
+ const startTime = Date.now();
774
903
  const resp = await this.doFetch(this.rpcURL(), {
775
904
  method: "POST",
776
905
  headers: {
@@ -779,6 +908,7 @@ var Client = class _Client {
779
908
  },
780
909
  body: JSON.stringify(body)
781
910
  });
911
+ this.verboseLog(`\u2190 ${resp.status} ${resp.statusText} (${Date.now() - startTime}ms)`);
782
912
  if (resp.status === 429) throw errRateLimited();
783
913
  if (resp.status === 401 || resp.status === 403) {
784
914
  const detail = await resp.text().catch(() => "");
@@ -801,9 +931,13 @@ var Client = class _Client {
801
931
  for (const [k, v] of Object.entries(params)) {
802
932
  url.searchParams.set(k, v);
803
933
  }
934
+ const redactedURL = redactSensitiveText(url.toString());
935
+ this.verboseLog(`\u2192 GET ${redactedURL}`);
936
+ const startTime = Date.now();
804
937
  const resp = await this.doFetch(url.toString(), {
805
938
  headers: { Accept: "application/json" }
806
939
  });
940
+ this.verboseLog(`\u2190 ${resp.status} ${resp.statusText} (${Date.now() - startTime}ms)`);
807
941
  if (resp.status === 429) throw errRateLimited();
808
942
  if (resp.status === 401 || resp.status === 403) {
809
943
  const detail = await resp.text().catch(() => "");
@@ -1008,13 +1142,13 @@ function resolveAccessKey(program, cfg) {
1008
1142
  if (config.access_key) return config.access_key;
1009
1143
  return void 0;
1010
1144
  }
1011
- function resolveNetwork(program, cfg) {
1145
+ function resolveNetwork(program, cfg, defaultNetwork) {
1012
1146
  const opts = program.opts();
1013
1147
  if (opts.network) return opts.network;
1014
1148
  if (process.env.ALCHEMY_NETWORK) return process.env.ALCHEMY_NETWORK;
1015
1149
  const config = cfg ?? load();
1016
1150
  if (config.network) return config.network;
1017
- return "eth-mainnet";
1151
+ return defaultNetwork ?? "eth-mainnet";
1018
1152
  }
1019
1153
  function resolveAppId(program, cfg) {
1020
1154
  const opts = program.opts();
@@ -1048,10 +1182,16 @@ function resolveWalletKey(program, cfg) {
1048
1182
  }
1049
1183
  return void 0;
1050
1184
  }
1051
- function clientFromFlags(program) {
1185
+ function clientFromFlags(program, opts) {
1052
1186
  const cfg = load();
1053
- const network = resolveNetwork(program, cfg);
1187
+ const network = resolveNetwork(program, cfg, opts?.defaultNetwork);
1054
1188
  debug(`using network=${network}`);
1189
+ const programOpts = program.opts();
1190
+ if (programOpts.accessKey) {
1191
+ throw errInvalidArgs(
1192
+ "--access-key is for admin commands (apps, chains, webhooks). Use --api-key for RPC commands."
1193
+ );
1194
+ }
1055
1195
  if (resolveX402(program, cfg)) {
1056
1196
  const walletKey = resolveWalletKey(program, cfg);
1057
1197
  if (!walletKey) throw errWalletKeyRequired();
@@ -1139,7 +1279,7 @@ function registerWallet(program) {
1139
1279
  exitWithError(err);
1140
1280
  }
1141
1281
  });
1142
- cmd.command("import <path>").description("Import a wallet from a private key file").action((path) => {
1282
+ cmd.command("import").argument("<path>", "Path to private key file").description("Import a wallet from a private key file").action((path) => {
1143
1283
  try {
1144
1284
  const wallet = importAndPersistWallet(path);
1145
1285
  if (isJSONMode()) {
@@ -1177,6 +1317,7 @@ export {
1177
1317
  splitCommaList,
1178
1318
  readStdinArg,
1179
1319
  validateAddress,
1320
+ resolveAddress,
1180
1321
  validateTxHash,
1181
1322
  selectOrCreateApp,
1182
1323
  registerConfig,
@@ -2,7 +2,7 @@
2
2
  if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
4
  isInteractiveAllowed
5
- } from "./chunk-2WI4JODY.js";
5
+ } from "./chunk-6XTLILDF.js";
6
6
 
7
7
  // src/lib/networks.ts
8
8
  var TESTNET_TOKEN_RE = /(testnet|sepolia|holesky|hoodi|devnet|minato|amoy|fuji|saigon|cardona|aeneid|curtis|chiado|cassiopeia|blaze|ropsten|signet|mocha|fam|bepolia)$/i;
@@ -227,6 +227,42 @@ function getRPCNetworks() {
227
227
  function getRPCNetworkIds() {
228
228
  return [...RPC_NETWORK_IDS];
229
229
  }
230
+ var NATIVE_TOKEN_SYMBOLS = {
231
+ eth: "ETH",
232
+ arb: "ETH",
233
+ arbnova: "ETH",
234
+ opt: "ETH",
235
+ base: "ETH",
236
+ zksync: "ETH",
237
+ scroll: "ETH",
238
+ blast: "ETH",
239
+ linea: "ETH",
240
+ zora: "ETH",
241
+ shape: "ETH",
242
+ polygon: "POL",
243
+ polygonzkevm: "ETH",
244
+ bnb: "BNB",
245
+ opbnb: "BNB",
246
+ avax: "AVAX",
247
+ solana: "SOL",
248
+ starknet: "ETH",
249
+ fantom: "FTM",
250
+ metis: "METIS",
251
+ mantle: "MNT",
252
+ celo: "CELO",
253
+ gnosis: "xDAI",
254
+ frax: "frxETH",
255
+ worldchain: "ETH",
256
+ berachain: "BERA",
257
+ flow: "FLOW",
258
+ rootstock: "RBTC",
259
+ zetachain: "ZETA",
260
+ sui: "SUI"
261
+ };
262
+ function nativeTokenSymbol(networkId) {
263
+ const prefix = networkId.replace(/-(mainnet|testnet|sepolia|holesky|hoodi|devnet|amoy|fuji|cardona|saigon|chiado|signet|mocha|blaze|curtis|bepolia).*$/, "");
264
+ return NATIVE_TOKEN_SYMBOLS[prefix] ?? "ETH";
265
+ }
230
266
 
231
267
  // src/lib/onboarding.ts
232
268
  function hasAPIKey(cfg) {
@@ -275,6 +311,7 @@ function shouldRunOnboarding(program, cfg) {
275
311
  export {
276
312
  getRPCNetworks,
277
313
  getRPCNetworkIds,
314
+ nativeTokenSymbol,
278
315
  getSetupMethod,
279
316
  isSetupComplete,
280
317
  getSetupStatus,