nansen-cli 1.9.3 → 1.10.1
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 +42 -0
- package/README.md +41 -281
- package/package.json +8 -2
- package/src/api.js +13 -12
- package/src/cli.js +161 -375
- package/src/schema.json +1516 -0
- package/src/trading.js +16 -25
- package/src/transfer.js +10 -14
- package/src/wallet.js +2 -2
- package/src/walletconnect-exec.js +1 -1
- package/src/x402-svm.js +3 -3
- package/src/x402.js +1 -1
package/src/trading.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Nansen CLI - Trading Commands
|
|
3
3
|
* Quote and execute DEX swaps via the Nansen Trading API.
|
|
4
|
-
* Supports Solana and
|
|
4
|
+
* Supports Solana and Base.
|
|
5
5
|
* Zero external dependencies — uses Node.js built-in crypto only.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -19,21 +19,17 @@ const TRADING_API_URL = process.env.NANSEN_TRADING_API_URL || 'https://trading-a
|
|
|
19
19
|
|
|
20
20
|
const CHAIN_MAP = {
|
|
21
21
|
solana: { index: '501', type: 'solana', chainId: 501, name: 'Solana', explorer: 'https://solscan.io/tx/' },
|
|
22
|
-
ethereum: { index: '1', type: 'evm', chainId: 1, name: 'Ethereum', explorer: 'https://etherscan.io/tx/' },
|
|
23
22
|
base: { index: '8453', type: 'evm', chainId: 8453, name: 'Base', explorer: 'https://basescan.org/tx/' },
|
|
24
|
-
bsc: { index: '56', type: 'evm', chainId: 56, name: 'BSC', explorer: 'https://bscscan.com/tx/' },
|
|
25
23
|
};
|
|
26
24
|
|
|
27
25
|
// Extend when adding new EVM chains (e.g. arbitrum WETH, polygon WMATIC)
|
|
28
26
|
const WRAPPED_NATIVE_TOKENS = {
|
|
29
|
-
ethereum: { address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', symbol: 'WETH', nativeSymbol: 'ETH' },
|
|
30
27
|
base: { address: '0x4200000000000000000000000000000000000006', symbol: 'WETH', nativeSymbol: 'ETH' },
|
|
31
|
-
bsc: { address: '0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c', symbol: 'WBNB', nativeSymbol: 'BNB' },
|
|
32
28
|
};
|
|
33
29
|
|
|
34
30
|
// Common token symbol → address lookup per chain.
|
|
35
31
|
// Native sentinels: Solana uses native mint, EVM uses 0xeee…eee.
|
|
36
|
-
// Wrapped-native addresses (WETH
|
|
32
|
+
// Wrapped-native addresses (WETH) are derived from WRAPPED_NATIVE_TOKENS
|
|
37
33
|
// to avoid duplication — keep that map as the single source of truth.
|
|
38
34
|
const EVM_NATIVE = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
|
|
39
35
|
const TOKEN_SYMBOLS = {
|
|
@@ -43,12 +39,6 @@ const TOKEN_SYMBOLS = {
|
|
|
43
39
|
USDC: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
|
44
40
|
USDT: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
|
|
45
41
|
},
|
|
46
|
-
ethereum: {
|
|
47
|
-
ETH: EVM_NATIVE,
|
|
48
|
-
WETH: WRAPPED_NATIVE_TOKENS.ethereum.address,
|
|
49
|
-
USDC: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
|
50
|
-
USDT: '0xdac17f958d2ee523a2206206994597c13d831ec7',
|
|
51
|
-
},
|
|
52
42
|
base: {
|
|
53
43
|
ETH: EVM_NATIVE,
|
|
54
44
|
WETH: WRAPPED_NATIVE_TOKENS.base.address,
|
|
@@ -57,12 +47,6 @@ const TOKEN_SYMBOLS = {
|
|
|
57
47
|
// (like Circle did with USDC), this address will need updating.
|
|
58
48
|
USDT: '0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2',
|
|
59
49
|
},
|
|
60
|
-
bsc: {
|
|
61
|
-
BNB: EVM_NATIVE,
|
|
62
|
-
WBNB: WRAPPED_NATIVE_TOKENS.bsc.address,
|
|
63
|
-
USDC: '0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d',
|
|
64
|
-
USDT: '0x55d398326f99059ff775485246999027b3197955',
|
|
65
|
-
},
|
|
66
50
|
};
|
|
67
51
|
|
|
68
52
|
/**
|
|
@@ -80,9 +64,7 @@ export function resolveTokenAddress(symbolOrAddress, chainName) {
|
|
|
80
64
|
|
|
81
65
|
// Default public RPC endpoints (used for nonce fetching)
|
|
82
66
|
const EVM_RPC_URLS = {
|
|
83
|
-
ethereum: process.env.NANSEN_RPC_ETHEREUM || 'https://eth.llamarpc.com',
|
|
84
67
|
base: process.env.NANSEN_RPC_BASE || 'https://mainnet.base.org',
|
|
85
|
-
bsc: process.env.NANSEN_RPC_BSC || 'https://bsc-dataseed.binance.org',
|
|
86
68
|
};
|
|
87
69
|
|
|
88
70
|
function getQuotesDir() {
|
|
@@ -321,7 +303,7 @@ export function signSolanaTransaction(transactionBase64, privateKeyHex) {
|
|
|
321
303
|
*
|
|
322
304
|
* @param {object} txData - Transaction fields from quote.transaction { to, data, value, gas, gasPrice }
|
|
323
305
|
* @param {string} privateKeyHex - 64-char hex (32-byte secp256k1 private key)
|
|
324
|
-
* @param {string} chain - Chain name
|
|
306
|
+
* @param {string} chain - Chain name
|
|
325
307
|
* @param {number} nonce - Account nonce
|
|
326
308
|
* @returns {string} 0x-prefixed signed transaction hex
|
|
327
309
|
*/
|
|
@@ -794,12 +776,17 @@ export function buildTradingCommands(deps = {}) {
|
|
|
794
776
|
errorOutput(`
|
|
795
777
|
Usage: nansen trade quote --chain <chain> --from <token> --to <token> --amount <baseUnits>
|
|
796
778
|
|
|
779
|
+
PREREQUISITE:
|
|
780
|
+
A wallet must be configured before using this command (the trading API builds
|
|
781
|
+
a transaction specific to your sender address).
|
|
782
|
+
Set one up with: nansen wallet create
|
|
783
|
+
|
|
797
784
|
OPTIONS:
|
|
798
|
-
--chain <chain> Chain: solana,
|
|
785
|
+
--chain <chain> Chain: solana, base
|
|
799
786
|
--from <symbol|address> Input token (symbol like SOL, USDC or address)
|
|
800
787
|
--to <symbol|address> Output token (symbol like USDC, ETH or address)
|
|
801
788
|
--amount <units> Amount in BASE UNITS (e.g. lamports, wei)
|
|
802
|
-
--wallet <name> Wallet name (default: default wallet)
|
|
789
|
+
--wallet <name> Wallet name (default: default wallet). Use "walletconnect" or "wc" for WalletConnect (EVM only).
|
|
803
790
|
--slippage <pct> Slippage as decimal (e.g. 0.03 for 3%). Default: 0.03
|
|
804
791
|
--auto-slippage Enable auto slippage calculation
|
|
805
792
|
--max-auto-slippage <pct> Max auto slippage when auto-slippage enabled
|
|
@@ -844,11 +831,15 @@ EXAMPLES:
|
|
|
844
831
|
const wallet = showWallet(walletName);
|
|
845
832
|
walletAddress = chainType === 'solana' ? wallet.solana : wallet.evm;
|
|
846
833
|
} else {
|
|
847
|
-
|
|
834
|
+
try {
|
|
835
|
+
walletAddress = getDefaultAddress(chainType);
|
|
836
|
+
} catch {
|
|
837
|
+
// No wallet configured — fall through to the check below
|
|
838
|
+
}
|
|
848
839
|
}
|
|
849
840
|
|
|
850
841
|
if (!walletAddress) {
|
|
851
|
-
errorOutput('No wallet found.
|
|
842
|
+
errorOutput('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');
|
|
852
843
|
exit(1);
|
|
853
844
|
return;
|
|
854
845
|
}
|
package/src/transfer.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import crypto from 'crypto';
|
|
8
8
|
import { base58Encode, exportWallet, getWalletConfig, verifyPassword } from './wallet.js';
|
|
9
|
-
import { keccak256, signSecp256k1, rlpEncode
|
|
9
|
+
import { keccak256, signSecp256k1, rlpEncode } from './crypto.js';
|
|
10
10
|
import { getWalletConnectAddress, sendTransactionViaWalletConnect } from './walletconnect-trading.js';
|
|
11
11
|
import { EVM_CHAIN_IDS } from './chain-ids.js';
|
|
12
12
|
|
|
@@ -217,7 +217,6 @@ async function buildEvmTransaction({ to, amount, token, privateKey, chain, max =
|
|
|
217
217
|
const safeReserve = l2GasCost * 3n;
|
|
218
218
|
if (ethBalance <= safeReserve) throw new Error(`Insufficient balance: ${ethBalance} wei (need > ${safeReserve} for gas + L1 fees)`);
|
|
219
219
|
txValue = ethBalance - safeReserve;
|
|
220
|
-
amount = txValue;
|
|
221
220
|
stderr(` Max send: ${formatAmount(txValue, 18)} ETH (reserved ${formatAmount(safeReserve, 18)} for gas)`);
|
|
222
221
|
} else {
|
|
223
222
|
txValue = amount;
|
|
@@ -375,7 +374,7 @@ async function buildSolanaTransaction({ to, amount, amountStr, token, privateKey
|
|
|
375
374
|
}
|
|
376
375
|
} catch (e) {
|
|
377
376
|
if (e.message.includes('Insufficient')) throw e;
|
|
378
|
-
throw new Error(`Source token account not found. Do you hold this token
|
|
377
|
+
throw new Error(`Source token account not found. Do you hold this token?`, { cause: e });
|
|
379
378
|
}
|
|
380
379
|
|
|
381
380
|
// TransferChecked instruction data: [12, amount u64 LE, decimals u8]
|
|
@@ -568,7 +567,7 @@ async function validateErc20Token(rpcUrl, tokenAddress) {
|
|
|
568
567
|
return decimals;
|
|
569
568
|
} catch (e) {
|
|
570
569
|
if (e.message.includes('not a valid')) throw e;
|
|
571
|
-
throw new Error(`Contract ${tokenAddress} does not implement ERC-20 decimals() — may not be a valid token
|
|
570
|
+
throw new Error(`Contract ${tokenAddress} does not implement ERC-20 decimals() — may not be a valid token`, { cause: e });
|
|
572
571
|
}
|
|
573
572
|
}
|
|
574
573
|
|
|
@@ -626,7 +625,7 @@ export async function sendTokens({ to, amount, chain, token = null, wallet = nul
|
|
|
626
625
|
} else if (max && token) {
|
|
627
626
|
// Max SPL: full token balance
|
|
628
627
|
const rpcUrl = CHAIN_RPCS.solana;
|
|
629
|
-
const { tokenProgram, decimals } = await getTokenInfo(rpcUrl, token);
|
|
628
|
+
const { tokenProgram, decimals: _decimals } = await getTokenInfo(rpcUrl, token);
|
|
630
629
|
const sourceATA = deriveATA(walletData.solana.address, token, tokenProgram);
|
|
631
630
|
const sourceAtaAddr = base58Encode(sourceATA);
|
|
632
631
|
const ataInfo = await rpcCall(rpcUrl, 'getTokenAccountBalance', [sourceAtaAddr]);
|
|
@@ -721,7 +720,7 @@ async function sendTokensViaWalletConnect({ to, amount, chain, token, max, dryRu
|
|
|
721
720
|
const wcAddress = await getWalletConnectAddress();
|
|
722
721
|
if (!wcAddress) throw new Error('No WalletConnect session active. Run: walletconnect connect');
|
|
723
722
|
|
|
724
|
-
let txTo, txValue, txData
|
|
723
|
+
let txTo, txValue, txData;
|
|
725
724
|
|
|
726
725
|
if (token) {
|
|
727
726
|
// Validate ERC-20 contract
|
|
@@ -730,7 +729,7 @@ async function sendTokensViaWalletConnect({ to, amount, chain, token, max, dryRu
|
|
|
730
729
|
throw new Error(`Address ${token} is not a contract — not a valid ERC-20 token`);
|
|
731
730
|
}
|
|
732
731
|
const decResult = await rpcCall(rpcUrl, 'eth_call', [{ to: token, data: '0x313ce567' }, 'latest']);
|
|
733
|
-
decimals = parseInt(decResult, 16);
|
|
732
|
+
const decimals = parseInt(decResult, 16);
|
|
734
733
|
|
|
735
734
|
if (max) {
|
|
736
735
|
// Max ERC-20: full token balance
|
|
@@ -809,14 +808,11 @@ async function sendTokensViaWalletConnect({ to, amount, chain, token, max, dryRu
|
|
|
809
808
|
chainId,
|
|
810
809
|
});
|
|
811
810
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
} else if (wcResult.signedTransaction) {
|
|
816
|
-
txHash = await broadcastTransaction(wcResult.signedTransaction, chain);
|
|
817
|
-
} else {
|
|
811
|
+
const txHash = await (async () => {
|
|
812
|
+
if (wcResult.txHash) return wcResult.txHash;
|
|
813
|
+
if (wcResult.signedTransaction) return broadcastTransaction(wcResult.signedTransaction, chain);
|
|
818
814
|
throw new Error('No transaction hash or signed transaction returned from WalletConnect');
|
|
819
|
-
}
|
|
815
|
+
})();
|
|
820
816
|
|
|
821
817
|
// Wait for confirmation
|
|
822
818
|
const confirmation = await waitForEvmConfirmation(rpcUrl, txHash);
|
package/src/wallet.js
CHANGED
|
@@ -26,7 +26,7 @@ const SCRYPT_P = 1;
|
|
|
26
26
|
const SCRYPT_KEYLEN = 32;
|
|
27
27
|
const SALT_LEN = 16;
|
|
28
28
|
const IV_LEN = 12;
|
|
29
|
-
const
|
|
29
|
+
const _AUTH_TAG_LEN = 16;
|
|
30
30
|
|
|
31
31
|
import { keccak256 } from './crypto.js';
|
|
32
32
|
|
|
@@ -481,7 +481,7 @@ export function getDefaultAddress(chainType = 'evm') {
|
|
|
481
481
|
* Build wallet command handlers for integration into CLI.
|
|
482
482
|
*/
|
|
483
483
|
export function buildWalletCommands(deps = {}) {
|
|
484
|
-
const { log = console.log, promptFn, exit = process.exit } = deps;
|
|
484
|
+
const { log = console.log, promptFn: _promptFn, exit = process.exit } = deps;
|
|
485
485
|
|
|
486
486
|
return {
|
|
487
487
|
'wallet': async (args, apiInstance, flags, options) => {
|
|
@@ -11,7 +11,7 @@ import { execFile } from 'child_process';
|
|
|
11
11
|
*/
|
|
12
12
|
export function wcExec(cmd, args, timeoutMs = 10000) {
|
|
13
13
|
return new Promise((resolve, reject) => {
|
|
14
|
-
execFile(cmd, args, { timeout: timeoutMs }, (err, stdout,
|
|
14
|
+
execFile(cmd, args, { timeout: timeoutMs }, (err, stdout, _stderr) => {
|
|
15
15
|
if (err) {
|
|
16
16
|
reject(new Error(err.message));
|
|
17
17
|
return;
|
package/src/x402-svm.js
CHANGED
|
@@ -30,11 +30,11 @@ export function base58Encode(buf) {
|
|
|
30
30
|
// ============= Constants =============
|
|
31
31
|
|
|
32
32
|
const TOKEN_PROGRAM = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
|
|
33
|
-
const
|
|
33
|
+
const _TOKEN_2022_PROGRAM = 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
|
|
34
34
|
const COMPUTE_BUDGET_PROGRAM = 'ComputeBudget111111111111111111111111111111';
|
|
35
35
|
const MEMO_PROGRAM = 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr';
|
|
36
36
|
const ATA_PROGRAM = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';
|
|
37
|
-
const
|
|
37
|
+
const _SYSTEM_PROGRAM = '11111111111111111111111111111111';
|
|
38
38
|
|
|
39
39
|
const DEFAULT_COMPUTE_UNIT_LIMIT = 20000;
|
|
40
40
|
const DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS = 1;
|
|
@@ -186,7 +186,7 @@ function modInverse(a, mod) {
|
|
|
186
186
|
* Build a Solana MessageV0 from accounts and instructions.
|
|
187
187
|
* Simplified builder for x402 payment transactions.
|
|
188
188
|
*/
|
|
189
|
-
function buildMessageV0({ feePayer, instructions, recentBlockhash, accounts }) {
|
|
189
|
+
function buildMessageV0({ feePayer, instructions, recentBlockhash, accounts: _accounts }) {
|
|
190
190
|
// All unique accounts in order: feePayer first, then signers, then rest
|
|
191
191
|
const accountMap = new Map();
|
|
192
192
|
const feePayerKey = feePayer;
|
package/src/x402.js
CHANGED
|
@@ -154,7 +154,7 @@ export async function createPaymentSignature(response, url, options = {}) {
|
|
|
154
154
|
*/
|
|
155
155
|
export async function checkX402Balance(network) {
|
|
156
156
|
try {
|
|
157
|
-
const { listWallets, exportWallet } = await import('./wallet.js');
|
|
157
|
+
const { listWallets, exportWallet: _exportWallet } = await import('./wallet.js');
|
|
158
158
|
const wallets = listWallets();
|
|
159
159
|
if (!wallets.defaultWallet) return null;
|
|
160
160
|
|