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 +12 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/api.js +5 -3
- package/src/cli.js +23 -9
- package/src/index.js +4 -4
- package/src/schema.json +169 -1
- package/src/trading.js +111 -111
- package/src/wallet.js +32 -39
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
|
[](https://www.npmjs.com/package/nansen-cli)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
> **Built by agents, for agents.** Command-line interface for the [Nansen API](https://docs.nansen.ai)
|
|
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
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.
|
|
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.
|
|
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
|
-
|
|
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} —
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
5
|
-
*
|
|
4
|
+
* Designed for AI agents.
|
|
5
|
+
*
|
|
6
6
|
* Usage: nansen <command> [options]
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
848
|
-
|
|
847
|
+
log(`\nFetching quote on ${chainConfig.name}...`);
|
|
848
|
+
log(` Wallet: ${walletAddress}`);
|
|
849
849
|
|
|
850
850
|
const fromWarning = getWrappedNativeFromWarning(from, chain);
|
|
851
|
-
if (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
|
-
|
|
868
|
+
log('No quotes available');
|
|
869
869
|
if (response.warnings?.length) {
|
|
870
|
-
response.warnings.forEach(w =>
|
|
870
|
+
response.warnings.forEach(w => log(` Warning: ${w}`));
|
|
871
871
|
}
|
|
872
872
|
exit(1);
|
|
873
873
|
return;
|
|
874
874
|
}
|
|
875
875
|
|
|
876
|
-
|
|
877
|
-
response.quotes.forEach((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
|
-
|
|
881
|
-
|
|
880
|
+
log(`\n Quote ID: ${quoteId}`);
|
|
881
|
+
log(` Execute: nansen trade execute --quote ${quoteId}`);
|
|
882
882
|
if (response.quotes.length > 1) {
|
|
883
|
-
|
|
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
|
-
|
|
888
|
-
|
|
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
|
-
|
|
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
|
-
|
|
900
|
-
if (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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
948
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1012
|
+
log(`\nExecuting trade on ${chainConfig.name}...`);
|
|
1013
1013
|
if (endIndex - startIndex > 1) {
|
|
1014
|
-
|
|
1014
|
+
log(` Trying quote ${qi + 1}/${allQuotes.length} (${quoteName})...`);
|
|
1015
1015
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1045
|
-
if (qi + 1 < endIndex)
|
|
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
|
-
|
|
1052
|
-
if (qi + 1 < endIndex)
|
|
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
|
-
|
|
1066
|
+
log(` ✓ Sufficient allowance exists for ${quoteName}, skipping approval`);
|
|
1067
1067
|
} else {
|
|
1068
|
-
|
|
1069
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1091
|
+
log(` Waiting for approval confirmation...`);
|
|
1092
1092
|
const receipt = await waitForReceipt(chain, approvalTxHash);
|
|
1093
|
-
|
|
1093
|
+
log(` ✓ Approval confirmed in block ${parseInt(receipt.blockNumber, 16)}: ${approvalTxHash}`);
|
|
1094
1094
|
}
|
|
1095
1095
|
} catch (approvalErr) {
|
|
1096
|
-
|
|
1097
|
-
if (qi + 1 < endIndex)
|
|
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
|
-
|
|
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
|
-
|
|
1117
|
-
if (qi + 1 < endIndex)
|
|
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
|
-
|
|
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
|
-
|
|
1142
|
-
if (qi + 1 < endIndex)
|
|
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
|
-
|
|
1149
|
+
log(' Verifying on-chain status...');
|
|
1150
1150
|
try {
|
|
1151
1151
|
await waitForReceipt(chain, wcResult.txHash);
|
|
1152
1152
|
} catch (receiptErr) {
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
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
|
-
|
|
1195
|
-
if (qi + 1 < endIndex)
|
|
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
|
-
|
|
1202
|
-
if (qi + 1 < endIndex)
|
|
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
|
-
|
|
1216
|
+
log(` ✓ Sufficient allowance exists for ${quoteName}, skipping approval`);
|
|
1217
1217
|
} else {
|
|
1218
|
-
|
|
1219
|
-
|
|
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
|
-
|
|
1240
|
-
if (qi + 1 < endIndex)
|
|
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
|
-
|
|
1245
|
+
log(` Waiting for approval confirmation...`);
|
|
1246
1246
|
try {
|
|
1247
1247
|
const receipt = await waitForReceipt(chain, approvalResult.txHash);
|
|
1248
|
-
|
|
1248
|
+
log(` ✓ Approval confirmed in block ${parseInt(receipt.blockNumber, 16)}: ${approvalResult.txHash}`);
|
|
1249
1249
|
} catch (receiptErr) {
|
|
1250
|
-
|
|
1251
|
-
if (qi + 1 < endIndex)
|
|
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
|
-
|
|
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
|
-
|
|
1274
|
-
if (qi + 1 < endIndex)
|
|
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
|
-
|
|
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
|
-
|
|
1293
|
+
log(' Fetching nonce...');
|
|
1294
1294
|
await new Promise(r => setTimeout(r, 1000));
|
|
1295
1295
|
const nonce = await getEvmNonce(chain, walletAddress);
|
|
1296
|
-
|
|
1296
|
+
log(` Nonce: ${nonce}`);
|
|
1297
1297
|
|
|
1298
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1323
|
+
log(' Verifying on-chain status...');
|
|
1324
1324
|
try {
|
|
1325
1325
|
await waitForReceipt(chain, result.txHash);
|
|
1326
1326
|
} catch (receiptErr) {
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
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
|
-
|
|
1332
|
+
log(` Trying next quote...`);
|
|
1333
1333
|
lastQuoteError = `${quoteName} reverted on-chain`;
|
|
1334
1334
|
continue;
|
|
1335
1335
|
}
|
|
1336
|
-
|
|
1337
|
-
|
|
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
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
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
|
-
|
|
1351
|
+
log(` Swaps:`);
|
|
1352
1352
|
result.swapEvents.forEach(e => {
|
|
1353
|
-
|
|
1353
|
+
log(` ${e.inputAmount} ${e.inputMint?.slice(0, 8)}... → ${e.outputAmount} ${e.outputMint?.slice(0, 8)}...`);
|
|
1354
1354
|
});
|
|
1355
1355
|
}
|
|
1356
|
-
|
|
1356
|
+
log('');
|
|
1357
1357
|
return undefined; // Success — done
|
|
1358
1358
|
} else {
|
|
1359
|
-
|
|
1360
|
-
if (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)
|
|
1362
|
+
if (qi + 1 < endIndex) log(` Trying next quote...`);
|
|
1363
1363
|
}
|
|
1364
1364
|
|
|
1365
1365
|
} catch (quoteErr) {
|
|
1366
|
-
|
|
1366
|
+
log(` ❌ Quote ${quoteName} failed: ${quoteErr.message}`);
|
|
1367
1367
|
lastQuoteError = `${quoteName}: ${quoteErr.message}`;
|
|
1368
|
-
if (qi + 1 < endIndex)
|
|
1368
|
+
if (qi + 1 < endIndex) log(` Trying next quote...`);
|
|
1369
1369
|
}
|
|
1370
1370
|
}
|
|
1371
1371
|
|
|
1372
1372
|
// All quotes exhausted
|
|
1373
|
-
|
|
1374
|
-
|
|
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
|
-
|
|
1380
|
-
if (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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
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
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
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(
|
|
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
|
-
|
|
751
|
+
log(`Unknown subcommand: ${subcommand}. Available: ${Object.keys(handlers).join(', ')}`);
|
|
752
|
+
exit(1);
|
|
753
|
+
return;
|
|
761
754
|
}
|
|
762
755
|
|
|
763
756
|
return handlers[subcommand]();
|