nansen-cli 1.16.1 → 1.18.0
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/CHANGELOG.md +53 -0
- package/package.json +2 -1
- package/skills/nansen-alerts/SKILL.md +137 -0
- package/skills/nansen-alpha-discovery/SKILL.md +43 -0
- package/skills/nansen-batch-wallet/SKILL.md +26 -0
- package/skills/nansen-cross-chain-flow/SKILL.md +27 -0
- package/skills/nansen-dca-watch/SKILL.md +38 -0
- package/skills/nansen-defi-exposure/SKILL.md +37 -0
- package/skills/nansen-exit-signal/SKILL.md +39 -0
- package/skills/nansen-fund-watch/SKILL.md +35 -0
- package/skills/nansen-holder-quality/SKILL.md +38 -0
- package/skills/nansen-perp-scan/SKILL.md +32 -0
- package/skills/nansen-perp-trader/SKILL.md +39 -0
- package/skills/nansen-pm-deep-dive/SKILL.md +50 -0
- package/skills/nansen-pm-insider-scan/SKILL.md +62 -0
- package/skills/nansen-polymarket-trader/SKILL.md +43 -0
- package/skills/nansen-portfolio-history/SKILL.md +36 -0
- package/skills/nansen-prediction-market/SKILL.md +47 -0
- package/skills/nansen-profiler/SKILL.md +98 -0
- package/skills/nansen-search/SKILL.md +34 -0
- package/skills/nansen-sm-trend/SKILL.md +30 -0
- package/skills/nansen-smart-money/SKILL.md +71 -0
- package/skills/nansen-token/SKILL.md +90 -0
- package/skills/nansen-token-discovery/SKILL.md +54 -0
- package/skills/nansen-token-forensics/SKILL.md +40 -0
- package/skills/nansen-trade/SKILL.md +100 -0
- package/skills/nansen-wallet/SKILL.md +140 -0
- package/skills/nansen-wallet-analysis/SKILL.md +45 -0
- package/skills/nansen-wallet-attribution/REFERENCE.md +43 -0
- package/skills/nansen-wallet-attribution/SKILL.md +46 -0
- package/skills/nansen-wallet-migration/SKILL.md +183 -0
- package/skills/nansen-web-fetch/SKILL.md +50 -0
- package/skills/nansen-web-search/SKILL.md +39 -0
- package/src/api.js +144 -73
- package/src/cli.js +181 -14
- package/src/rpc-urls.js +29 -0
- package/src/schema.json +401 -1448
- package/src/telemetry.js +237 -0
- package/src/trading.js +26 -12
- package/src/transfer.js +1 -11
- package/src/update-check.js +2 -2
- package/src/wallet.js +2 -1
- package/src/x402.js +3 -2
package/src/cli.js
CHANGED
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
import { NansenAPI, NansenError, ErrorCode, saveConfig, deleteConfig, getConfigFile, clearCache, getCacheDir, validateAddress, sleep } from './api.js';
|
|
7
7
|
import { buildWalletCommands } from './wallet.js';
|
|
8
8
|
import { buildTradingCommands } from './trading.js';
|
|
9
|
+
import { formatAlertsTable, buildAlertsCommands } from './commands/alerts.js';
|
|
9
10
|
import { resolveAddress, isEnsName } from './ens.js';
|
|
10
11
|
import fs from 'fs';
|
|
11
12
|
import { getUpdateNotification, getUpgradeNotice, scheduleUpdateCheck } from './update-check.js';
|
|
13
|
+
import { trackCommandSucceeded, trackCommandFailed } from './telemetry.js';
|
|
12
14
|
import { createRequire } from 'module';
|
|
13
15
|
import * as readline from 'readline';
|
|
14
16
|
|
|
@@ -163,19 +165,29 @@ export function parseArgs(args) {
|
|
|
163
165
|
const key = arg.slice(2);
|
|
164
166
|
const next = args[i + 1];
|
|
165
167
|
|
|
166
|
-
if (key === 'pretty' || key === 'help' || key === 'version' || key === 'table' || key === 'no-retry' || key === 'cache' || key === 'no-cache' || key === 'stream' || key === 'enrich' || key === 'full' || key === 'human') {
|
|
168
|
+
if (key === 'pretty' || key === 'help' || key === 'version' || key === 'table' || key === 'no-retry' || key === 'cache' || key === 'no-cache' || key === 'stream' || key === 'enrich' || key === 'full' || key === 'human' || key === 'enabled' || key === 'disabled') {
|
|
167
169
|
result.flags[key] = true;
|
|
168
|
-
} else if (next && !next.startsWith('-')) {
|
|
170
|
+
} else if (next && (!next.startsWith('-') || /^-\d/.test(next))) {
|
|
169
171
|
// Try to parse as JSON first (for objects/arrays/booleans),
|
|
170
172
|
// but keep numeric strings as strings to avoid precision loss
|
|
171
173
|
// and scientific notation for large integers (e.g. 1e+21).
|
|
174
|
+
let parsedValue;
|
|
172
175
|
try {
|
|
173
176
|
const parsed = JSON.parse(next);
|
|
174
|
-
|
|
177
|
+
parsedValue = typeof parsed === 'number' ? next : parsed;
|
|
175
178
|
} catch {
|
|
176
|
-
|
|
179
|
+
parsedValue = next;
|
|
177
180
|
}
|
|
178
181
|
i++;
|
|
182
|
+
// Accumulate repeated options into arrays (supports repeatable flags like --token, --subject)
|
|
183
|
+
if (key in result.options) {
|
|
184
|
+
if (!Array.isArray(result.options[key])) {
|
|
185
|
+
result.options[key] = [result.options[key]];
|
|
186
|
+
}
|
|
187
|
+
result.options[key].push(parsedValue);
|
|
188
|
+
} else {
|
|
189
|
+
result.options[key] = parsedValue;
|
|
190
|
+
}
|
|
179
191
|
} else {
|
|
180
192
|
result.flags[key] = true;
|
|
181
193
|
}
|
|
@@ -226,7 +238,7 @@ export function formatTable(data) {
|
|
|
226
238
|
// Get columns from first record, prioritize common useful fields
|
|
227
239
|
const priorityFields = ['token_symbol', 'token_name', 'symbol', 'name', 'address', 'label', 'chain', 'value_usd', 'amount', 'pnl_usd', 'price_usd', 'volume_usd', 'net_flow_usd', 'timestamp', 'block_timestamp'];
|
|
228
240
|
const allKeys = [...new Set(records.flatMap(r => Object.keys(r)))];
|
|
229
|
-
|
|
241
|
+
|
|
230
242
|
// Sort: priority fields first, then alphabetically
|
|
231
243
|
const columns = allKeys.sort((a, b) => {
|
|
232
244
|
const aIdx = priorityFields.indexOf(a);
|
|
@@ -250,12 +262,12 @@ export function formatTable(data) {
|
|
|
250
262
|
// Build table
|
|
251
263
|
const separator = '─';
|
|
252
264
|
const lines = [];
|
|
253
|
-
|
|
265
|
+
|
|
254
266
|
// Header
|
|
255
267
|
const header = columns.map((col, i) => col.padEnd(widths[i])).join(' │ ');
|
|
256
268
|
lines.push(header);
|
|
257
269
|
lines.push(widths.map(w => separator.repeat(w)).join('─┼─'));
|
|
258
|
-
|
|
270
|
+
|
|
259
271
|
// Rows
|
|
260
272
|
for (const record of records.slice(0, 50)) { // Limit to 50 rows
|
|
261
273
|
const row = columns.map((col, i) => {
|
|
@@ -670,6 +682,9 @@ COMMANDS:
|
|
|
670
682
|
research smart-money, profiler, token, search, perp, portfolio, points
|
|
671
683
|
trade quote, execute
|
|
672
684
|
wallet create, list, show, export, default, delete, forget-password
|
|
685
|
+
alerts list, create, update, toggle, delete
|
|
686
|
+
web search, fetch
|
|
687
|
+
account Show API key status, plan, and remaining credits
|
|
673
688
|
login Save API key (--api-key <key> or NANSEN_API_KEY env var)
|
|
674
689
|
logout Remove saved API key
|
|
675
690
|
schema JSON schema for all commands (use "nansen schema <cmd>" for one)
|
|
@@ -692,6 +707,8 @@ Labels: Fund, Smart Trader, 30D/90D/180D Smart Trader, Smart HL Perps Trader
|
|
|
692
707
|
|
|
693
708
|
Docs: https://docs.nansen.ai
|
|
694
709
|
Skills: npx skills add nansen-ai/nansen-cli (agent-optimised docs per command group)
|
|
710
|
+
|
|
711
|
+
Telemetry: anonymous usage stats collected. Disable: DO_NOT_TRACK=1
|
|
695
712
|
`;
|
|
696
713
|
|
|
697
714
|
// Helper to prompt for input (exported for mocking)
|
|
@@ -757,6 +774,80 @@ export function buildCommands(deps = {}) {
|
|
|
757
774
|
} = deps;
|
|
758
775
|
|
|
759
776
|
const cmds = {
|
|
777
|
+
'account': async (_args, apiInstance, _flags, _options) => {
|
|
778
|
+
return apiInstance.getAccount();
|
|
779
|
+
},
|
|
780
|
+
|
|
781
|
+
'web': async (args, apiInstance, flags, options) => {
|
|
782
|
+
const subcommand = args[0] || 'help';
|
|
783
|
+
const subArgs = args.slice(1);
|
|
784
|
+
|
|
785
|
+
const handlers = {
|
|
786
|
+
'search': async () => {
|
|
787
|
+
// Accept queries as positional args or --query (repeated)
|
|
788
|
+
let queries = subArgs.length > 0 ? subArgs : [];
|
|
789
|
+
if (options.query) {
|
|
790
|
+
const fromOption = Array.isArray(options.query) ? options.query : [options.query];
|
|
791
|
+
queries = queries.concat(fromOption);
|
|
792
|
+
}
|
|
793
|
+
queries = queries.filter(q => q.trim());
|
|
794
|
+
if (queries.length === 0) {
|
|
795
|
+
throw new NansenError('At least one query is required. Usage: nansen web search "bitcoin price" --num-results 5', ErrorCode.MISSING_PARAM);
|
|
796
|
+
}
|
|
797
|
+
let numResults;
|
|
798
|
+
if (options['num-results'] !== undefined) {
|
|
799
|
+
const numResultsRaw = parseInt(options['num-results'], 10);
|
|
800
|
+
if (Number.isNaN(numResultsRaw)) {
|
|
801
|
+
// Non-numeric — fall back to API default
|
|
802
|
+
numResults = undefined;
|
|
803
|
+
} else if (numResultsRaw >= 1 && numResultsRaw <= 20) {
|
|
804
|
+
numResults = numResultsRaw;
|
|
805
|
+
} else {
|
|
806
|
+
throw new NansenError('--num-results must be between 1 and 20', ErrorCode.INVALID_PARAMS);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return apiInstance.webSearch({ queries, numResults });
|
|
810
|
+
},
|
|
811
|
+
|
|
812
|
+
'fetch': async () => {
|
|
813
|
+
// Accept URLs as positional args or --url (repeated)
|
|
814
|
+
let urls = subArgs.length > 0 ? subArgs : [];
|
|
815
|
+
if (options.url) {
|
|
816
|
+
const fromOption = Array.isArray(options.url) ? options.url : [options.url];
|
|
817
|
+
urls = urls.concat(fromOption);
|
|
818
|
+
}
|
|
819
|
+
if (urls.length === 0) {
|
|
820
|
+
throw new NansenError('At least one URL is required. Usage: nansen web fetch https://example.com --question "What is this about?"', ErrorCode.MISSING_PARAM);
|
|
821
|
+
}
|
|
822
|
+
for (const u of urls) {
|
|
823
|
+
try { new URL(u); } catch {
|
|
824
|
+
throw new NansenError(`Invalid URL: "${u}". URLs must include a scheme, e.g. https://example.com`, ErrorCode.INVALID_PARAMS);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (!options.question || !options.question.trim()) {
|
|
828
|
+
throw new NansenError('--question is required and cannot be blank. Usage: nansen web fetch https://example.com --question "What is this about?"', ErrorCode.MISSING_PARAM);
|
|
829
|
+
}
|
|
830
|
+
return apiInstance.webFetch({ urls, question: options.question });
|
|
831
|
+
},
|
|
832
|
+
|
|
833
|
+
'help': async () => ({
|
|
834
|
+
subcommands: ['search', 'fetch'],
|
|
835
|
+
description: 'Web search and fetch commands',
|
|
836
|
+
examples: [
|
|
837
|
+
'nansen web search "bitcoin price"',
|
|
838
|
+
'nansen web search "solana news" --num-results 5',
|
|
839
|
+
'nansen web fetch https://nansen.ai --question "What does Nansen do?"',
|
|
840
|
+
],
|
|
841
|
+
}),
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
if (!handlers[subcommand]) {
|
|
845
|
+
throw new NansenError(`Unknown web subcommand: ${subcommand}. Available: search, fetch`, ErrorCode.UNKNOWN);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
return handlers[subcommand]();
|
|
849
|
+
},
|
|
850
|
+
|
|
760
851
|
'login': async (args, apiInstance, flags, options) => {
|
|
761
852
|
if (flags.help || flags.h) {
|
|
762
853
|
log('nansen login - Save your Nansen API key\n');
|
|
@@ -806,13 +897,48 @@ export function buildCommands(deps = {}) {
|
|
|
806
897
|
return;
|
|
807
898
|
}
|
|
808
899
|
|
|
900
|
+
// Verify API key before saving
|
|
901
|
+
const NansenAPIClass = _NansenAPIClass;
|
|
902
|
+
const testApi = new NansenAPIClass(apiKey.trim(), undefined, {
|
|
903
|
+
retry: { maxRetries: 2 },
|
|
904
|
+
cache: { enabled: false }
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
let accountInfo;
|
|
908
|
+
try {
|
|
909
|
+
accountInfo = await testApi.getAccount();
|
|
910
|
+
} catch (error) {
|
|
911
|
+
if (error.code === ErrorCode.UNAUTHORIZED) {
|
|
912
|
+
log(JSON.stringify({
|
|
913
|
+
error: 'INVALID_API_KEY',
|
|
914
|
+
message: 'The API key is not valid.',
|
|
915
|
+
resolution: ['Check your key at https://app.nansen.ai/api']
|
|
916
|
+
}));
|
|
917
|
+
} else {
|
|
918
|
+
log(JSON.stringify({
|
|
919
|
+
error: 'VERIFICATION_FAILED',
|
|
920
|
+
message: `Could not verify API key: ${error.message}`,
|
|
921
|
+
resolution: ['Check your internet connection', 'Try again']
|
|
922
|
+
}));
|
|
923
|
+
}
|
|
924
|
+
exit(1);
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// Key is valid - now save
|
|
809
929
|
saveConfigFn({
|
|
810
930
|
apiKey: apiKey.trim(),
|
|
811
931
|
baseUrl: 'https://api.nansen.ai'
|
|
812
932
|
});
|
|
813
933
|
|
|
814
934
|
log(`✓ Saved to ${getConfigFileFn()}\n`);
|
|
815
|
-
|
|
935
|
+
if (accountInfo?.plan) {
|
|
936
|
+
log(`Plan: ${accountInfo.plan}`);
|
|
937
|
+
}
|
|
938
|
+
if (accountInfo?.credits_remaining !== undefined) {
|
|
939
|
+
log(`Credits remaining: ${accountInfo.credits_remaining}`);
|
|
940
|
+
}
|
|
941
|
+
log('\nYou can now use the Nansen CLI. Try:');
|
|
816
942
|
log(' nansen research token screener --chain solana --pretty');
|
|
817
943
|
},
|
|
818
944
|
|
|
@@ -1443,7 +1569,7 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1443
1569
|
if (updateNotification) errorOutput(updateNotification);
|
|
1444
1570
|
};
|
|
1445
1571
|
|
|
1446
|
-
const commands = { ...buildCommands(deps), ...buildWalletCommands(deps), ...buildTradingCommands(deps), ...commandOverrides };
|
|
1572
|
+
const commands = { ...buildCommands(deps), ...buildWalletCommands(deps), ...buildTradingCommands(deps), ...buildAlertsCommands(deps), ...commandOverrides };
|
|
1447
1573
|
|
|
1448
1574
|
if (flags.version || flags.v) {
|
|
1449
1575
|
output(VERSION);
|
|
@@ -1480,8 +1606,8 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1480
1606
|
}
|
|
1481
1607
|
}
|
|
1482
1608
|
// First try subcommand help
|
|
1483
|
-
// Skip for 'trade' —
|
|
1484
|
-
if (command && subcommand && command !== 'trade') {
|
|
1609
|
+
// Skip for 'trade'/'alerts' — their handlers show their own rich usage
|
|
1610
|
+
if (command && subcommand && command !== 'trade' && command !== 'alerts') {
|
|
1485
1611
|
const subHelp = generateSubcommandHelp(command, subcommand);
|
|
1486
1612
|
if (subHelp) {
|
|
1487
1613
|
output(subHelp);
|
|
@@ -1490,8 +1616,8 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1490
1616
|
}
|
|
1491
1617
|
}
|
|
1492
1618
|
// Then try command-level help (list subcommands)
|
|
1493
|
-
// Skip for 'trade' — let the handler show its own usage
|
|
1494
|
-
const cmdSchemaLookup = command !== 'trade' && (SCHEMA.commands[command] || SCHEMA.commands.research.subcommands[command]);
|
|
1619
|
+
// Skip for 'trade'/'alerts' — let the handler show its own usage
|
|
1620
|
+
const cmdSchemaLookup = command !== 'trade' && command !== 'alerts' && (SCHEMA.commands[command] || SCHEMA.commands.research.subcommands[command]);
|
|
1495
1621
|
if (command && cmdSchemaLookup) {
|
|
1496
1622
|
const cmdSchema = cmdSchemaLookup;
|
|
1497
1623
|
const lines = [`${command} — ${cmdSchema.description}`];
|
|
@@ -1499,6 +1625,19 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1499
1625
|
lines.push('Subcommands: ' + Object.keys(cmdSchema.subcommands).join(', '));
|
|
1500
1626
|
lines.push(`Use: nansen ${command} <subcommand> --help`);
|
|
1501
1627
|
}
|
|
1628
|
+
if (cmdSchema.options) {
|
|
1629
|
+
const params = Object.entries(cmdSchema.options).map(([name, opt]) => {
|
|
1630
|
+
const parts = [`--${name}`];
|
|
1631
|
+
if (opt.required) parts[0] += '*';
|
|
1632
|
+
if (opt.default !== undefined) parts.push(`(default: ${opt.default})`);
|
|
1633
|
+
if (opt.description) parts.push(`— ${opt.description}`);
|
|
1634
|
+
return parts.join(' ');
|
|
1635
|
+
});
|
|
1636
|
+
lines.push(`\nOptions (* required):\n ${params.join('\n ')}`);
|
|
1637
|
+
}
|
|
1638
|
+
if (cmdSchema.examples?.length) {
|
|
1639
|
+
lines.push(`\nExamples:\n ${cmdSchema.examples.join('\n ')}`);
|
|
1640
|
+
}
|
|
1502
1641
|
output(lines.join('\n'));
|
|
1503
1642
|
notify();
|
|
1504
1643
|
return { type: 'command-help', command };
|
|
@@ -1526,13 +1665,22 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1526
1665
|
}
|
|
1527
1666
|
}
|
|
1528
1667
|
|
|
1668
|
+
// ── Telemetry setup ──
|
|
1669
|
+
const startTime = Date.now();
|
|
1670
|
+
const fullCommand = subcommand ? `${command} ${subcommand}` : command;
|
|
1671
|
+
const flagNames = Object.keys(flags).filter(k => flags[k]).map(k => `--${k}`);
|
|
1672
|
+
const optionNames = Object.keys(options).map(k => `--${k}`);
|
|
1673
|
+
const usedFlags = [...flagNames, ...optionNames];
|
|
1674
|
+
const chain = options.chain || null;
|
|
1675
|
+
|
|
1529
1676
|
if (!commands[command]) {
|
|
1530
|
-
const errorData = {
|
|
1677
|
+
const errorData = {
|
|
1531
1678
|
error: `Unknown command: ${command}`,
|
|
1532
1679
|
available: Object.keys(commands)
|
|
1533
1680
|
};
|
|
1534
1681
|
const formatted = formatOutput(errorData, { pretty, table });
|
|
1535
1682
|
output(formatted.text);
|
|
1683
|
+
trackCommandFailed({ command: fullCommand, duration_ms: Date.now() - startTime, error_code: 'UNKNOWN_COMMAND', flags: usedFlags, chain });
|
|
1536
1684
|
notify();
|
|
1537
1685
|
exit(1);
|
|
1538
1686
|
return { type: 'error', data: errorData };
|
|
@@ -1560,6 +1708,7 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1560
1708
|
|
|
1561
1709
|
// Commands that handle their own output return undefined
|
|
1562
1710
|
if (result === undefined) {
|
|
1711
|
+
trackCommandSucceeded({ command: fullCommand, duration_ms: Date.now() - startTime, flags: usedFlags, chain });
|
|
1563
1712
|
notify();
|
|
1564
1713
|
return { type: 'no-output', command };
|
|
1565
1714
|
}
|
|
@@ -1568,6 +1717,7 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1568
1717
|
if (command === 'schema') {
|
|
1569
1718
|
const formatted = formatOutput(result, { pretty, table: false });
|
|
1570
1719
|
output(formatted.text);
|
|
1720
|
+
trackCommandSucceeded({ command: fullCommand, duration_ms: Date.now() - startTime, flags: usedFlags, chain });
|
|
1571
1721
|
notify();
|
|
1572
1722
|
return { type: 'schema', data: result };
|
|
1573
1723
|
}
|
|
@@ -1578,6 +1728,13 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1578
1728
|
result = filterFields(result, fields);
|
|
1579
1729
|
}
|
|
1580
1730
|
|
|
1731
|
+
// Alerts list with --table uses custom table format
|
|
1732
|
+
if (command === 'alerts' && subcommand === 'list' && table) {
|
|
1733
|
+
output(formatAlertsTable(result));
|
|
1734
|
+
notify();
|
|
1735
|
+
return { type: 'success', data: result };
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1581
1738
|
// Output in requested format
|
|
1582
1739
|
if (stream) {
|
|
1583
1740
|
// Stream mode: output each record as a JSON line (NDJSON)
|
|
@@ -1585,6 +1742,7 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1585
1742
|
if (streamOutput) {
|
|
1586
1743
|
output(streamOutput);
|
|
1587
1744
|
}
|
|
1745
|
+
trackCommandSucceeded({ command: fullCommand, duration_ms: Date.now() - startTime, from_cache: !!result?.fromCache, flags: usedFlags, chain });
|
|
1588
1746
|
notify();
|
|
1589
1747
|
return { type: 'stream', data: result };
|
|
1590
1748
|
}
|
|
@@ -1592,12 +1750,21 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1592
1750
|
const successData = { success: true, data: result };
|
|
1593
1751
|
const formatted = formatOutput(successData, { pretty, table, csv });
|
|
1594
1752
|
output(formatted.text);
|
|
1753
|
+
trackCommandSucceeded({ command: fullCommand, duration_ms: Date.now() - startTime, from_cache: !!result?.fromCache, flags: usedFlags, chain });
|
|
1595
1754
|
notify();
|
|
1596
1755
|
return { type: csv ? 'csv' : 'success', data: result };
|
|
1597
1756
|
} catch (error) {
|
|
1598
1757
|
const errorData = formatError(error);
|
|
1599
1758
|
const formatted = formatOutput(errorData, { pretty, table, csv });
|
|
1600
1759
|
output(formatted.text);
|
|
1760
|
+
trackCommandFailed({
|
|
1761
|
+
command: fullCommand,
|
|
1762
|
+
duration_ms: Date.now() - startTime,
|
|
1763
|
+
error_code: error.code || 'UNKNOWN',
|
|
1764
|
+
status: error.status || null,
|
|
1765
|
+
flags: usedFlags,
|
|
1766
|
+
chain,
|
|
1767
|
+
});
|
|
1601
1768
|
notify();
|
|
1602
1769
|
exit(1);
|
|
1603
1770
|
return { type: 'error', data: errorData };
|
package/src/rpc-urls.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for chain RPC endpoints.
|
|
3
|
+
*
|
|
4
|
+
* Both trading.js and transfer.js read from here so that:
|
|
5
|
+
* (a) adding a new chain only requires one edit, and
|
|
6
|
+
* (b) env-var overrides work consistently across all commands.
|
|
7
|
+
*
|
|
8
|
+
* Override env vars:
|
|
9
|
+
* NANSEN_EVM_RPC Custom Ethereum RPC (also used as generic EVM fallback)
|
|
10
|
+
* NANSEN_BASE_RPC Custom Base RPC
|
|
11
|
+
* NANSEN_SOLANA_RPC Custom Solana RPC
|
|
12
|
+
*
|
|
13
|
+
* Backward-compat aliases (deprecated — prefer the forms above):
|
|
14
|
+
* NANSEN_RPC_BASE Old name for NANSEN_BASE_RPC; trading.js previously read this
|
|
15
|
+
* but transfer.js never did, so the two commands were inconsistent.
|
|
16
|
+
* Both forms are now accepted here so existing .env files keep
|
|
17
|
+
* working while new code uses the standardised NANSEN_BASE_RPC name.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const DEFAULT_EVM_RPC = 'https://eth.public-rpc.com';
|
|
21
|
+
const DEFAULT_BASE_RPC = 'https://mainnet.base.org';
|
|
22
|
+
const DEFAULT_SOLANA_RPC = 'https://api.mainnet-beta.solana.com';
|
|
23
|
+
|
|
24
|
+
export const CHAIN_RPCS = {
|
|
25
|
+
ethereum: process.env.NANSEN_EVM_RPC || DEFAULT_EVM_RPC,
|
|
26
|
+
evm: process.env.NANSEN_EVM_RPC || DEFAULT_EVM_RPC, // generic EVM fallback
|
|
27
|
+
base: process.env.NANSEN_BASE_RPC || process.env.NANSEN_RPC_BASE || DEFAULT_BASE_RPC,
|
|
28
|
+
solana: process.env.NANSEN_SOLANA_RPC || DEFAULT_SOLANA_RPC,
|
|
29
|
+
};
|