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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nansen-cli",
3
- "version": "1.24.0",
3
+ "version": "1.25.0",
4
4
  "description": "Command-line interface for Nansen API - designed for AI agents",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/api.js CHANGED
@@ -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, days }),
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
- "days": {
412
- "default": 30
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). Decimals are resolved locally and the API always receives base units."
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) lines.push(` Est. Time: ~${Math.round(meta.executionDuration / 60)} min`);
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) lines.push(` Bridge Fees: $${totalFees.toFixed(2)}`);
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" to specify amount in token units (e.g. 0.5 SOL)
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
- if (amountUnit === 'token') {
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. Only runs for --amount-unit token
1057
- // in exactIn mode (in exactOut, the amount is the buy amount so
1058
- // comparing it against the sell token balance is meaningless).
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 !== amount) {
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
  /**