nansen-cli 1.7.0 → 1.9.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/AGENTS.md +118 -164
- package/CLAUDE.md +16 -19
- package/README.md +163 -112
- package/SKILL.md +170 -76
- package/package.json +3 -1
- package/scripts/check-changeset.js +28 -0
- package/src/api.js +92 -70
- package/src/chain-ids.js +19 -0
- package/src/cli.js +406 -353
- package/src/ens.js +163 -0
- package/src/trading.js +324 -25
- package/src/transfer.js +133 -2
- package/src/update-check.js +35 -0
- package/src/wallet.js +11 -9
- package/src/walletconnect-exec.js +22 -0
- package/src/walletconnect-trading.js +91 -0
- package/src/walletconnect-x402.js +215 -0
- package/vitest.e2e.config.js +10 -0
package/src/api.js
CHANGED
|
@@ -391,6 +391,17 @@ function parseRetryAfter(headerValue) {
|
|
|
391
391
|
return null;
|
|
392
392
|
}
|
|
393
393
|
|
|
394
|
+
/**
|
|
395
|
+
* Build a date range from today back N days
|
|
396
|
+
* @param {number} days - Number of days back from today
|
|
397
|
+
* @returns {{from: string, to: string}} Date range with YYYY-MM-DD strings
|
|
398
|
+
*/
|
|
399
|
+
export function buildDateRange(days) {
|
|
400
|
+
const to = new Date().toISOString().split('T')[0];
|
|
401
|
+
const from = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
402
|
+
return { from, to };
|
|
403
|
+
}
|
|
404
|
+
|
|
394
405
|
export class NansenAPI {
|
|
395
406
|
constructor(apiKey = config.apiKey, baseUrl = config.baseUrl, options = {}) {
|
|
396
407
|
this.apiKey = apiKey || null;
|
|
@@ -494,8 +505,11 @@ export class NansenAPI {
|
|
|
494
505
|
} else if (code === ErrorCode.CREDITS_EXHAUSTED) {
|
|
495
506
|
message = message.replace(/\.+$/, '') + '. No retry will help. Check your Nansen dashboard for credit balance.';
|
|
496
507
|
} else if (code === ErrorCode.PAYMENT_REQUIRED) {
|
|
497
|
-
// Try x402 auto-payment with fallback
|
|
498
|
-
|
|
508
|
+
// Try x402 auto-payment: local wallet (with network fallback), then WalletConnect
|
|
509
|
+
const hasManualSignature = !!(this.defaultHeaders['Payment-Signature'] || options.headers?.['Payment-Signature']);
|
|
510
|
+
|
|
511
|
+
if (!hasManualSignature) {
|
|
512
|
+
// 1. Try local wallet with fallback across payment networks
|
|
499
513
|
try {
|
|
500
514
|
const { createPaymentSignatures } = await import('./x402.js');
|
|
501
515
|
for await (const { signature, network } of createPaymentSignatures(response, url)) {
|
|
@@ -526,17 +540,54 @@ export class NansenAPI {
|
|
|
526
540
|
}
|
|
527
541
|
// This payment option was rejected, try next
|
|
528
542
|
}
|
|
529
|
-
} catch { /*
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
543
|
+
} catch { /* local wallet unavailable, try WalletConnect */ }
|
|
544
|
+
|
|
545
|
+
// 2. Fall back to WalletConnect (walletconnect-x402.js)
|
|
546
|
+
// (local wallet returns early on success above, so we always reach here if it failed)
|
|
547
|
+
{
|
|
548
|
+
let paymentRequirements;
|
|
549
|
+
const paymentHeader = response.headers.get('payment-required');
|
|
550
|
+
if (paymentHeader) {
|
|
551
|
+
try {
|
|
552
|
+
paymentRequirements = JSON.parse(atob(paymentHeader));
|
|
553
|
+
} catch {
|
|
554
|
+
data.paymentRequiredRaw = paymentHeader;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (!paymentRequirements && data.paymentRequirements) {
|
|
558
|
+
paymentRequirements = data.paymentRequirements;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (paymentRequirements) {
|
|
562
|
+
try {
|
|
563
|
+
const { handleX402Payment } = await import('./walletconnect-x402.js');
|
|
564
|
+
const paymentSignature = await handleX402Payment(paymentRequirements);
|
|
565
|
+
const paidResponse = await fetch(url, {
|
|
566
|
+
method: 'POST',
|
|
567
|
+
headers: {
|
|
568
|
+
'Content-Type': 'application/json',
|
|
569
|
+
'X-Client-Type': 'nansen-cli',
|
|
570
|
+
'X-Client-Version': packageVersion,
|
|
571
|
+
'Payment-Signature': paymentSignature,
|
|
572
|
+
...this.defaultHeaders,
|
|
573
|
+
...options.headers,
|
|
574
|
+
},
|
|
575
|
+
body: JSON.stringify(NansenAPI.cleanBody(body)),
|
|
576
|
+
});
|
|
577
|
+
if (paidResponse.ok) {
|
|
578
|
+
return await paidResponse.json();
|
|
579
|
+
}
|
|
580
|
+
} catch (x402Err) {
|
|
581
|
+
message = `x402 auto-payment failed: ${x402Err.message}`;
|
|
582
|
+
}
|
|
583
|
+
data.paymentRequirements = paymentRequirements;
|
|
584
|
+
}
|
|
538
585
|
}
|
|
539
586
|
}
|
|
587
|
+
|
|
588
|
+
if (!message || message === data.message) {
|
|
589
|
+
message = 'Payment required (x402). Sign the paymentRequirements below per https://docs.x402.org and pass the result with --x402-payment-signature <value>.';
|
|
590
|
+
}
|
|
540
591
|
}
|
|
541
592
|
|
|
542
593
|
lastError = new NansenError(message, code, response.status, {
|
|
@@ -624,11 +675,9 @@ export class NansenAPI {
|
|
|
624
675
|
|
|
625
676
|
async smartMoneyHistoricalHoldings(params = {}) {
|
|
626
677
|
const { chains = ['solana'], filters = {}, orderBy, pagination, days = 30 } = params;
|
|
627
|
-
const to = new Date().toISOString().split('T')[0];
|
|
628
|
-
const from = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
629
678
|
return this.request('/api/v1/smart-money/historical-holdings', {
|
|
630
679
|
chains,
|
|
631
|
-
date_range:
|
|
680
|
+
date_range: buildDateRange(days),
|
|
632
681
|
filters,
|
|
633
682
|
order_by: orderBy,
|
|
634
683
|
pagination
|
|
@@ -671,11 +720,7 @@ export class NansenAPI {
|
|
|
671
720
|
const validation = validateAddress(address, chain);
|
|
672
721
|
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
673
722
|
}
|
|
674
|
-
const dateRange = date || (
|
|
675
|
-
const to = new Date().toISOString().split('T')[0];
|
|
676
|
-
const from = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
677
|
-
return { from, to };
|
|
678
|
-
})();
|
|
723
|
+
const dateRange = date || buildDateRange(days);
|
|
679
724
|
return this.request('/api/v1/profiler/address/transactions', {
|
|
680
725
|
address,
|
|
681
726
|
chain,
|
|
@@ -692,13 +737,7 @@ export class NansenAPI {
|
|
|
692
737
|
const validation = validateAddress(address, chain);
|
|
693
738
|
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
694
739
|
}
|
|
695
|
-
|
|
696
|
-
let dateRange = date;
|
|
697
|
-
if (!dateRange) {
|
|
698
|
-
const to = new Date().toISOString().split('T')[0];
|
|
699
|
-
const from = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
700
|
-
dateRange = { from, to };
|
|
701
|
-
}
|
|
740
|
+
const dateRange = date || buildDateRange(days);
|
|
702
741
|
return this.request('/api/v1/profiler/address/pnl', {
|
|
703
742
|
address,
|
|
704
743
|
chain,
|
|
@@ -734,12 +773,10 @@ export class NansenAPI {
|
|
|
734
773
|
const validation = validateAddress(address, chain);
|
|
735
774
|
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
736
775
|
}
|
|
737
|
-
const to = new Date().toISOString().split('T')[0];
|
|
738
|
-
const from = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
739
776
|
return this.request('/api/v1/profiler/address/historical-balances', {
|
|
740
777
|
address,
|
|
741
778
|
chain,
|
|
742
|
-
date:
|
|
779
|
+
date: buildDateRange(days),
|
|
743
780
|
filters,
|
|
744
781
|
order_by: orderBy,
|
|
745
782
|
pagination
|
|
@@ -766,12 +803,10 @@ export class NansenAPI {
|
|
|
766
803
|
const validation = validateAddress(address, chain);
|
|
767
804
|
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
768
805
|
}
|
|
769
|
-
const to = new Date().toISOString().split('T')[0];
|
|
770
|
-
const from = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
771
806
|
return this.request('/api/v1/profiler/address/counterparties', {
|
|
772
807
|
address,
|
|
773
808
|
chain,
|
|
774
|
-
date:
|
|
809
|
+
date: buildDateRange(days),
|
|
775
810
|
filters,
|
|
776
811
|
order_by: orderBy,
|
|
777
812
|
pagination
|
|
@@ -784,12 +819,10 @@ export class NansenAPI {
|
|
|
784
819
|
const validation = validateAddress(address, chain);
|
|
785
820
|
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
786
821
|
}
|
|
787
|
-
const to = new Date().toISOString().split('T')[0];
|
|
788
|
-
const from = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
789
822
|
return this.request('/api/v1/profiler/address/pnl-summary', {
|
|
790
823
|
address,
|
|
791
824
|
chain,
|
|
792
|
-
date:
|
|
825
|
+
date: buildDateRange(days),
|
|
793
826
|
order_by: orderBy,
|
|
794
827
|
pagination
|
|
795
828
|
});
|
|
@@ -808,11 +841,9 @@ export class NansenAPI {
|
|
|
808
841
|
|
|
809
842
|
async addressPerpTrades(params = {}) {
|
|
810
843
|
const { address, filters = {}, orderBy, pagination, days = 30 } = params;
|
|
811
|
-
const to = new Date().toISOString().split('T')[0];
|
|
812
|
-
const from = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
813
844
|
return this.request('/api/v1/profiler/perp-trades', {
|
|
814
845
|
address,
|
|
815
|
-
date:
|
|
846
|
+
date: buildDateRange(days),
|
|
816
847
|
filters,
|
|
817
848
|
order_by: orderBy,
|
|
818
849
|
pagination
|
|
@@ -854,11 +885,7 @@ export class NansenAPI {
|
|
|
854
885
|
const validation = validateTokenAddress(tokenAddress, chain);
|
|
855
886
|
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
856
887
|
}
|
|
857
|
-
const dateRange = date || (
|
|
858
|
-
const to = new Date().toISOString().split('T')[0];
|
|
859
|
-
const from = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
860
|
-
return { from, to };
|
|
861
|
-
})();
|
|
888
|
+
const dateRange = date || buildDateRange(days);
|
|
862
889
|
return this.request('/api/v1/tgm/flows', {
|
|
863
890
|
token_address: tokenAddress,
|
|
864
891
|
chain,
|
|
@@ -875,9 +902,6 @@ export class NansenAPI {
|
|
|
875
902
|
const validation = validateTokenAddress(tokenAddress, chain);
|
|
876
903
|
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
877
904
|
}
|
|
878
|
-
const to = new Date().toISOString().split('T')[0];
|
|
879
|
-
const from = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
880
|
-
|
|
881
905
|
// Apply smart money filter via filters object
|
|
882
906
|
if (onlySmartMoney) {
|
|
883
907
|
filters.include_smart_money_labels = filters.include_smart_money_labels ||
|
|
@@ -887,7 +911,7 @@ export class NansenAPI {
|
|
|
887
911
|
return this.request('/api/v1/tgm/dex-trades', {
|
|
888
912
|
token_address: tokenAddress,
|
|
889
913
|
chain,
|
|
890
|
-
date:
|
|
914
|
+
date: buildDateRange(days),
|
|
891
915
|
filters,
|
|
892
916
|
order_by: orderBy,
|
|
893
917
|
pagination
|
|
@@ -900,12 +924,10 @@ export class NansenAPI {
|
|
|
900
924
|
const validation = validateTokenAddress(tokenAddress, chain);
|
|
901
925
|
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
902
926
|
}
|
|
903
|
-
const to = new Date().toISOString().split('T')[0];
|
|
904
|
-
const from = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
905
927
|
return this.request('/api/v1/tgm/pnl-leaderboard', {
|
|
906
928
|
token_address: tokenAddress,
|
|
907
929
|
chain,
|
|
908
|
-
date:
|
|
930
|
+
date: buildDateRange(days),
|
|
909
931
|
filters,
|
|
910
932
|
order_by: orderBy,
|
|
911
933
|
pagination
|
|
@@ -918,11 +940,7 @@ export class NansenAPI {
|
|
|
918
940
|
const validation = validateTokenAddress(tokenAddress, chain);
|
|
919
941
|
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
920
942
|
}
|
|
921
|
-
const dateRange = date || (
|
|
922
|
-
const to = new Date().toISOString().split('T')[0];
|
|
923
|
-
const from = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
924
|
-
return { from, to };
|
|
925
|
-
})();
|
|
943
|
+
const dateRange = date || buildDateRange(days);
|
|
926
944
|
return this.request('/api/v1/tgm/who-bought-sold', {
|
|
927
945
|
token_address: tokenAddress,
|
|
928
946
|
chain,
|
|
@@ -951,12 +969,10 @@ export class NansenAPI {
|
|
|
951
969
|
const validation = validateTokenAddress(tokenAddress, chain);
|
|
952
970
|
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
953
971
|
}
|
|
954
|
-
const to = new Date().toISOString().split('T')[0];
|
|
955
|
-
const from = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
956
972
|
return this.request('/api/v1/tgm/transfers', {
|
|
957
973
|
token_address: tokenAddress,
|
|
958
974
|
chain,
|
|
959
|
-
date:
|
|
975
|
+
date: buildDateRange(days),
|
|
960
976
|
filters,
|
|
961
977
|
order_by: orderBy,
|
|
962
978
|
pagination
|
|
@@ -980,11 +996,9 @@ export class NansenAPI {
|
|
|
980
996
|
|
|
981
997
|
async tokenPerpTrades(params = {}) {
|
|
982
998
|
const { tokenSymbol, filters = {}, orderBy, pagination, days = 30 } = params;
|
|
983
|
-
const to = new Date().toISOString().split('T')[0];
|
|
984
|
-
const from = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
985
999
|
return this.request('/api/v1/tgm/perp-trades', {
|
|
986
1000
|
token_symbol: tokenSymbol,
|
|
987
|
-
date:
|
|
1001
|
+
date: buildDateRange(days),
|
|
988
1002
|
filters,
|
|
989
1003
|
order_by: orderBy,
|
|
990
1004
|
pagination
|
|
@@ -1003,11 +1017,9 @@ export class NansenAPI {
|
|
|
1003
1017
|
|
|
1004
1018
|
async tokenPerpPnlLeaderboard(params = {}) {
|
|
1005
1019
|
const { tokenSymbol, filters = {}, orderBy, pagination, days = 30 } = params;
|
|
1006
|
-
const to = new Date().toISOString().split('T')[0];
|
|
1007
|
-
const from = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
1008
1020
|
return this.request('/api/v1/tgm/perp-pnl-leaderboard', {
|
|
1009
1021
|
token_symbol: tokenSymbol,
|
|
1010
|
-
date:
|
|
1022
|
+
date: buildDateRange(days),
|
|
1011
1023
|
filters,
|
|
1012
1024
|
order_by: orderBy,
|
|
1013
1025
|
pagination
|
|
@@ -1026,6 +1038,20 @@ export class NansenAPI {
|
|
|
1026
1038
|
});
|
|
1027
1039
|
}
|
|
1028
1040
|
|
|
1041
|
+
async tokenOhlcv(params = {}) {
|
|
1042
|
+
const { tokenAddress, chain = 'solana', timeframe, pagination } = params;
|
|
1043
|
+
if (tokenAddress) {
|
|
1044
|
+
const validation = validateTokenAddress(tokenAddress, chain);
|
|
1045
|
+
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
1046
|
+
}
|
|
1047
|
+
return this.request('/api/v1/tgm/token-ohlcv', {
|
|
1048
|
+
token_address: tokenAddress,
|
|
1049
|
+
chain,
|
|
1050
|
+
timeframe,
|
|
1051
|
+
pagination
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1029
1055
|
async tokenInformation(params = {}) {
|
|
1030
1056
|
const { tokenAddress, chain = 'solana', timeframe = '1d' } = params;
|
|
1031
1057
|
if (tokenAddress) {
|
|
@@ -1043,10 +1069,8 @@ export class NansenAPI {
|
|
|
1043
1069
|
|
|
1044
1070
|
async perpScreener(params = {}) {
|
|
1045
1071
|
const { filters = {}, orderBy, pagination, days = 30 } = params;
|
|
1046
|
-
const to = new Date().toISOString().split('T')[0];
|
|
1047
|
-
const from = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
1048
1072
|
return this.request('/api/v1/perp-screener', {
|
|
1049
|
-
date:
|
|
1073
|
+
date: buildDateRange(days),
|
|
1050
1074
|
filters,
|
|
1051
1075
|
order_by: orderBy,
|
|
1052
1076
|
pagination
|
|
@@ -1055,10 +1079,8 @@ export class NansenAPI {
|
|
|
1055
1079
|
|
|
1056
1080
|
async perpLeaderboard(params = {}) {
|
|
1057
1081
|
const { filters = {}, orderBy, pagination, days = 30 } = params;
|
|
1058
|
-
const to = new Date().toISOString().split('T')[0];
|
|
1059
|
-
const from = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
1060
1082
|
return this.request('/api/v1/perp-leaderboard', {
|
|
1061
|
-
date:
|
|
1083
|
+
date: buildDateRange(days),
|
|
1062
1084
|
filters,
|
|
1063
1085
|
order_by: orderBy,
|
|
1064
1086
|
pagination
|
package/src/chain-ids.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical EVM chain name → numeric chain ID mapping.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth — import from here instead of defining inline.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const EVM_CHAIN_IDS = {
|
|
8
|
+
ethereum: 1,
|
|
9
|
+
base: 8453,
|
|
10
|
+
optimism: 10,
|
|
11
|
+
arbitrum: 42161,
|
|
12
|
+
polygon: 137,
|
|
13
|
+
avalanche: 43114,
|
|
14
|
+
bnb: 56,
|
|
15
|
+
linea: 59144,
|
|
16
|
+
scroll: 534352,
|
|
17
|
+
zksync: 324,
|
|
18
|
+
mantle: 5000,
|
|
19
|
+
};
|