nansen-cli 1.24.0 → 1.25.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 +14 -0
- package/package.json +1 -1
- package/src/api.js +3 -2
- package/src/cli.js +1 -1
- package/src/schema.json +4 -4
- package/src/trading.js +61 -13
- package/src/x402-svm.js +1 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.25.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#363](https://github.com/nansen-ai/nansen-cli/pull/363) [`6ae402e`](https://github.com/nansen-ai/nansen-cli/commit/6ae402ef1e5bdeacf83fe04bcf6e8e0c9f9c91b7) Thanks [@TimNooren](https://github.com/TimNooren)! - Add `--amount-unit usd` to trade commands — specify swap amounts in USD
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- [#374](https://github.com/nansen-ai/nansen-cli/pull/374) [`0f14803`](https://github.com/nansen-ai/nansen-cli/commit/0f148031ef7590f3405c1dba9f31ad83768a7141) Thanks [@TimNooren](https://github.com/TimNooren)! - Fix cross-chain quote display: show adaptive precision for sub-cent bridge fees, "< 1 min" for fast bridges, and echo --to-wallet address in output
|
|
12
|
+
|
|
13
|
+
- [#366](https://github.com/nansen-ai/nansen-cli/pull/366) [`f358fff`](https://github.com/nansen-ai/nansen-cli/commit/f358fffcc80136c4e609f2e056f2c5ffb052d626) Thanks [@kome12](https://github.com/kome12)! - fix(token): replace dead `--days` param with working `--timeframe` for `token flow-intelligence`
|
|
14
|
+
|
|
15
|
+
The `--days` option was accepted but never sent to the API, resulting in always fetching `1d` data. This replaces it with `--timeframe` (enum: `1h | 6h | 12h | 1d | 7d`, default `1d`) which maps correctly to the API parameter.
|
|
16
|
+
|
|
3
17
|
## 1.24.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -1061,14 +1061,15 @@ export class NansenAPI {
|
|
|
1061
1061
|
}
|
|
1062
1062
|
|
|
1063
1063
|
async tokenFlowIntelligence(params = {}) {
|
|
1064
|
-
const { tokenAddress, chain = 'solana' } = params;
|
|
1064
|
+
const { tokenAddress, chain = 'solana', timeframe = '1d' } = params;
|
|
1065
1065
|
if (tokenAddress) {
|
|
1066
1066
|
const validation = validateTokenAddress(tokenAddress, chain);
|
|
1067
1067
|
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
1068
1068
|
}
|
|
1069
1069
|
return this.request('/api/v1/tgm/flow-intelligence', {
|
|
1070
1070
|
token_address: tokenAddress,
|
|
1071
|
-
chain
|
|
1071
|
+
chain,
|
|
1072
|
+
timeframe
|
|
1072
1073
|
});
|
|
1073
1074
|
}
|
|
1074
1075
|
|
package/src/cli.js
CHANGED
|
@@ -1250,7 +1250,7 @@ export function buildCommands(deps = {}) {
|
|
|
1250
1250
|
const buyOrSell = (options['buy-or-sell'] || 'BUY').toUpperCase();
|
|
1251
1251
|
return apiInstance.tokenWhoBoughtSold({ tokenAddress, chain, buyOrSell, filters, orderBy, pagination, days, date });
|
|
1252
1252
|
},
|
|
1253
|
-
'flow-intelligence': () => apiInstance.tokenFlowIntelligence({ tokenAddress, chain,
|
|
1253
|
+
'flow-intelligence': () => apiInstance.tokenFlowIntelligence({ tokenAddress, chain, timeframe: options.timeframe || '1d' }),
|
|
1254
1254
|
'transfers': () => {
|
|
1255
1255
|
// Inject --from/--to into filters
|
|
1256
1256
|
if (options.from) filters.from_address = options.from;
|
package/src/schema.json
CHANGED
|
@@ -408,8 +408,8 @@
|
|
|
408
408
|
"token": {
|
|
409
409
|
"required": true
|
|
410
410
|
},
|
|
411
|
-
"
|
|
412
|
-
"default":
|
|
411
|
+
"timeframe": {
|
|
412
|
+
"default": "1d"
|
|
413
413
|
}
|
|
414
414
|
}
|
|
415
415
|
},
|
|
@@ -796,11 +796,11 @@
|
|
|
796
796
|
"amount": {
|
|
797
797
|
"type": "string",
|
|
798
798
|
"required": true,
|
|
799
|
-
"description": "Amount to swap (base units by default, or token units with --amount-unit token)"
|
|
799
|
+
"description": "Amount to swap (base units by default, or token units with --amount-unit token, or USD with --amount-unit usd)"
|
|
800
800
|
},
|
|
801
801
|
"amount-unit": {
|
|
802
802
|
"type": "string",
|
|
803
|
-
"description": "\"token\" to specify amount in token units (e.g. 0.5 SOL), or \"base\" for base units (default).
|
|
803
|
+
"description": "\"token\" to specify amount in token units (e.g. 0.5 SOL), \"usd\" to specify amount in USD (e.g. 50), or \"base\" for base units (default). The CLI resolves the current token price and decimals locally; the API always receives base units."
|
|
804
804
|
},
|
|
805
805
|
"wallet": {
|
|
806
806
|
"type": "string",
|
package/src/trading.js
CHANGED
|
@@ -853,6 +853,27 @@ export function convertToBaseUnits(amount, decimals) {
|
|
|
853
853
|
return raw;
|
|
854
854
|
}
|
|
855
855
|
|
|
856
|
+
/**
|
|
857
|
+
* Fetch the current USD price for a token via the Nansen search API.
|
|
858
|
+
* Used by --amount-unit usd to convert dollar amounts to token amounts.
|
|
859
|
+
*/
|
|
860
|
+
export async function resolveUsdPrice(apiInstance, tokenAddress, chain) {
|
|
861
|
+
const result = await apiInstance.generalSearch({
|
|
862
|
+
query: tokenAddress,
|
|
863
|
+
resultType: 'token',
|
|
864
|
+
chain,
|
|
865
|
+
limit: 1,
|
|
866
|
+
});
|
|
867
|
+
const isEvm = tokenAddress.startsWith('0x');
|
|
868
|
+
const token = result.tokens?.find(t =>
|
|
869
|
+
isEvm ? t.address?.toLowerCase() === tokenAddress.toLowerCase() : t.address === tokenAddress
|
|
870
|
+
);
|
|
871
|
+
if (!token?.price) {
|
|
872
|
+
throw new Error(`Could not resolve USD price for ${tokenAddress} on ${chain}. The token may not have pricing data.`);
|
|
873
|
+
}
|
|
874
|
+
return token.price;
|
|
875
|
+
}
|
|
876
|
+
|
|
856
877
|
/**
|
|
857
878
|
* Check if amount contains a decimal point (i.e. not in base units).
|
|
858
879
|
* Returns an error string if invalid, or null if OK. Pure function.
|
|
@@ -892,10 +913,16 @@ export function formatQuote(quote, index) {
|
|
|
892
913
|
const meta = quote.metadata || {};
|
|
893
914
|
if (meta.isCrossChain) {
|
|
894
915
|
if (meta.bridgeTool) lines.push(` Bridge: ${meta.bridgeTool}`);
|
|
895
|
-
if (meta.executionDuration)
|
|
916
|
+
if (meta.executionDuration) {
|
|
917
|
+
const mins = Math.round(meta.executionDuration / 60);
|
|
918
|
+
lines.push(` Est. Time: ${mins < 1 ? '< 1 min' : `~${mins} min`}`);
|
|
919
|
+
}
|
|
896
920
|
if (meta.feeCosts?.length) {
|
|
897
921
|
const totalFees = meta.feeCosts.reduce((sum, f) => sum + parseFloat(f.amountUSD || 0), 0);
|
|
898
|
-
if (totalFees > 0)
|
|
922
|
+
if (totalFees > 0) {
|
|
923
|
+
const feeStr = totalFees < 0.01 ? totalFees.toPrecision(1) : totalFees.toFixed(2);
|
|
924
|
+
lines.push(` Bridge Fees: $${feeStr}`);
|
|
925
|
+
}
|
|
899
926
|
}
|
|
900
927
|
}
|
|
901
928
|
if (quote.priceImpactPct) {
|
|
@@ -948,7 +975,7 @@ OPTIONS:
|
|
|
948
975
|
--from <symbol|address> Input token (symbol like SOL, USDC or address)
|
|
949
976
|
--to <symbol|address> Output token (symbol like USDC, ETH or address)
|
|
950
977
|
--amount <units> Amount in BASE UNITS (e.g. lamports, wei)
|
|
951
|
-
--amount-unit <unit> "token"
|
|
978
|
+
--amount-unit <unit> "token" for token units (e.g. 0.5 SOL), "usd" for USD (e.g. 50)
|
|
952
979
|
--wallet <name> Wallet name (default: default wallet). Use "walletconnect" or "wc" for WalletConnect.
|
|
953
980
|
--to-wallet <address> Destination wallet address (auto-derived for cross-chain if omitted)
|
|
954
981
|
--slippage <pct> Slippage as decimal (e.g. 0.03 for 3%). Default: 0.03
|
|
@@ -959,6 +986,7 @@ OPTIONS:
|
|
|
959
986
|
EXAMPLES:
|
|
960
987
|
nansen trade quote --chain solana --from SOL --to USDC --amount 1000000000
|
|
961
988
|
nansen trade quote --chain solana --from SOL --to USDC --amount 0.5 --amount-unit token
|
|
989
|
+
nansen trade quote --chain solana --from SOL --to USDC --amount 50 --amount-unit usd
|
|
962
990
|
nansen trade quote --chain base --from ETH --to USDC --amount 1000000000000000000
|
|
963
991
|
nansen trade quote --chain base --to-chain solana --from USDC --to USDC --amount 1000000
|
|
964
992
|
nansen trade quote --chain solana --to-chain base --from SOL --to ETH --amount 1000000000
|
|
@@ -968,8 +996,8 @@ EXAMPLES:
|
|
|
968
996
|
}
|
|
969
997
|
|
|
970
998
|
// Validate --amount-unit if provided
|
|
971
|
-
if (amountUnit && amountUnit !== 'token' && amountUnit !== 'base') {
|
|
972
|
-
log(`Error: Unknown --amount-unit "${amountUnit}". Supported values: token, base`);
|
|
999
|
+
if (amountUnit && amountUnit !== 'token' && amountUnit !== 'base' && amountUnit !== 'usd') {
|
|
1000
|
+
log(`Error: Unknown --amount-unit "${amountUnit}". Supported values: token, base, usd`);
|
|
973
1001
|
exit(1);
|
|
974
1002
|
return;
|
|
975
1003
|
}
|
|
@@ -988,7 +1016,23 @@ EXAMPLES:
|
|
|
988
1016
|
// Otherwise, validate that the amount is already in base units (integer).
|
|
989
1017
|
let resolvedAmount = amount;
|
|
990
1018
|
let resolvedDecimals;
|
|
991
|
-
|
|
1019
|
+
let usdTokenAmount; // token-unit amount after USD conversion (for balance pre-check)
|
|
1020
|
+
if (amountUnit === 'usd') {
|
|
1021
|
+
try {
|
|
1022
|
+
const tokenForPrice = swapMode === 'exactOut' ? to : from;
|
|
1023
|
+
const price = await resolveUsdPrice(apiInstance, tokenForPrice, chain);
|
|
1024
|
+
resolvedDecimals = await resolveTokenDecimals(tokenForPrice, chain);
|
|
1025
|
+
// Convert USD to token amount, then to base units via string math.
|
|
1026
|
+
// Use toFixed() instead of String() to avoid scientific notation for small values.
|
|
1027
|
+
const tokenAmount = parseFloat(amount) / price;
|
|
1028
|
+
usdTokenAmount = tokenAmount.toFixed(resolvedDecimals);
|
|
1029
|
+
resolvedAmount = convertToBaseUnits(usdTokenAmount, resolvedDecimals);
|
|
1030
|
+
} catch (err) {
|
|
1031
|
+
log(`Error converting USD amount: ${err.message}`);
|
|
1032
|
+
exit(1);
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
} else if (amountUnit === 'token') {
|
|
992
1036
|
try {
|
|
993
1037
|
const tokenForDecimals = swapMode === 'exactOut' ? to : from;
|
|
994
1038
|
resolvedDecimals = await resolveTokenDecimals(tokenForDecimals, chain);
|
|
@@ -1053,21 +1097,24 @@ EXAMPLES:
|
|
|
1053
1097
|
}
|
|
1054
1098
|
|
|
1055
1099
|
// Balance pre-check — catches zero balances and insufficient funds
|
|
1056
|
-
// before wasting a quote API call.
|
|
1057
|
-
// in exactIn mode
|
|
1058
|
-
// comparing
|
|
1059
|
-
if (amountUnit === 'token' && swapMode !== 'exactOut') {
|
|
1100
|
+
// before wasting a quote API call. Runs for --amount-unit token and
|
|
1101
|
+
// usd (after USD→token conversion) in exactIn mode. In exactOut the
|
|
1102
|
+
// amount is the buy amount so comparing against sell balance is meaningless.
|
|
1103
|
+
if ((amountUnit === 'token' || amountUnit === 'usd') && swapMode !== 'exactOut') {
|
|
1060
1104
|
try {
|
|
1105
|
+
// For USD, pass the converted token-unit amount so validateBalance
|
|
1106
|
+
// can compare against the wallet balance in token units.
|
|
1107
|
+
const tokenUnitAmount = amountUnit === 'usd' ? usdTokenAmount : amount;
|
|
1061
1108
|
const { adjustedAmount: balanceAdjusted } = await validateBalance({
|
|
1062
1109
|
chain,
|
|
1063
1110
|
from,
|
|
1064
|
-
amount,
|
|
1065
|
-
amountUnit,
|
|
1111
|
+
amount: tokenUnitAmount,
|
|
1112
|
+
amountUnit: 'token',
|
|
1066
1113
|
walletAddress,
|
|
1067
1114
|
decimals: resolvedDecimals,
|
|
1068
1115
|
symbol: fromRaw,
|
|
1069
1116
|
});
|
|
1070
|
-
if (balanceAdjusted !==
|
|
1117
|
+
if (balanceAdjusted !== tokenUnitAmount) {
|
|
1071
1118
|
resolvedAmount = convertToBaseUnits(balanceAdjusted, resolvedDecimals);
|
|
1072
1119
|
}
|
|
1073
1120
|
} catch (balanceErr) {
|
|
@@ -1101,6 +1148,7 @@ EXAMPLES:
|
|
|
1101
1148
|
params.toChainIndex = toChainConfig.index;
|
|
1102
1149
|
if (toWallet) {
|
|
1103
1150
|
params.toWalletAddress = toWallet;
|
|
1151
|
+
log(` Destination wallet: ${toWallet}`);
|
|
1104
1152
|
} else if (chainConfig.type !== toChainConfig.type) {
|
|
1105
1153
|
// Solana↔Base: auto-derive the destination address from the same wallet
|
|
1106
1154
|
const effectiveWalletName = walletName || getWalletConfig()?.defaultWallet;
|
package/src/x402-svm.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import crypto from 'crypto';
|
|
7
7
|
import { base58Encode, base58DecodePubkey } from './wallet.js';
|
|
8
|
+
import { encodeCompactU16 } from './transfer.js';
|
|
8
9
|
|
|
9
10
|
// ============= Constants =============
|
|
10
11
|
|
|
@@ -18,22 +19,6 @@ const _SYSTEM_PROGRAM = '11111111111111111111111111111111';
|
|
|
18
19
|
const DEFAULT_COMPUTE_UNIT_LIMIT = 20000;
|
|
19
20
|
const DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS = 1;
|
|
20
21
|
|
|
21
|
-
// ============= Compact-u16 Encoding =============
|
|
22
|
-
// (Solana's variable-length integer format, from trading.js pattern)
|
|
23
|
-
|
|
24
|
-
export function encodeCompactU16(value) {
|
|
25
|
-
if (value < 0x80) return Buffer.from([value]);
|
|
26
|
-
if (value < 0x4000) return Buffer.from([
|
|
27
|
-
(value & 0x7f) | 0x80,
|
|
28
|
-
(value >> 7) & 0x7f,
|
|
29
|
-
]);
|
|
30
|
-
return Buffer.from([
|
|
31
|
-
(value & 0x7f) | 0x80,
|
|
32
|
-
((value >> 7) & 0x7f) | 0x80,
|
|
33
|
-
(value >> 14) & 0x03,
|
|
34
|
-
]);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
22
|
// ============= PDA Derivation =============
|
|
38
23
|
|
|
39
24
|
/**
|