nansen-cli 1.30.1 → 1.30.2
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 +6 -0
- package/package.json +1 -1
- package/src/api.js +37 -80
- package/src/trading.js +21 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.30.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#431](https://github.com/nansen-ai/nansen-cli/pull/431) [`c2c033b`](https://github.com/nansen-ai/nansen-cli/commit/c2c033b86f9dab59df9497ce971ef6f267ee3669) Thanks [@MarcLlopart](https://github.com/MarcLlopart)! - Pass backend quoteId in execute requests for BI correlation
|
|
8
|
+
|
|
3
9
|
## 1.30.1
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -336,6 +336,22 @@ export function validateTokenAddress(tokenAddress, chain = 'solana') {
|
|
|
336
336
|
return validateAddress(tokenAddress, chain);
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
/**
|
|
340
|
+
* Throw if address is present but invalid.
|
|
341
|
+
*/
|
|
342
|
+
function requireValidAddress(address, chain) {
|
|
343
|
+
const v = validateAddress(address, chain);
|
|
344
|
+
if (!v.valid) throw new NansenError(v.error, v.code);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Throw if token address is present but invalid.
|
|
349
|
+
*/
|
|
350
|
+
function requireValidToken(tokenAddress, chain) {
|
|
351
|
+
const v = validateTokenAddress(tokenAddress, chain);
|
|
352
|
+
if (!v.valid) throw new NansenError(v.error, v.code);
|
|
353
|
+
}
|
|
354
|
+
|
|
339
355
|
function loadConfig() {
|
|
340
356
|
// Base config from files, then env vars override individual fields
|
|
341
357
|
let config = null;
|
|
@@ -791,10 +807,7 @@ export class NansenAPI {
|
|
|
791
807
|
|
|
792
808
|
async addressBalance(params = {}) {
|
|
793
809
|
const { address, entityName, chain = 'all', hideSpamToken = true, filters = {}, orderBy } = params;
|
|
794
|
-
if (address)
|
|
795
|
-
const validation = validateAddress(address, chain);
|
|
796
|
-
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
797
|
-
}
|
|
810
|
+
if (address) requireValidAddress(address, chain);
|
|
798
811
|
return this.request('/api/v1/profiler/address/current-balance', {
|
|
799
812
|
address,
|
|
800
813
|
entity_name: entityName,
|
|
@@ -807,10 +820,7 @@ export class NansenAPI {
|
|
|
807
820
|
|
|
808
821
|
async addressLabels(params = {}) {
|
|
809
822
|
const { address, chain = 'ethereum', pagination = { page: 1, per_page: 100 } } = params;
|
|
810
|
-
if (address)
|
|
811
|
-
const validation = validateAddress(address, chain);
|
|
812
|
-
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
813
|
-
}
|
|
823
|
+
if (address) requireValidAddress(address, chain);
|
|
814
824
|
return this.request('/api/beta/profiler/address/labels', {
|
|
815
825
|
parameters: { address, chain },
|
|
816
826
|
pagination
|
|
@@ -819,10 +829,7 @@ export class NansenAPI {
|
|
|
819
829
|
|
|
820
830
|
async addressTransactions(params = {}) {
|
|
821
831
|
const { address, chain = 'ethereum', filters = {}, orderBy, pagination, days = 30, date } = params;
|
|
822
|
-
if (address)
|
|
823
|
-
const validation = validateAddress(address, chain);
|
|
824
|
-
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
825
|
-
}
|
|
832
|
+
if (address) requireValidAddress(address, chain);
|
|
826
833
|
const dateRange = date || buildDateRange(days);
|
|
827
834
|
return this.request('/api/v1/profiler/address/transactions', {
|
|
828
835
|
address,
|
|
@@ -836,10 +843,7 @@ export class NansenAPI {
|
|
|
836
843
|
|
|
837
844
|
async addressPnl(params = {}) {
|
|
838
845
|
const { address, chain = 'ethereum', date, days = 30, filters = {}, orderBy, pagination } = params;
|
|
839
|
-
if (address)
|
|
840
|
-
const validation = validateAddress(address, chain);
|
|
841
|
-
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
842
|
-
}
|
|
846
|
+
if (address) requireValidAddress(address, chain);
|
|
843
847
|
const dateRange = date || buildDateRange(days);
|
|
844
848
|
return this.request('/api/v1/profiler/address/pnl', {
|
|
845
849
|
address,
|
|
@@ -899,10 +903,7 @@ export class NansenAPI {
|
|
|
899
903
|
|
|
900
904
|
async addressHistoricalBalances(params = {}) {
|
|
901
905
|
const { address, chain = 'ethereum', filters = {}, orderBy, pagination, days = 30 } = params;
|
|
902
|
-
if (address)
|
|
903
|
-
const validation = validateAddress(address, chain);
|
|
904
|
-
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
905
|
-
}
|
|
906
|
+
if (address) requireValidAddress(address, chain);
|
|
906
907
|
return this.request('/api/v1/profiler/address/historical-balances', {
|
|
907
908
|
address,
|
|
908
909
|
chain,
|
|
@@ -915,10 +916,7 @@ export class NansenAPI {
|
|
|
915
916
|
|
|
916
917
|
async addressRelatedWallets(params = {}) {
|
|
917
918
|
const { address, chain = 'ethereum', orderBy, pagination } = params;
|
|
918
|
-
if (address)
|
|
919
|
-
const validation = validateAddress(address, chain);
|
|
920
|
-
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
921
|
-
}
|
|
919
|
+
if (address) requireValidAddress(address, chain);
|
|
922
920
|
return this.request('/api/v1/profiler/address/related-wallets', {
|
|
923
921
|
address,
|
|
924
922
|
chain,
|
|
@@ -929,10 +927,7 @@ export class NansenAPI {
|
|
|
929
927
|
|
|
930
928
|
async addressCounterparties(params = {}) {
|
|
931
929
|
const { address, chain = 'ethereum', filters = {}, orderBy, pagination, days = 30 } = params;
|
|
932
|
-
if (address)
|
|
933
|
-
const validation = validateAddress(address, chain);
|
|
934
|
-
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
935
|
-
}
|
|
930
|
+
if (address) requireValidAddress(address, chain);
|
|
936
931
|
return this.request('/api/v1/profiler/address/counterparties', {
|
|
937
932
|
address,
|
|
938
933
|
chain,
|
|
@@ -947,10 +942,7 @@ export class NansenAPI {
|
|
|
947
942
|
// Note: pnl-summary endpoint is non-paginated (returns aggregate stats, not a list).
|
|
948
943
|
// Pagination param intentionally omitted from this request.
|
|
949
944
|
const { address, chain = 'ethereum', orderBy, days = 30 } = params;
|
|
950
|
-
if (address)
|
|
951
|
-
const validation = validateAddress(address, chain);
|
|
952
|
-
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
953
|
-
}
|
|
945
|
+
if (address) requireValidAddress(address, chain);
|
|
954
946
|
return this.request('/api/v1/profiler/address/pnl-summary', {
|
|
955
947
|
address,
|
|
956
948
|
chain,
|
|
@@ -996,10 +988,7 @@ export class NansenAPI {
|
|
|
996
988
|
|
|
997
989
|
async tokenHolders(params = {}) {
|
|
998
990
|
const { tokenAddress, chain = 'solana', labelType = 'all_holders', filters = {}, orderBy, pagination, withLabels } = params;
|
|
999
|
-
if (tokenAddress)
|
|
1000
|
-
const validation = validateTokenAddress(tokenAddress, chain);
|
|
1001
|
-
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
1002
|
-
}
|
|
991
|
+
if (tokenAddress) requireValidToken(tokenAddress, chain);
|
|
1003
992
|
const body = {
|
|
1004
993
|
token_address: tokenAddress,
|
|
1005
994
|
chain,
|
|
@@ -1014,10 +1003,7 @@ export class NansenAPI {
|
|
|
1014
1003
|
|
|
1015
1004
|
async tokenFlows(params = {}) {
|
|
1016
1005
|
const { tokenAddress, chain = 'solana', label, filters = {}, orderBy, pagination, days = 30, date } = params;
|
|
1017
|
-
if (tokenAddress)
|
|
1018
|
-
const validation = validateTokenAddress(tokenAddress, chain);
|
|
1019
|
-
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
1020
|
-
}
|
|
1006
|
+
if (tokenAddress) requireValidToken(tokenAddress, chain);
|
|
1021
1007
|
const dateRange = date || buildDateRange(days);
|
|
1022
1008
|
return this.request('/api/v1/tgm/flows', {
|
|
1023
1009
|
token_address: tokenAddress,
|
|
@@ -1032,10 +1018,7 @@ export class NansenAPI {
|
|
|
1032
1018
|
|
|
1033
1019
|
async tokenDexTrades(params = {}) {
|
|
1034
1020
|
const { tokenAddress, chain = 'solana', onlySmartMoney = false, filters = {}, orderBy, pagination, days = 7 } = params;
|
|
1035
|
-
if (tokenAddress)
|
|
1036
|
-
const validation = validateTokenAddress(tokenAddress, chain);
|
|
1037
|
-
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
1038
|
-
}
|
|
1021
|
+
if (tokenAddress) requireValidToken(tokenAddress, chain);
|
|
1039
1022
|
// Apply smart money filter via filters object
|
|
1040
1023
|
if (onlySmartMoney) {
|
|
1041
1024
|
filters.include_smart_money_labels = filters.include_smart_money_labels ||
|
|
@@ -1054,10 +1037,7 @@ export class NansenAPI {
|
|
|
1054
1037
|
|
|
1055
1038
|
async tokenPnlLeaderboard(params = {}) {
|
|
1056
1039
|
const { tokenAddress, chain = 'solana', filters = {}, orderBy, pagination, days = 30, withLabels } = params;
|
|
1057
|
-
if (tokenAddress)
|
|
1058
|
-
const validation = validateTokenAddress(tokenAddress, chain);
|
|
1059
|
-
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
1060
|
-
}
|
|
1040
|
+
if (tokenAddress) requireValidToken(tokenAddress, chain);
|
|
1061
1041
|
const body = {
|
|
1062
1042
|
token_address: tokenAddress,
|
|
1063
1043
|
chain,
|
|
@@ -1072,10 +1052,7 @@ export class NansenAPI {
|
|
|
1072
1052
|
|
|
1073
1053
|
async tokenWhoBoughtSold(params = {}) {
|
|
1074
1054
|
const { tokenAddress, chain = 'solana', buyOrSell = 'BUY', filters = {}, orderBy, pagination, days = 30, date } = params;
|
|
1075
|
-
if (tokenAddress)
|
|
1076
|
-
const validation = validateTokenAddress(tokenAddress, chain);
|
|
1077
|
-
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
1078
|
-
}
|
|
1055
|
+
if (tokenAddress) requireValidToken(tokenAddress, chain);
|
|
1079
1056
|
const dateRange = date || buildDateRange(days);
|
|
1080
1057
|
return this.request('/api/v1/tgm/who-bought-sold', {
|
|
1081
1058
|
token_address: tokenAddress,
|
|
@@ -1090,10 +1067,7 @@ export class NansenAPI {
|
|
|
1090
1067
|
|
|
1091
1068
|
async tokenFlowIntelligence(params = {}) {
|
|
1092
1069
|
const { tokenAddress, chain = 'solana', timeframe = '1d' } = params;
|
|
1093
|
-
if (tokenAddress)
|
|
1094
|
-
const validation = validateTokenAddress(tokenAddress, chain);
|
|
1095
|
-
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
1096
|
-
}
|
|
1070
|
+
if (tokenAddress) requireValidToken(tokenAddress, chain);
|
|
1097
1071
|
return this.request('/api/v1/tgm/flow-intelligence', {
|
|
1098
1072
|
token_address: tokenAddress,
|
|
1099
1073
|
chain,
|
|
@@ -1103,10 +1077,7 @@ export class NansenAPI {
|
|
|
1103
1077
|
|
|
1104
1078
|
async tokenTransfers(params = {}) {
|
|
1105
1079
|
const { tokenAddress, chain = 'solana', filters = {}, orderBy, pagination, days = 7 } = params;
|
|
1106
|
-
if (tokenAddress)
|
|
1107
|
-
const validation = validateTokenAddress(tokenAddress, chain);
|
|
1108
|
-
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
1109
|
-
}
|
|
1080
|
+
if (tokenAddress) requireValidToken(tokenAddress, chain);
|
|
1110
1081
|
return this.request('/api/v1/tgm/transfers', {
|
|
1111
1082
|
token_address: tokenAddress,
|
|
1112
1083
|
chain,
|
|
@@ -1120,10 +1091,7 @@ export class NansenAPI {
|
|
|
1120
1091
|
async tokenJupDca(params = {}) {
|
|
1121
1092
|
const { tokenAddress, filters = {}, orderBy, pagination } = params;
|
|
1122
1093
|
// JUP DCA is Solana-only
|
|
1123
|
-
if (tokenAddress)
|
|
1124
|
-
const validation = validateTokenAddress(tokenAddress, 'solana');
|
|
1125
|
-
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
1126
|
-
}
|
|
1094
|
+
if (tokenAddress) requireValidToken(tokenAddress, 'solana');
|
|
1127
1095
|
return this.request('/api/v1/tgm/jup-dca', {
|
|
1128
1096
|
token_address: tokenAddress,
|
|
1129
1097
|
filters,
|
|
@@ -1168,10 +1136,7 @@ export class NansenAPI {
|
|
|
1168
1136
|
|
|
1169
1137
|
async tokenIndicators(params = {}) {
|
|
1170
1138
|
const { tokenAddress, chain = 'ethereum' } = params;
|
|
1171
|
-
if (tokenAddress)
|
|
1172
|
-
const validation = validateTokenAddress(tokenAddress, chain);
|
|
1173
|
-
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
1174
|
-
}
|
|
1139
|
+
if (tokenAddress) requireValidToken(tokenAddress, chain);
|
|
1175
1140
|
return this.request('/api/v1/tgm/indicators', {
|
|
1176
1141
|
token_address: tokenAddress,
|
|
1177
1142
|
chain
|
|
@@ -1180,10 +1145,7 @@ export class NansenAPI {
|
|
|
1180
1145
|
|
|
1181
1146
|
async tokenOhlcv(params = {}) {
|
|
1182
1147
|
const { tokenAddress, chain = 'solana', timeframe } = params;
|
|
1183
|
-
if (tokenAddress)
|
|
1184
|
-
const validation = validateTokenAddress(tokenAddress, chain);
|
|
1185
|
-
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
1186
|
-
}
|
|
1148
|
+
if (tokenAddress) requireValidToken(tokenAddress, chain);
|
|
1187
1149
|
return this.request('/api/v1/tgm/token-ohlcv', {
|
|
1188
1150
|
token_address: tokenAddress,
|
|
1189
1151
|
chain,
|
|
@@ -1193,10 +1155,7 @@ export class NansenAPI {
|
|
|
1193
1155
|
|
|
1194
1156
|
async tokenInformation(params = {}) {
|
|
1195
1157
|
const { tokenAddress, chain = 'solana', timeframe = '1d' } = params;
|
|
1196
|
-
if (tokenAddress)
|
|
1197
|
-
const validation = validateTokenAddress(tokenAddress, chain);
|
|
1198
|
-
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
1199
|
-
}
|
|
1158
|
+
if (tokenAddress) requireValidToken(tokenAddress, chain);
|
|
1200
1159
|
return this.request('/api/v1/tgm/token-information', {
|
|
1201
1160
|
token_address: tokenAddress,
|
|
1202
1161
|
chain,
|
|
@@ -1279,8 +1238,7 @@ export class NansenAPI {
|
|
|
1279
1238
|
async pmTradesByAddress(params = {}) {
|
|
1280
1239
|
const { address, orderBy, pagination } = params;
|
|
1281
1240
|
// Polymarket runs exclusively on Polygon
|
|
1282
|
-
|
|
1283
|
-
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
1241
|
+
requireValidAddress(address, 'polygon');
|
|
1284
1242
|
return this.request('/api/v1/prediction-market/trades-by-address', {
|
|
1285
1243
|
address,
|
|
1286
1244
|
order_by: orderBy,
|
|
@@ -1351,8 +1309,7 @@ export class NansenAPI {
|
|
|
1351
1309
|
async pmPnlByAddress(params = {}) {
|
|
1352
1310
|
const { address, orderBy, pagination } = params;
|
|
1353
1311
|
// Polymarket runs exclusively on Polygon
|
|
1354
|
-
|
|
1355
|
-
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
1312
|
+
requireValidAddress(address, 'polygon');
|
|
1356
1313
|
return this.request('/api/v1/prediction-market/pnl-by-address', {
|
|
1357
1314
|
address,
|
|
1358
1315
|
order_by: orderBy,
|
package/src/trading.js
CHANGED
|
@@ -104,6 +104,14 @@ function getQuotesDir() {
|
|
|
104
104
|
return path.join(configDir, 'quotes');
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
// Resolve a filename inside the quotes dir, rejecting path traversal.
|
|
108
|
+
function safeQuotesPath(filename) {
|
|
109
|
+
const base = path.resolve(getQuotesDir());
|
|
110
|
+
const target = path.resolve(base, filename);
|
|
111
|
+
if (path.relative(base, target).startsWith('..')) return null;
|
|
112
|
+
return target;
|
|
113
|
+
}
|
|
114
|
+
|
|
107
115
|
// ============= Trading API Client =============
|
|
108
116
|
|
|
109
117
|
/**
|
|
@@ -337,10 +345,12 @@ const TX_RECORD_TTL_MS = 30 * 24 * 3600 * 1000; // 30 days
|
|
|
337
345
|
*/
|
|
338
346
|
export function saveTxRecord(txHash, { aggregator, requestId, fromChain, toChain }) {
|
|
339
347
|
if (!txHash) return;
|
|
340
|
-
const
|
|
348
|
+
const filePath = safeQuotesPath(`tx-${txHash}.json`);
|
|
349
|
+
if (!filePath) return;
|
|
350
|
+
const dir = path.dirname(filePath);
|
|
341
351
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
342
352
|
const data = { txHash, aggregator, requestId, fromChain, toChain, timestamp: Date.now() };
|
|
343
|
-
fs.writeFileSync(
|
|
353
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
344
354
|
}
|
|
345
355
|
|
|
346
356
|
/**
|
|
@@ -348,7 +358,8 @@ export function saveTxRecord(txHash, { aggregator, requestId, fromChain, toChain
|
|
|
348
358
|
*/
|
|
349
359
|
export function loadTxRecord(txHash) {
|
|
350
360
|
if (!txHash) return null;
|
|
351
|
-
const filePath =
|
|
361
|
+
const filePath = safeQuotesPath(`tx-${txHash}.json`);
|
|
362
|
+
if (!filePath) return null;
|
|
352
363
|
if (!fs.existsSync(filePath)) return null;
|
|
353
364
|
try {
|
|
354
365
|
const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
@@ -391,8 +402,8 @@ export function saveQuote(quoteResponse, chain, signerType = 'local', privyWalle
|
|
|
391
402
|
* Load a saved quote by ID.
|
|
392
403
|
*/
|
|
393
404
|
export function loadQuote(quoteId) {
|
|
394
|
-
const filePath =
|
|
395
|
-
if (!fs.existsSync(filePath)) {
|
|
405
|
+
const filePath = safeQuotesPath(`${quoteId}.json`);
|
|
406
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
396
407
|
throw new Error(`Quote "${quoteId}" not found. Quotes expire after 1 hour.`);
|
|
397
408
|
}
|
|
398
409
|
const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
@@ -2006,6 +2017,11 @@ EXAMPLES:
|
|
|
2006
2017
|
chain,
|
|
2007
2018
|
simulate: !noSimulate && !gasless,
|
|
2008
2019
|
};
|
|
2020
|
+
|
|
2021
|
+
// Include backend quoteId if available for better BI tracking
|
|
2022
|
+
if (currentQuote.metadata?.quoteId) {
|
|
2023
|
+
execParams.quoteId = currentQuote.metadata.quoteId;
|
|
2024
|
+
}
|
|
2009
2025
|
// The backend's /execute schema is strict; sending fields it doesn't expect
|
|
2010
2026
|
// for the (chain × aggregator × gasless) combination causes 502s or
|
|
2011
2027
|
// "Unrecognized keys" rejections. The matrix we've validated against the
|