nansen-cli 1.10.1 → 1.11.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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.11.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#186](https://github.com/nansen-ai/nansen-cli/pull/186) [`feecc50`](https://github.com/nansen-ai/nansen-cli/commit/feecc5080254b55aaef0addb646279d52a468063) Thanks [@TimNooren](https://github.com/TimNooren)! - Trade commands output to stdout instead of stderr; wallet send prints human-readable text instead of JSON
8
+
9
+ ### Patch Changes
10
+
11
+ - [#166](https://github.com/nansen-ai/nansen-cli/pull/166) [`c1034db`](https://github.com/nansen-ai/nansen-cli/commit/c1034dbb4bf2fc173f377cbc0adbbbe3e67873aa) Thanks [@0xlaveen](https://github.com/0xlaveen)! - fix: pass --page parameter correctly in smart-money, profiler, token, perp, and points commands
12
+
13
+ - [#137](https://github.com/nansen-ai/nansen-cli/pull/137) [`1214767`](https://github.com/nansen-ai/nansen-cli/commit/12147675aadfd0bd97627cb2f41f1dcc5205b0d7) Thanks [@0xlaveen](https://github.com/0xlaveen)! - Add missing sort/filters options to profiler schema and fix pnl sort/filters forwarding
14
+
3
15
  ## 1.10.1
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![npm version](https://img.shields.io/npm/v/nansen-cli.svg)](https://www.npmjs.com/package/nansen-cli)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- > **Built by agents, for agents.** Command-line interface for the [Nansen API](https://docs.nansen.ai) with structured JSON output.
6
+ > **Built by agents, for agents.** Command-line interface for the [Nansen API](https://docs.nansen.ai), designed for AI agents.
7
7
 
8
8
  ## Installation
9
9
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nansen-cli",
3
- "version": "1.10.1",
3
+ "version": "1.11.0",
4
4
  "description": "Command-line interface for Nansen API - designed for AI agents",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/api.js CHANGED
@@ -62,7 +62,7 @@ export class NansenError extends Error {
62
62
  this.name = 'NansenError';
63
63
  this.code = code;
64
64
  this.status = status;
65
- this.data = data;
65
+ this.details = data;
66
66
  }
67
67
 
68
68
  toJSON() {
@@ -70,7 +70,7 @@ export class NansenError extends Error {
70
70
  error: this.message,
71
71
  code: this.code,
72
72
  status: this.status,
73
- details: this.data,
73
+ details: this.details,
74
74
  };
75
75
  }
76
76
  }
@@ -733,7 +733,7 @@ export class NansenAPI {
733
733
  }
734
734
 
735
735
  async addressPnl(params = {}) {
736
- const { address, chain = 'ethereum', date, days = 30, pagination } = params;
736
+ const { address, chain = 'ethereum', date, days = 30, filters = {}, orderBy, pagination } = params;
737
737
  if (address) {
738
738
  const validation = validateAddress(address, chain);
739
739
  if (!validation.valid) throw new NansenError(validation.error, validation.code);
@@ -743,6 +743,8 @@ export class NansenAPI {
743
743
  address,
744
744
  chain,
745
745
  date: dateRange,
746
+ filters,
747
+ order_by: orderBy,
746
748
  pagination
747
749
  });
748
750
  }
package/src/cli.js CHANGED
@@ -24,6 +24,16 @@ const schemaDefinition = require('./schema.json');
24
24
  // and should be updated whenever the API changes — do not edit returns arrays here.
25
25
  export const SCHEMA = { version: VERSION, ...schemaDefinition };
26
26
 
27
+ // ============= Pagination =============
28
+
29
+ export function buildPagination(options) {
30
+ if (!options.limit && !options.page) return undefined;
31
+ return {
32
+ page: Math.max(1, parseInt(options.page, 10) || 1),
33
+ per_page: options.limit,
34
+ };
35
+ }
36
+
27
37
  // ============= Field Filtering =============
28
38
 
29
39
  /**
@@ -313,13 +323,17 @@ export function formatOutput(data, { pretty = false, table = false, csv = false
313
323
 
314
324
  // Format error data (returns object, does not exit)
315
325
  export function formatError(error) {
316
- return {
326
+ const details = error.details ?? error.data ?? null;
327
+ const result = {
317
328
  success: false,
318
329
  error: error.message,
319
330
  code: error.code || 'UNKNOWN',
320
331
  status: error.status || null,
321
- details: error.data || null
322
332
  };
333
+ if (details != null && !(typeof details === 'object' && !Array.isArray(details) && Object.keys(details).length === 0)) {
334
+ result.details = details;
335
+ }
336
+ return result;
323
337
  }
324
338
 
325
339
  /**
@@ -606,7 +620,7 @@ export async function compareWallets(api, params = {}) {
606
620
 
607
621
  export const BANNER = '';
608
622
 
609
- export const HELP = `Nansen CLI v${VERSION} — structured JSON output for AI agents.
623
+ export const HELP = `Nansen CLI v${VERSION} — designed for AI agents.
610
624
 
611
625
  USAGE: nansen <command> [subcommand] [options]
612
626
 
@@ -855,7 +869,7 @@ export function buildCommands(deps = {}) {
855
869
  const chains = options.chains || [chain];
856
870
  const filters = options.filters || {};
857
871
  const orderBy = parseSort(options.sort, options['order-by']);
858
- const pagination = options.limit ? { page: 1, per_page: options.limit } : undefined;
872
+ const pagination = buildPagination(options);
859
873
 
860
874
  // Add smart money label filter if specified
861
875
  if (options.labels) {
@@ -906,7 +920,7 @@ export function buildCommands(deps = {}) {
906
920
  }
907
921
  const filters = options.filters || {};
908
922
  const orderBy = parseSort(options.sort, options['order-by']);
909
- const pagination = options.limit ? { page: 1, per_page: options.limit } : undefined;
923
+ const pagination = buildPagination(options);
910
924
  const days = options.days ? parseInt(options.days) : 30;
911
925
 
912
926
  const handlers = {
@@ -918,7 +932,7 @@ export function buildCommands(deps = {}) {
918
932
  },
919
933
  'pnl': () => {
920
934
  const date = parseDateOption(options.date, days);
921
- return apiInstance.addressPnl({ address, chain, date, days, pagination });
935
+ return apiInstance.addressPnl({ address, chain, date, days, filters, orderBy, pagination });
922
936
  },
923
937
  'search': () => apiInstance.entitySearch({ query: options.query }),
924
938
  'historical-balances': () => apiInstance.addressHistoricalBalances({ address, chain, filters, orderBy, pagination, days }),
@@ -992,7 +1006,7 @@ export function buildCommands(deps = {}) {
992
1006
  const timeframe = options.timeframe || '24h';
993
1007
  const filters = options.filters || {};
994
1008
  const orderBy = parseSort(options.sort, options['order-by']);
995
- const pagination = options.limit ? { page: 1, per_page: options.limit } : undefined;
1009
+ const pagination = buildPagination(options);
996
1010
  const days = options.days ? parseInt(options.days) : 30;
997
1011
 
998
1012
  // Convenience filter for smart money only
@@ -1098,7 +1112,7 @@ export function buildCommands(deps = {}) {
1098
1112
  const subcommand = args[0] || 'help';
1099
1113
  const filters = options.filters || {};
1100
1114
  const orderBy = parseSort(options.sort, options['order-by']);
1101
- const pagination = options.limit ? { page: 1, per_page: options.limit } : undefined;
1115
+ const pagination = buildPagination(options);
1102
1116
  const days = options.days ? parseInt(options.days) : 30;
1103
1117
 
1104
1118
  const handlers = {
@@ -1130,7 +1144,7 @@ export function buildCommands(deps = {}) {
1130
1144
  'points': async (args, apiInstance, flags, options) => {
1131
1145
  const subcommand = args[0] || 'help';
1132
1146
  const tier = options.tier;
1133
- const pagination = options.limit ? { page: 1, per_page: options.limit } : undefined;
1147
+ const pagination = buildPagination(options);
1134
1148
 
1135
1149
  const handlers = {
1136
1150
  'leaderboard': () => apiInstance.pointsLeaderboard({ tier, pagination }),
package/src/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * Nansen CLI - Command-line interface for Nansen API
4
- * Designed for AI agents with structured JSON output
5
- *
4
+ * Designed for AI agents.
5
+ *
6
6
  * Usage: nansen <command> [options]
7
- *
8
- * All output is JSON for easy parsing by AI agents.
7
+ *
8
+ * Research commands return JSON; operational commands print human-readable text.
9
9
  * Use --pretty for human-readable formatting.
10
10
  *
11
11
  * Core logic lives in cli.js for testability.
package/src/schema.json CHANGED
@@ -33,6 +33,10 @@
33
33
  "filters": {
34
34
  "type": "object",
35
35
  "description": "Additional filters as JSON"
36
+ },
37
+ "page": {
38
+ "type": "number",
39
+ "description": "Page number for paginated results (default: 1)"
36
40
  }
37
41
  },
38
42
  "returns": [
@@ -70,6 +74,10 @@
70
74
  },
71
75
  "filters": {
72
76
  "type": "object"
77
+ },
78
+ "page": {
79
+ "type": "number",
80
+ "description": "Page number for paginated results (default: 1)"
73
81
  }
74
82
  },
75
83
  "returns": [
@@ -110,6 +118,10 @@
110
118
  },
111
119
  "filters": {
112
120
  "type": "object"
121
+ },
122
+ "page": {
123
+ "type": "number",
124
+ "description": "Page number for paginated results (default: 1)"
113
125
  }
114
126
  },
115
127
  "returns": [
@@ -141,6 +153,10 @@
141
153
  },
142
154
  "labels": {
143
155
  "type": "string|array"
156
+ },
157
+ "page": {
158
+ "type": "number",
159
+ "description": "Page number for paginated results (default: 1)"
144
160
  }
145
161
  },
146
162
  "returns": [
@@ -164,6 +180,10 @@
164
180
  },
165
181
  "filters": {
166
182
  "type": "object"
183
+ },
184
+ "page": {
185
+ "type": "number",
186
+ "description": "Page number for paginated results (default: 1)"
167
187
  }
168
188
  },
169
189
  "returns": [
@@ -200,6 +220,10 @@
200
220
  },
201
221
  "limit": {
202
222
  "type": "number"
223
+ },
224
+ "page": {
225
+ "type": "number",
226
+ "description": "Page number for paginated results (default: 1)"
203
227
  }
204
228
  },
205
229
  "returns": [
@@ -290,6 +314,18 @@
290
314
  "days": {
291
315
  "type": "number",
292
316
  "default": 30
317
+ },
318
+ "page": {
319
+ "type": "number",
320
+ "description": "Page number for paginated results (default: 1)"
321
+ },
322
+ "sort": {
323
+ "type": "string",
324
+ "description": "Sort field:direction (e.g., block_timestamp:desc)"
325
+ },
326
+ "filters": {
327
+ "type": "object",
328
+ "description": "Additional filters as JSON"
293
329
  }
294
330
  },
295
331
  "returns": [
@@ -324,6 +360,18 @@
324
360
  },
325
361
  "limit": {
326
362
  "type": "number"
363
+ },
364
+ "page": {
365
+ "type": "number",
366
+ "description": "Page number for paginated results (default: 1)"
367
+ },
368
+ "sort": {
369
+ "type": "string",
370
+ "description": "Sort field:direction (e.g., pnl_usd_realised:desc)"
371
+ },
372
+ "filters": {
373
+ "type": "object",
374
+ "description": "Additional filters as JSON"
327
375
  }
328
376
  },
329
377
  "returns": [
@@ -358,6 +406,10 @@
358
406
  },
359
407
  "limit": {
360
408
  "type": "number"
409
+ },
410
+ "page": {
411
+ "type": "number",
412
+ "description": "Page number for paginated results (default: 1)"
361
413
  }
362
414
  },
363
415
  "returns": [
@@ -381,6 +433,18 @@
381
433
  },
382
434
  "limit": {
383
435
  "type": "number"
436
+ },
437
+ "page": {
438
+ "type": "number",
439
+ "description": "Page number for paginated results (default: 1)"
440
+ },
441
+ "sort": {
442
+ "type": "string",
443
+ "description": "Sort field:direction (e.g., value_usd:desc)"
444
+ },
445
+ "filters": {
446
+ "type": "object",
447
+ "description": "Additional filters as JSON"
384
448
  }
385
449
  },
386
450
  "returns": [
@@ -405,6 +469,14 @@
405
469
  },
406
470
  "limit": {
407
471
  "type": "number"
472
+ },
473
+ "page": {
474
+ "type": "number",
475
+ "description": "Page number for paginated results (default: 1)"
476
+ },
477
+ "sort": {
478
+ "type": "string",
479
+ "description": "Sort field:direction (e.g., order:desc)"
408
480
  }
409
481
  },
410
482
  "returns": [
@@ -434,6 +506,18 @@
434
506
  },
435
507
  "limit": {
436
508
  "type": "number"
509
+ },
510
+ "page": {
511
+ "type": "number",
512
+ "description": "Page number for paginated results (default: 1)"
513
+ },
514
+ "sort": {
515
+ "type": "string",
516
+ "description": "Sort field:direction (e.g., total_volume_usd:desc)"
517
+ },
518
+ "filters": {
519
+ "type": "object",
520
+ "description": "Additional filters as JSON"
437
521
  }
438
522
  },
439
523
  "returns": [
@@ -481,6 +565,18 @@
481
565
  },
482
566
  "limit": {
483
567
  "type": "number"
568
+ },
569
+ "page": {
570
+ "type": "number",
571
+ "description": "Page number for paginated results (default: 1)"
572
+ },
573
+ "sort": {
574
+ "type": "string",
575
+ "description": "Sort field:direction (e.g., position_value_usd:desc)"
576
+ },
577
+ "filters": {
578
+ "type": "object",
579
+ "description": "Additional filters as JSON"
484
580
  }
485
581
  },
486
582
  "returns": [
@@ -511,6 +607,18 @@
511
607
  },
512
608
  "limit": {
513
609
  "type": "number"
610
+ },
611
+ "page": {
612
+ "type": "number",
613
+ "description": "Page number for paginated results (default: 1)"
614
+ },
615
+ "sort": {
616
+ "type": "string",
617
+ "description": "Sort field:direction (e.g., value_usd:desc)"
618
+ },
619
+ "filters": {
620
+ "type": "object",
621
+ "description": "Additional filters as JSON"
514
622
  }
515
623
  },
516
624
  "returns": [
@@ -774,6 +882,10 @@
774
882
  "filters": {
775
883
  "type": "object",
776
884
  "description": "Additional filters as JSON"
885
+ },
886
+ "page": {
887
+ "type": "number",
888
+ "description": "Page number for paginated results (default: 1)"
777
889
  }
778
890
  },
779
891
  "returns": [
@@ -819,6 +931,10 @@
819
931
  "filters": {
820
932
  "type": "object",
821
933
  "description": "Additional filters as JSON"
934
+ },
935
+ "page": {
936
+ "type": "number",
937
+ "description": "Page number for paginated results (default: 1)"
822
938
  }
823
939
  },
824
940
  "returns": [
@@ -864,6 +980,10 @@
864
980
  "filters": {
865
981
  "type": "object",
866
982
  "description": "Additional filters as JSON"
983
+ },
984
+ "page": {
985
+ "type": "number",
986
+ "description": "Page number for paginated results (default: 1)"
867
987
  }
868
988
  },
869
989
  "returns": [
@@ -904,6 +1024,10 @@
904
1024
  "filters": {
905
1025
  "type": "object",
906
1026
  "description": "Additional filters as JSON"
1027
+ },
1028
+ "page": {
1029
+ "type": "number",
1030
+ "description": "Page number for paginated results (default: 1)"
907
1031
  }
908
1032
  },
909
1033
  "returns": [
@@ -947,6 +1071,10 @@
947
1071
  "filters": {
948
1072
  "type": "object",
949
1073
  "description": "Additional filters as JSON"
1074
+ },
1075
+ "page": {
1076
+ "type": "number",
1077
+ "description": "Page number for paginated results (default: 1)"
950
1078
  }
951
1079
  },
952
1080
  "returns": [
@@ -999,6 +1127,10 @@
999
1127
  "filters": {
1000
1128
  "type": "object",
1001
1129
  "description": "Additional filters as JSON"
1130
+ },
1131
+ "page": {
1132
+ "type": "number",
1133
+ "description": "Page number for paginated results (default: 1)"
1002
1134
  }
1003
1135
  },
1004
1136
  "returns": [
@@ -1086,6 +1218,10 @@
1086
1218
  "filters": {
1087
1219
  "type": "object",
1088
1220
  "description": "Additional filters as JSON"
1221
+ },
1222
+ "page": {
1223
+ "type": "number",
1224
+ "description": "Page number for paginated results (default: 1)"
1089
1225
  }
1090
1226
  },
1091
1227
  "returns": [
@@ -1117,6 +1253,10 @@
1117
1253
  "filters": {
1118
1254
  "type": "object",
1119
1255
  "description": "Additional filters as JSON"
1256
+ },
1257
+ "page": {
1258
+ "type": "number",
1259
+ "description": "Page number for paginated results (default: 1)"
1120
1260
  }
1121
1261
  },
1122
1262
  "returns": [
@@ -1159,6 +1299,10 @@
1159
1299
  "filters": {
1160
1300
  "type": "object",
1161
1301
  "description": "Additional filters as JSON"
1302
+ },
1303
+ "page": {
1304
+ "type": "number",
1305
+ "description": "Page number for paginated results (default: 1)"
1162
1306
  }
1163
1307
  },
1164
1308
  "returns": [
@@ -1192,6 +1336,10 @@
1192
1336
  "filters": {
1193
1337
  "type": "object",
1194
1338
  "description": "Additional filters as JSON"
1339
+ },
1340
+ "page": {
1341
+ "type": "number",
1342
+ "description": "Page number for paginated results (default: 1)"
1195
1343
  }
1196
1344
  },
1197
1345
  "returns": [
@@ -1230,6 +1378,10 @@
1230
1378
  "filters": {
1231
1379
  "type": "object",
1232
1380
  "description": "Additional filters as JSON"
1381
+ },
1382
+ "page": {
1383
+ "type": "number",
1384
+ "description": "Page number for paginated results (default: 1)"
1233
1385
  }
1234
1386
  },
1235
1387
  "returns": [
@@ -1280,6 +1432,10 @@
1280
1432
  "type": "number",
1281
1433
  "default": 25,
1282
1434
  "description": "Max results (1-50)"
1435
+ },
1436
+ "page": {
1437
+ "type": "number",
1438
+ "description": "Page number for paginated results (default: 1)"
1283
1439
  }
1284
1440
  },
1285
1441
  "returns": [
@@ -1306,6 +1462,10 @@
1306
1462
  },
1307
1463
  "filters": {
1308
1464
  "type": "object"
1465
+ },
1466
+ "page": {
1467
+ "type": "number",
1468
+ "description": "Page number for paginated results (default: 1)"
1309
1469
  }
1310
1470
  },
1311
1471
  "returns": [
@@ -1336,6 +1496,10 @@
1336
1496
  },
1337
1497
  "filters": {
1338
1498
  "type": "object"
1499
+ },
1500
+ "page": {
1501
+ "type": "number",
1502
+ "description": "Page number for paginated results (default: 1)"
1339
1503
  }
1340
1504
  },
1341
1505
  "returns": [
@@ -1384,6 +1548,10 @@
1384
1548
  },
1385
1549
  "limit": {
1386
1550
  "type": "number"
1551
+ },
1552
+ "page": {
1553
+ "type": "number",
1554
+ "description": "Page number for paginated results (default: 1)"
1387
1555
  }
1388
1556
  },
1389
1557
  "returns": [
@@ -1426,7 +1594,7 @@
1426
1594
  },
1427
1595
  "wallet": {
1428
1596
  "type": "string",
1429
- "description": "Wallet name (or \"walletconnect\"/\"wc\" for WalletConnect, EVM only). A configured wallet is required \u2014 run `nansen wallet create` if you haven't set one up yet."
1597
+ "description": "Wallet name (or \"walletconnect\"/\"wc\" for WalletConnect, EVM only). A configured wallet is required run `nansen wallet create` if you haven't set one up yet."
1430
1598
  }
1431
1599
  },
1432
1600
  "prerequisites": [
package/src/trading.js CHANGED
@@ -756,7 +756,7 @@ export function formatQuote(quote, index) {
756
756
  * Build trading command handlers for CLI integration.
757
757
  */
758
758
  export function buildTradingCommands(deps = {}) {
759
- const { errorOutput = console.error, exit = process.exit } = deps;
759
+ const { log = console.log, exit = process.exit } = deps;
760
760
 
761
761
  return {
762
762
  'quote': async (args, apiInstance, flags, options) => {
@@ -773,7 +773,7 @@ export function buildTradingCommands(deps = {}) {
773
773
  const swapMode = options['swap-mode'] || 'exactIn';
774
774
 
775
775
  if (!chain || !from || !to || !amount) {
776
- errorOutput(`
776
+ log(`
777
777
  Usage: nansen trade quote --chain <chain> --from <token> --to <token> --amount <baseUnits>
778
778
 
779
779
  PREREQUISITE:
@@ -803,7 +803,7 @@ EXAMPLES:
803
803
 
804
804
  const amountError = validateBaseUnitAmount(amount);
805
805
  if (amountError) {
806
- errorOutput(`Error: ${amountError}`);
806
+ log(`Error: ${amountError}`);
807
807
  exit(1);
808
808
  return;
809
809
  }
@@ -817,13 +817,13 @@ EXAMPLES:
817
817
  let walletAddress;
818
818
  if (isWalletConnect) {
819
819
  if (chainType !== 'evm') {
820
- errorOutput('WalletConnect is only supported for EVM chains');
820
+ log('WalletConnect is only supported for EVM chains');
821
821
  exit(1);
822
822
  return;
823
823
  }
824
824
  walletAddress = await getWalletConnectAddress();
825
825
  if (!walletAddress) {
826
- errorOutput('No WalletConnect session active. Run: walletconnect connect');
826
+ log('No WalletConnect session active. Run: walletconnect connect');
827
827
  exit(1);
828
828
  return;
829
829
  }
@@ -839,16 +839,16 @@ EXAMPLES:
839
839
  }
840
840
 
841
841
  if (!walletAddress) {
842
- errorOutput('No wallet found. A wallet address is required for quotes because the trading API builds a transaction specific to the sender.\nCreate one with: nansen wallet create');
842
+ log('No wallet found. A wallet address is required for quotes because the trading API builds a transaction specific to the sender.\nCreate one with: nansen wallet create');
843
843
  exit(1);
844
844
  return;
845
845
  }
846
846
 
847
- errorOutput(`\nFetching quote on ${chainConfig.name}...`);
848
- errorOutput(` Wallet: ${walletAddress}`);
847
+ log(`\nFetching quote on ${chainConfig.name}...`);
848
+ log(` Wallet: ${walletAddress}`);
849
849
 
850
850
  const fromWarning = getWrappedNativeFromWarning(from, chain);
851
- if (fromWarning) errorOutput(` ${fromWarning}`);
851
+ if (fromWarning) log(` ${fromWarning}`);
852
852
 
853
853
  const params = {
854
854
  chainIndex: chainConfig.index,
@@ -865,30 +865,30 @@ EXAMPLES:
865
865
  const response = await getQuote(params);
866
866
 
867
867
  if (!response.success || !response.quotes?.length) {
868
- errorOutput('No quotes available');
868
+ log('No quotes available');
869
869
  if (response.warnings?.length) {
870
- response.warnings.forEach(w => errorOutput(` Warning: ${w}`));
870
+ response.warnings.forEach(w => log(` Warning: ${w}`));
871
871
  }
872
872
  exit(1);
873
873
  return;
874
874
  }
875
875
 
876
- errorOutput('');
877
- response.quotes.forEach((q, i) => errorOutput(formatQuote(q, i)));
876
+ log('');
877
+ response.quotes.forEach((q, i) => log(formatQuote(q, i)));
878
878
 
879
879
  const quoteId = saveQuote(response, chain, isWalletConnect ? 'walletconnect' : 'local');
880
- errorOutput(`\n Quote ID: ${quoteId}`);
881
- errorOutput(` Execute: nansen trade execute --quote ${quoteId}`);
880
+ log(`\n Quote ID: ${quoteId}`);
881
+ log(` Execute: nansen trade execute --quote ${quoteId}`);
882
882
  if (response.quotes.length > 1) {
883
- errorOutput(` Pin #1: nansen trade execute --quote ${quoteId} --quote-index 0`);
883
+ log(` Pin #1: nansen trade execute --quote ${quoteId} --quote-index 0`);
884
884
  }
885
885
 
886
886
  if (response.quotes[0]?.approvalAddress && !isNativeToken(response.quotes[0]?.inputMint)) {
887
- errorOutput(`\n Warning: This token swap requires an ERC-20 approval step.`);
888
- errorOutput(` The execute command will handle this automatically.`);
887
+ log(`\n Warning: This token swap requires an ERC-20 approval step.`);
888
+ log(` The execute command will handle this automatically.`);
889
889
  }
890
890
 
891
- errorOutput('');
891
+ log('');
892
892
  return undefined; // Output already printed above
893
893
 
894
894
  } catch (err) {
@@ -896,8 +896,8 @@ EXAMPLES:
896
896
  if (err.code === 'INVALID_AMOUNT' || /amount/i.test(err.message)) {
897
897
  message += '. Amounts must be in base units (e.g., 1000000000 lamports for 1 SOL, 1000000000000000000 wei for 1 ETH)';
898
898
  }
899
- errorOutput(`Error: ${message}`);
900
- if (err.details) errorOutput(` Details: ${JSON.stringify(err.details)}`);
899
+ log(`Error: ${message}`);
900
+ if (err.details) log(` Details: ${JSON.stringify(err.details)}`);
901
901
  exit(1);
902
902
  }
903
903
  },
@@ -908,7 +908,7 @@ EXAMPLES:
908
908
  const noSimulate = flags['no-simulate'] || flags.noSimulate;
909
909
 
910
910
  if (!quoteId) {
911
- errorOutput(`
911
+ log(`
912
912
  Usage: nansen trade execute --quote <quoteId> [options]
913
913
 
914
914
  OPTIONS:
@@ -931,7 +931,7 @@ EXAMPLES:
931
931
 
932
932
  const allQuotes = quoteData.response.quotes || [];
933
933
  if (!allQuotes.length) {
934
- errorOutput('❌ No quote data found');
934
+ log('❌ No quote data found');
935
935
  exit(1);
936
936
  return;
937
937
  }
@@ -944,8 +944,8 @@ EXAMPLES:
944
944
  // Check if any quote in range has transaction data before prompting for password
945
945
  const hasAnyTransaction = allQuotes.slice(startIndex, endIndex).some(q => q?.transaction);
946
946
  if (!hasAnyTransaction) {
947
- errorOutput('❌ No quotes contain transaction data.');
948
- errorOutput(' Ensure userWalletAddress was provided when fetching the quote.');
947
+ log('❌ No quotes contain transaction data.');
948
+ log(' Ensure userWalletAddress was provided when fetching the quote.');
949
949
  exit(1);
950
950
  return;
951
951
  }
@@ -965,7 +965,7 @@ EXAMPLES:
965
965
  effectiveWalletName = list.defaultWallet;
966
966
  }
967
967
  if (!effectiveWalletName) {
968
- errorOutput('No wallet found. Create one with: nansen wallet create');
968
+ log('No wallet found. Create one with: nansen wallet create');
969
969
  exit(1);
970
970
  return;
971
971
  }
@@ -974,13 +974,13 @@ EXAMPLES:
974
974
  } else {
975
975
  // Verify WalletConnect session is still active and address matches quote
976
976
  if (chainType !== 'evm') {
977
- errorOutput('WalletConnect is only supported for EVM chains');
977
+ log('WalletConnect is only supported for EVM chains');
978
978
  exit(1);
979
979
  return;
980
980
  }
981
981
  const wcAddress = await getWalletConnectAddress();
982
982
  if (!wcAddress) {
983
- errorOutput('No WalletConnect session active. Run: walletconnect connect');
983
+ log('No WalletConnect session active. Run: walletconnect connect');
984
984
  exit(1);
985
985
  return;
986
986
  }
@@ -988,7 +988,7 @@ EXAMPLES:
988
988
  const quoteWallet = quoteData.response?.quotes?.[0]?.transaction?.from
989
989
  || quoteData.response?.metadata?.userWalletAddress;
990
990
  if (quoteWallet && wcAddress.toLowerCase() !== quoteWallet.toLowerCase()) {
991
- errorOutput(`Connected wallet (${wcAddress}) doesn't match quote. Get a new quote with --wallet walletconnect`);
991
+ log(`Connected wallet (${wcAddress}) doesn't match quote. Get a new quote with --wallet walletconnect`);
992
992
  exit(1);
993
993
  return;
994
994
  }
@@ -1004,17 +1004,17 @@ EXAMPLES:
1004
1004
 
1005
1005
  // Verify transaction data exists
1006
1006
  if (!currentQuote.transaction) {
1007
- errorOutput(` ⚠ Quote ${quoteName}: no transaction data, skipping...`);
1007
+ log(` ⚠ Quote ${quoteName}: no transaction data, skipping...`);
1008
1008
  lastQuoteError = `Quote ${quoteName} has no transaction data`;
1009
1009
  continue;
1010
1010
  }
1011
1011
 
1012
- errorOutput(`\nExecuting trade on ${chainConfig.name}...`);
1012
+ log(`\nExecuting trade on ${chainConfig.name}...`);
1013
1013
  if (endIndex - startIndex > 1) {
1014
- errorOutput(` Trying quote ${qi + 1}/${allQuotes.length} (${quoteName})...`);
1014
+ log(` Trying quote ${qi + 1}/${allQuotes.length} (${quoteName})...`);
1015
1015
  }
1016
- errorOutput(formatQuote(currentQuote));
1017
- errorOutput('');
1016
+ log(formatQuote(currentQuote));
1017
+ log('');
1018
1018
 
1019
1019
  try {
1020
1020
  let signedTransaction;
@@ -1027,7 +1027,7 @@ EXAMPLES:
1027
1027
  if (typeof txBase64 === 'object' && txBase64.data) {
1028
1028
  txBase64 = base58Decode(txBase64.data).toString('base64');
1029
1029
  }
1030
- errorOutput(' Signing Solana transaction...');
1030
+ log(' Signing Solana transaction...');
1031
1031
  signedTransaction = signSolanaTransaction(txBase64, exported.solana.privateKey);
1032
1032
  requestId = currentQuote.metadata?.requestId;
1033
1033
 
@@ -1041,15 +1041,15 @@ EXAMPLES:
1041
1041
  if (isNative) {
1042
1042
  const expectedValue = BigInt(currentQuote.inAmount || currentQuote.inputAmount || '0');
1043
1043
  if (txValue !== expectedValue) {
1044
- errorOutput(` ❌ Transaction value mismatch for ${quoteName}: tx.value=${txValue}, expected=${expectedValue}`);
1045
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1044
+ log(` ❌ Transaction value mismatch for ${quoteName}: tx.value=${txValue}, expected=${expectedValue}`);
1045
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1046
1046
  lastQuoteError = `${quoteName} transaction value mismatch`;
1047
1047
  continue;
1048
1048
  }
1049
1049
  } else {
1050
1050
  if (txValue > 0n) {
1051
- errorOutput(` ❌ ERC-20 swap has non-zero tx.value (${txValue}) for ${quoteName} — aborting`);
1052
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1051
+ log(` ❌ ERC-20 swap has non-zero tx.value (${txValue}) for ${quoteName} — aborting`);
1052
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1053
1053
  lastQuoteError = `${quoteName} unexpected tx.value`;
1054
1054
  continue;
1055
1055
  }
@@ -1063,10 +1063,10 @@ EXAMPLES:
1063
1063
  );
1064
1064
 
1065
1065
  if (existingAllowance >= inputAmount && existingAllowance > 0n) {
1066
- errorOutput(` ✓ Sufficient allowance exists for ${quoteName}, skipping approval`);
1066
+ log(` ✓ Sufficient allowance exists for ${quoteName}, skipping approval`);
1067
1067
  } else {
1068
- errorOutput(` ⚠ Approval required → ${currentQuote.approvalAddress}`);
1069
- errorOutput(` Sending approval via WalletConnect...`);
1068
+ log(` ⚠ Approval required → ${currentQuote.approvalAddress}`);
1069
+ log(` Sending approval via WalletConnect...`);
1070
1070
  try {
1071
1071
  const approvalResult = await sendApprovalViaWalletConnect(
1072
1072
  currentQuote.inputMint,
@@ -1076,7 +1076,7 @@ EXAMPLES:
1076
1076
  let approvalTxHash = approvalResult.txHash;
1077
1077
  if (!approvalTxHash && approvalResult.signedTransaction) {
1078
1078
  // Wallet returned a signed tx instead of broadcasting — broadcast via Trading API
1079
- errorOutput(` Broadcasting approval via Trading API...`);
1079
+ log(` Broadcasting approval via Trading API...`);
1080
1080
  const broadcastResult = await executeTransaction({
1081
1081
  signedTransaction: approvalResult.signedTransaction,
1082
1082
  chain,
@@ -1088,18 +1088,18 @@ EXAMPLES:
1088
1088
  approvalTxHash = broadcastResult.txHash;
1089
1089
  }
1090
1090
  if (approvalTxHash) {
1091
- errorOutput(` Waiting for approval confirmation...`);
1091
+ log(` Waiting for approval confirmation...`);
1092
1092
  const receipt = await waitForReceipt(chain, approvalTxHash);
1093
- errorOutput(` ✓ Approval confirmed in block ${parseInt(receipt.blockNumber, 16)}: ${approvalTxHash}`);
1093
+ log(` ✓ Approval confirmed in block ${parseInt(receipt.blockNumber, 16)}: ${approvalTxHash}`);
1094
1094
  }
1095
1095
  } catch (approvalErr) {
1096
- errorOutput(` ❌ Approval failed for ${quoteName}: ${approvalErr.message}`);
1097
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1096
+ log(` ❌ Approval failed for ${quoteName}: ${approvalErr.message}`);
1097
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1098
1098
  lastQuoteError = `${quoteName} approval failed`;
1099
1099
  continue;
1100
1100
  }
1101
1101
  await new Promise(r => setTimeout(r, 2000));
1102
- errorOutput('');
1102
+ log('');
1103
1103
  }
1104
1104
  }
1105
1105
 
@@ -1113,8 +1113,8 @@ EXAMPLES:
1113
1113
  value: txData.value ? '0x' + BigInt(txData.value).toString(16) : '0x0',
1114
1114
  });
1115
1115
  if (!sim.success) {
1116
- errorOutput(` ⚠ Simulation failed for ${quoteName}: ${sim.reason}`);
1117
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1116
+ log(` ⚠ Simulation failed for ${quoteName}: ${sim.reason}`);
1117
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1118
1118
  lastQuoteError = `${quoteName} simulation failed: ${sim.reason}`;
1119
1119
  continue;
1120
1120
  }
@@ -1127,7 +1127,7 @@ EXAMPLES:
1127
1127
  const finalGas = apiGas > 0 ? apiGas : txGas;
1128
1128
 
1129
1129
  // Send transaction via WalletConnect
1130
- errorOutput(' Sending transaction via WalletConnect...');
1130
+ log(' Sending transaction via WalletConnect...');
1131
1131
  let wcResult;
1132
1132
  try {
1133
1133
  wcResult = await sendTransactionViaWalletConnect({
@@ -1138,24 +1138,24 @@ EXAMPLES:
1138
1138
  chainId: chainConfig.chainId,
1139
1139
  });
1140
1140
  } catch (wcErr) {
1141
- errorOutput(` ❌ WalletConnect transaction failed for ${quoteName}: ${wcErr.message}`);
1142
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1141
+ log(` ❌ WalletConnect transaction failed for ${quoteName}: ${wcErr.message}`);
1142
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1143
1143
  lastQuoteError = `${quoteName}: ${wcErr.message}`;
1144
1144
  continue;
1145
1145
  }
1146
1146
 
1147
1147
  if (wcResult.txHash) {
1148
1148
  // Wallet broadcast — verify on-chain
1149
- errorOutput(' Verifying on-chain status...');
1149
+ log(' Verifying on-chain status...');
1150
1150
  try {
1151
1151
  await waitForReceipt(chain, wcResult.txHash);
1152
1152
  } catch (receiptErr) {
1153
- errorOutput(`\n ⚠ Transaction was broadcast but REVERTED on-chain!`);
1154
- errorOutput(` Tx Hash: ${wcResult.txHash}`);
1155
- errorOutput(` Explorer: ${chainConfig.explorer}${wcResult.txHash}`);
1156
- errorOutput(` Error: ${receiptErr.message}`);
1153
+ log(`\n ⚠ Transaction was broadcast but REVERTED on-chain!`);
1154
+ log(` Tx Hash: ${wcResult.txHash}`);
1155
+ log(` Explorer: ${chainConfig.explorer}${wcResult.txHash}`);
1156
+ log(` Error: ${receiptErr.message}`);
1157
1157
  if (qi + 1 < endIndex) {
1158
- errorOutput(` Trying next quote...`);
1158
+ log(` Trying next quote...`);
1159
1159
  lastQuoteError = `${quoteName} reverted on-chain`;
1160
1160
  continue;
1161
1161
  }
@@ -1163,11 +1163,11 @@ EXAMPLES:
1163
1163
  return;
1164
1164
  }
1165
1165
 
1166
- errorOutput(`\n ✓ Transaction successful!`);
1167
- errorOutput(` Tx Hash: ${wcResult.txHash}`);
1168
- errorOutput(` Chain: ${chainConfig.name}`);
1169
- errorOutput(` Explorer: ${chainConfig.explorer}${wcResult.txHash}`);
1170
- errorOutput('');
1166
+ log(`\n ✓ Transaction successful!`);
1167
+ log(` Tx Hash: ${wcResult.txHash}`);
1168
+ log(` Chain: ${chainConfig.name}`);
1169
+ log(` Explorer: ${chainConfig.explorer}${wcResult.txHash}`);
1170
+ log('');
1171
1171
  return undefined; // Success
1172
1172
  }
1173
1173
 
@@ -1191,15 +1191,15 @@ EXAMPLES:
1191
1191
  if (isNative) {
1192
1192
  const expectedValue = BigInt(currentQuote.inAmount || currentQuote.inputAmount || '0');
1193
1193
  if (txValue !== expectedValue) {
1194
- errorOutput(` ❌ Transaction value mismatch for ${quoteName}: tx.value=${txValue}, expected=${expectedValue}`);
1195
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1194
+ log(` ❌ Transaction value mismatch for ${quoteName}: tx.value=${txValue}, expected=${expectedValue}`);
1195
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1196
1196
  lastQuoteError = `${quoteName} transaction value mismatch`;
1197
1197
  continue;
1198
1198
  }
1199
1199
  } else {
1200
1200
  if (txValue > 0n) {
1201
- errorOutput(` ❌ ERC-20 swap has non-zero tx.value (${txValue}) for ${quoteName} — aborting`);
1202
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1201
+ log(` ❌ ERC-20 swap has non-zero tx.value (${txValue}) for ${quoteName} — aborting`);
1202
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1203
1203
  lastQuoteError = `${quoteName} unexpected tx.value`;
1204
1204
  continue;
1205
1205
  }
@@ -1213,10 +1213,10 @@ EXAMPLES:
1213
1213
  );
1214
1214
 
1215
1215
  if (existingAllowance >= inputAmount && existingAllowance > 0n) {
1216
- errorOutput(` ✓ Sufficient allowance exists for ${quoteName}, skipping approval`);
1216
+ log(` ✓ Sufficient allowance exists for ${quoteName}, skipping approval`);
1217
1217
  } else {
1218
- errorOutput(` ⚠ Approval required → ${currentQuote.approvalAddress}`);
1219
- errorOutput(` Sending approval tx...`);
1218
+ log(` ⚠ Approval required → ${currentQuote.approvalAddress}`);
1219
+ log(` Sending approval tx...`);
1220
1220
  const approvalNonce = await getEvmNonce(chain, walletAddress);
1221
1221
 
1222
1222
  const approvalGasPrice = currentQuote.transaction?.gasPrice || currentQuote.transaction?.maxFeePerGas || '1000000';
@@ -1236,25 +1236,25 @@ EXAMPLES:
1236
1236
  });
1237
1237
 
1238
1238
  if (approvalResult.status !== 'Success') {
1239
- errorOutput(` ❌ Approval failed for ${quoteName}: ${approvalResult.error || 'unknown error'}`);
1240
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1239
+ log(` ❌ Approval failed for ${quoteName}: ${approvalResult.error || 'unknown error'}`);
1240
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1241
1241
  lastQuoteError = `${quoteName} approval failed`;
1242
1242
  continue;
1243
1243
  }
1244
1244
 
1245
- errorOutput(` Waiting for approval confirmation...`);
1245
+ log(` Waiting for approval confirmation...`);
1246
1246
  try {
1247
1247
  const receipt = await waitForReceipt(chain, approvalResult.txHash);
1248
- errorOutput(` ✓ Approval confirmed in block ${parseInt(receipt.blockNumber, 16)}: ${approvalResult.txHash}`);
1248
+ log(` ✓ Approval confirmed in block ${parseInt(receipt.blockNumber, 16)}: ${approvalResult.txHash}`);
1249
1249
  } catch (receiptErr) {
1250
- errorOutput(` ❌ Approval may not have confirmed: ${receiptErr.message}`);
1251
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1250
+ log(` ❌ Approval may not have confirmed: ${receiptErr.message}`);
1251
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1252
1252
  lastQuoteError = `${quoteName} approval unconfirmed`;
1253
1253
  continue;
1254
1254
  }
1255
1255
  // Wait for RPC state propagation after approval
1256
1256
  await new Promise(r => setTimeout(r, 2000));
1257
- errorOutput('');
1257
+ log('');
1258
1258
  }
1259
1259
  }
1260
1260
 
@@ -1270,8 +1270,8 @@ EXAMPLES:
1270
1270
  value: txData.value ? '0x' + BigInt(txData.value).toString(16) : '0x0',
1271
1271
  });
1272
1272
  if (!sim.success) {
1273
- errorOutput(` ⚠ Simulation failed for ${quoteName}: ${sim.reason}`);
1274
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1273
+ log(` ⚠ Simulation failed for ${quoteName}: ${sim.reason}`);
1274
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1275
1275
  lastQuoteError = `${quoteName} simulation failed: ${sim.reason}`;
1276
1276
  continue;
1277
1277
  }
@@ -1285,17 +1285,17 @@ EXAMPLES:
1285
1285
  const txGas = parseInt(txData.gas || txData.gasLimit || "0");
1286
1286
  const finalGas = apiGas > 0 ? apiGas : txGas;
1287
1287
  if (finalGas !== txGas) {
1288
- errorOutput(` ℹ Using API gas ${finalGas} (tx.gas was ${txGas})`);
1288
+ log(` ℹ Using API gas ${finalGas} (tx.gas was ${txGas})`);
1289
1289
  }
1290
1290
  if (txData.gasLimit) txData.gasLimit = String(finalGas);
1291
1291
  else txData.gas = String(finalGas);
1292
1292
 
1293
- errorOutput(' Fetching nonce...');
1293
+ log(' Fetching nonce...');
1294
1294
  await new Promise(r => setTimeout(r, 1000));
1295
1295
  const nonce = await getEvmNonce(chain, walletAddress);
1296
- errorOutput(` Nonce: ${nonce}`);
1296
+ log(` Nonce: ${nonce}`);
1297
1297
 
1298
- errorOutput(' Signing EVM transaction...');
1298
+ log(' Signing EVM transaction...');
1299
1299
  signedTransaction = signEvmTransaction(
1300
1300
  currentQuote.transaction,
1301
1301
  exported.evm.privateKey,
@@ -1304,7 +1304,7 @@ EXAMPLES:
1304
1304
  );
1305
1305
  }
1306
1306
 
1307
- errorOutput(' Broadcasting...');
1307
+ log(' Broadcasting...');
1308
1308
  const execParams = {
1309
1309
  signedTransaction,
1310
1310
  chain,
@@ -1320,64 +1320,64 @@ EXAMPLES:
1320
1320
 
1321
1321
  // For EVM: verify the tx actually succeeded on-chain
1322
1322
  if (chainType === 'evm' && result.txHash) {
1323
- errorOutput(' Verifying on-chain status...');
1323
+ log(' Verifying on-chain status...');
1324
1324
  try {
1325
1325
  await waitForReceipt(chain, result.txHash);
1326
1326
  } catch (receiptErr) {
1327
- errorOutput(`\n ⚠ Transaction was broadcast but REVERTED on-chain!`);
1328
- errorOutput(` Tx Hash: ${result.txHash}`);
1329
- errorOutput(` Explorer: ${explorerUrl}`);
1330
- errorOutput(` Error: ${receiptErr.message}`);
1327
+ log(`\n ⚠ Transaction was broadcast but REVERTED on-chain!`);
1328
+ log(` Tx Hash: ${result.txHash}`);
1329
+ log(` Explorer: ${explorerUrl}`);
1330
+ log(` Error: ${receiptErr.message}`);
1331
1331
  if (qi + 1 < endIndex) {
1332
- errorOutput(` Trying next quote...`);
1332
+ log(` Trying next quote...`);
1333
1333
  lastQuoteError = `${quoteName} reverted on-chain`;
1334
1334
  continue;
1335
1335
  }
1336
- errorOutput(`\n The trading API reported success, but the contract execution failed.`);
1337
- errorOutput(` This can happen due to: stale quotes, insufficient gas, or liquidity changes.`);
1336
+ log(`\n The trading API reported success, but the contract execution failed.`);
1337
+ log(` This can happen due to: stale quotes, insufficient gas, or liquidity changes.`);
1338
1338
  exit(1);
1339
1339
  return;
1340
1340
  }
1341
1341
  }
1342
1342
 
1343
- errorOutput(`\n ✓ Transaction successful!`);
1344
- errorOutput(` Status: ${result.status}`);
1345
- errorOutput(` ${result.signature ? 'Signature' : 'Tx Hash'}: ${txId}`);
1346
- errorOutput(` Chain: ${chainConfig.name} (${result.chainType})`);
1347
- errorOutput(` Broadcaster: ${result.broadcaster}`);
1348
- errorOutput(` Explorer: ${explorerUrl}`);
1343
+ log(`\n ✓ Transaction successful!`);
1344
+ log(` Status: ${result.status}`);
1345
+ log(` ${result.signature ? 'Signature' : 'Tx Hash'}: ${txId}`);
1346
+ log(` Chain: ${chainConfig.name} (${result.chainType})`);
1347
+ log(` Broadcaster: ${result.broadcaster}`);
1348
+ log(` Explorer: ${explorerUrl}`);
1349
1349
 
1350
1350
  if (result.swapEvents?.length) {
1351
- errorOutput(` Swaps:`);
1351
+ log(` Swaps:`);
1352
1352
  result.swapEvents.forEach(e => {
1353
- errorOutput(` ${e.inputAmount} ${e.inputMint?.slice(0, 8)}... → ${e.outputAmount} ${e.outputMint?.slice(0, 8)}...`);
1353
+ log(` ${e.inputAmount} ${e.inputMint?.slice(0, 8)}... → ${e.outputAmount} ${e.outputMint?.slice(0, 8)}...`);
1354
1354
  });
1355
1355
  }
1356
- errorOutput('');
1356
+ log('');
1357
1357
  return undefined; // Success — done
1358
1358
  } else {
1359
- errorOutput(`\n ✗ Quote ${quoteName} failed: ${result.status}`);
1360
- if (result.error) errorOutput(` Error: ${result.error}`);
1359
+ log(`\n ✗ Quote ${quoteName} failed: ${result.status}`);
1360
+ if (result.error) log(` Error: ${result.error}`);
1361
1361
  lastQuoteError = `${quoteName}: ${result.error || result.status}`;
1362
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1362
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1363
1363
  }
1364
1364
 
1365
1365
  } catch (quoteErr) {
1366
- errorOutput(` ❌ Quote ${quoteName} failed: ${quoteErr.message}`);
1366
+ log(` ❌ Quote ${quoteName} failed: ${quoteErr.message}`);
1367
1367
  lastQuoteError = `${quoteName}: ${quoteErr.message}`;
1368
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1368
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1369
1369
  }
1370
1370
  }
1371
1371
 
1372
1372
  // All quotes exhausted
1373
- errorOutput(`\n❌ All quotes failed. Last error: ${lastQuoteError || 'unknown'}`);
1374
- errorOutput('');
1373
+ log(`\n❌ All quotes failed. Last error: ${lastQuoteError || 'unknown'}`);
1374
+ log('');
1375
1375
  exit(1);
1376
1376
  return undefined;
1377
1377
 
1378
1378
  } catch (err) {
1379
- errorOutput(`Error: ${err.message}`);
1380
- if (err.details) errorOutput(` Details: ${JSON.stringify(err.details)}`);
1379
+ log(`Error: ${err.message}`);
1380
+ if (err.details) log(` Details: ${JSON.stringify(err.details)}`);
1381
1381
  exit(1);
1382
1382
  }
1383
1383
  },
package/src/wallet.js CHANGED
@@ -519,7 +519,7 @@ export function buildWalletCommands(deps = {}) {
519
519
  log(` Base (recommended, lower fees): send USDC to ${result.evm}`);
520
520
  log(` Solana: send USDC to ${result.solana}`);
521
521
  log('');
522
- return result;
522
+ return;
523
523
  } catch (err) {
524
524
  log(`❌ ${err.message}`);
525
525
  exit(1);
@@ -530,7 +530,7 @@ export function buildWalletCommands(deps = {}) {
530
530
  const result = listWallets();
531
531
  if (result.wallets.length === 0) {
532
532
  log('No wallets found. Create one with: nansen wallet create');
533
- return result;
533
+ return;
534
534
  }
535
535
  log('');
536
536
  for (const w of result.wallets) {
@@ -540,7 +540,6 @@ export function buildWalletCommands(deps = {}) {
540
540
  log(` Solana: ${w.solana}`);
541
541
  log('');
542
542
  }
543
- return result;
544
543
  },
545
544
 
546
545
  'show': async () => {
@@ -557,7 +556,7 @@ export function buildWalletCommands(deps = {}) {
557
556
  log(` EVM: ${result.evm}`);
558
557
  log(` Solana: ${result.solana}`);
559
558
  log(` Created: ${result.createdAt}\n`);
560
- return result;
559
+ return;
561
560
  } catch (err) {
562
561
  log(`❌ ${err.message}`);
563
562
  exit(1);
@@ -582,7 +581,7 @@ export function buildWalletCommands(deps = {}) {
582
581
  log(` Address: ${result.solana.address}`);
583
582
  log(` Private Key: ${result.solana.privateKey}`);
584
583
  log('');
585
- return result;
584
+ return;
586
585
  } catch (err) {
587
586
  log(`❌ ${err.message}`);
588
587
  exit(1);
@@ -599,7 +598,7 @@ export function buildWalletCommands(deps = {}) {
599
598
  try {
600
599
  const result = setDefaultWallet(name);
601
600
  log(`✓ Default wallet set to "${result.defaultWallet}"`);
602
- return result;
601
+ return;
603
602
  } catch (err) {
604
603
  log(`❌ ${err.message}`);
605
604
  exit(1);
@@ -620,7 +619,7 @@ export function buildWalletCommands(deps = {}) {
620
619
  if (result.newDefault) {
621
620
  log(` New default: ${result.newDefault}`);
622
621
  }
623
- return result;
622
+ return;
624
623
  } catch (err) {
625
624
  log(`❌ ${err.message}`);
626
625
  exit(1);
@@ -675,37 +674,32 @@ export function buildWalletCommands(deps = {}) {
675
674
  if (dryRun) {
676
675
  // Build the transaction but don't broadcast
677
676
  const result = await sendTokens(sendOpts);
678
- const output = {
679
- dryRun: true,
680
- from: result.from,
681
- to: options.to,
682
- amount: result.amount || (isMax ? 'max' : String(options.amount)),
683
- token: options.token || '(native)',
684
- chain: options.chain,
685
- ...(result.estimatedFee ? { estimatedFee: result.estimatedFee } : {}),
686
- };
687
- log(JSON.stringify(output, null, 2));
688
- return output;
677
+ log(`\nDry run transaction not broadcast\n`);
678
+ log(` From: ${result.from}`);
679
+ log(` To: ${options.to}`);
680
+ log(` Amount: ${result.amount || (isMax ? 'max' : String(options.amount))}`);
681
+ log(` Token: ${options.token || '(native)'}`);
682
+ log(` Chain: ${options.chain}`);
683
+ if (result.estimatedFee) log(` Fee: ${result.estimatedFee}`);
684
+ log('');
685
+ return;
689
686
  }
690
687
 
691
688
  const result = await sendTokens(sendOpts);
692
-
693
- const output = {
694
- success: true,
695
- transactionHash: result.transactionHash,
696
- confirmed: result.confirmed,
697
- ...(result.blockNumber ? { blockNumber: result.blockNumber } : {}),
698
- from: result.from,
699
- to: result.to,
700
- amount: result.amount,
701
- token: result.token,
702
- chain: result.chain,
703
- explorer: result.explorer,
704
- };
705
- log(JSON.stringify(output, null, 2));
706
- return output;
689
+ log(`\n✓ Transaction sent\n`);
690
+ log(` Tx Hash: ${result.transactionHash}`);
691
+ log(` Chain: ${result.chain}`);
692
+ log(` From: ${result.from}`);
693
+ log(` To: ${result.to}`);
694
+ log(` Amount: ${result.amount}`);
695
+ log(` Token: ${result.token}`);
696
+ if (result.blockNumber) log(` Block: ${result.blockNumber}`);
697
+ log(` Status: ${result.confirmed ? 'confirmed' : 'pending'}`);
698
+ log(` Explorer: ${result.explorer}`);
699
+ log('');
700
+ return;
707
701
  } catch (err) {
708
- log(JSON.stringify({ success: false, error: err.message }));
702
+ log(`Error: ${err.message}`);
709
703
  exit(1);
710
704
  }
711
705
  },
@@ -749,15 +743,14 @@ EXAMPLES:
749
743
  nansen wallet send --to 0x742d35Cc... --amount 1.5 --chain evm
750
744
  nansen wallet send --to 9WzDXw... --amount 0.1 --chain solana --token So11...
751
745
  `);
752
- return {
753
- commands: ['create', 'list', 'show', 'export', 'default', 'delete', 'send'],
754
- description: 'Local wallet management for EVM and Solana',
755
- };
746
+ return;
756
747
  },
757
748
  };
758
749
 
759
750
  if (!handlers[subcommand]) {
760
- return { error: `Unknown subcommand: ${subcommand}`, available: Object.keys(handlers) };
751
+ log(`Unknown subcommand: ${subcommand}. Available: ${Object.keys(handlers).join(', ')}`);
752
+ exit(1);
753
+ return;
761
754
  }
762
755
 
763
756
  return handlers[subcommand]();