nansen-cli 1.10.0 → 1.11.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/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 EVM chains (Ethereum, Base, BSC).
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, WBNB) are derived from WRAPPED_NATIVE_TOKENS
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 (ethereum, base, bsc)
306
+ * @param {string} chain - Chain name
325
307
  * @param {number} nonce - Account nonce
326
308
  * @returns {string} 0x-prefixed signed transaction hex
327
309
  */
@@ -774,7 +756,7 @@ export function formatQuote(quote, index) {
774
756
  * Build trading command handlers for CLI integration.
775
757
  */
776
758
  export function buildTradingCommands(deps = {}) {
777
- const { errorOutput = console.error, exit = process.exit } = deps;
759
+ const { log = console.log, exit = process.exit } = deps;
778
760
 
779
761
  return {
780
762
  'quote': async (args, apiInstance, flags, options) => {
@@ -791,15 +773,20 @@ export function buildTradingCommands(deps = {}) {
791
773
  const swapMode = options['swap-mode'] || 'exactIn';
792
774
 
793
775
  if (!chain || !from || !to || !amount) {
794
- errorOutput(`
776
+ log(`
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, ethereum, base, bsc
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
@@ -816,7 +803,7 @@ EXAMPLES:
816
803
 
817
804
  const amountError = validateBaseUnitAmount(amount);
818
805
  if (amountError) {
819
- errorOutput(`Error: ${amountError}`);
806
+ log(`Error: ${amountError}`);
820
807
  exit(1);
821
808
  return;
822
809
  }
@@ -830,13 +817,13 @@ EXAMPLES:
830
817
  let walletAddress;
831
818
  if (isWalletConnect) {
832
819
  if (chainType !== 'evm') {
833
- errorOutput('WalletConnect is only supported for EVM chains');
820
+ log('WalletConnect is only supported for EVM chains');
834
821
  exit(1);
835
822
  return;
836
823
  }
837
824
  walletAddress = await getWalletConnectAddress();
838
825
  if (!walletAddress) {
839
- errorOutput('No WalletConnect session active. Run: walletconnect connect');
826
+ log('No WalletConnect session active. Run: walletconnect connect');
840
827
  exit(1);
841
828
  return;
842
829
  }
@@ -852,16 +839,16 @@ EXAMPLES:
852
839
  }
853
840
 
854
841
  if (!walletAddress) {
855
- 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');
842
+ log('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');
856
843
  exit(1);
857
844
  return;
858
845
  }
859
846
 
860
- errorOutput(`\nFetching quote on ${chainConfig.name}...`);
861
- errorOutput(` Wallet: ${walletAddress}`);
847
+ log(`\nFetching quote on ${chainConfig.name}...`);
848
+ log(` Wallet: ${walletAddress}`);
862
849
 
863
850
  const fromWarning = getWrappedNativeFromWarning(from, chain);
864
- if (fromWarning) errorOutput(` ${fromWarning}`);
851
+ if (fromWarning) log(` ${fromWarning}`);
865
852
 
866
853
  const params = {
867
854
  chainIndex: chainConfig.index,
@@ -878,30 +865,30 @@ EXAMPLES:
878
865
  const response = await getQuote(params);
879
866
 
880
867
  if (!response.success || !response.quotes?.length) {
881
- errorOutput('No quotes available');
868
+ log('No quotes available');
882
869
  if (response.warnings?.length) {
883
- response.warnings.forEach(w => errorOutput(` Warning: ${w}`));
870
+ response.warnings.forEach(w => log(` Warning: ${w}`));
884
871
  }
885
872
  exit(1);
886
873
  return;
887
874
  }
888
875
 
889
- errorOutput('');
890
- response.quotes.forEach((q, i) => errorOutput(formatQuote(q, i)));
876
+ log('');
877
+ response.quotes.forEach((q, i) => log(formatQuote(q, i)));
891
878
 
892
879
  const quoteId = saveQuote(response, chain, isWalletConnect ? 'walletconnect' : 'local');
893
- errorOutput(`\n Quote ID: ${quoteId}`);
894
- errorOutput(` Execute: nansen trade execute --quote ${quoteId}`);
880
+ log(`\n Quote ID: ${quoteId}`);
881
+ log(` Execute: nansen trade execute --quote ${quoteId}`);
895
882
  if (response.quotes.length > 1) {
896
- errorOutput(` Pin #1: nansen trade execute --quote ${quoteId} --quote-index 0`);
883
+ log(` Pin #1: nansen trade execute --quote ${quoteId} --quote-index 0`);
897
884
  }
898
885
 
899
886
  if (response.quotes[0]?.approvalAddress && !isNativeToken(response.quotes[0]?.inputMint)) {
900
- errorOutput(`\n Warning: This token swap requires an ERC-20 approval step.`);
901
- errorOutput(` The execute command will handle this automatically.`);
887
+ log(`\n Warning: This token swap requires an ERC-20 approval step.`);
888
+ log(` The execute command will handle this automatically.`);
902
889
  }
903
890
 
904
- errorOutput('');
891
+ log('');
905
892
  return undefined; // Output already printed above
906
893
 
907
894
  } catch (err) {
@@ -909,8 +896,8 @@ EXAMPLES:
909
896
  if (err.code === 'INVALID_AMOUNT' || /amount/i.test(err.message)) {
910
897
  message += '. Amounts must be in base units (e.g., 1000000000 lamports for 1 SOL, 1000000000000000000 wei for 1 ETH)';
911
898
  }
912
- errorOutput(`Error: ${message}`);
913
- if (err.details) errorOutput(` Details: ${JSON.stringify(err.details)}`);
899
+ log(`Error: ${message}`);
900
+ if (err.details) log(` Details: ${JSON.stringify(err.details)}`);
914
901
  exit(1);
915
902
  }
916
903
  },
@@ -921,7 +908,7 @@ EXAMPLES:
921
908
  const noSimulate = flags['no-simulate'] || flags.noSimulate;
922
909
 
923
910
  if (!quoteId) {
924
- errorOutput(`
911
+ log(`
925
912
  Usage: nansen trade execute --quote <quoteId> [options]
926
913
 
927
914
  OPTIONS:
@@ -944,7 +931,7 @@ EXAMPLES:
944
931
 
945
932
  const allQuotes = quoteData.response.quotes || [];
946
933
  if (!allQuotes.length) {
947
- errorOutput('❌ No quote data found');
934
+ log('❌ No quote data found');
948
935
  exit(1);
949
936
  return;
950
937
  }
@@ -957,8 +944,8 @@ EXAMPLES:
957
944
  // Check if any quote in range has transaction data before prompting for password
958
945
  const hasAnyTransaction = allQuotes.slice(startIndex, endIndex).some(q => q?.transaction);
959
946
  if (!hasAnyTransaction) {
960
- errorOutput('❌ No quotes contain transaction data.');
961
- errorOutput(' Ensure userWalletAddress was provided when fetching the quote.');
947
+ log('❌ No quotes contain transaction data.');
948
+ log(' Ensure userWalletAddress was provided when fetching the quote.');
962
949
  exit(1);
963
950
  return;
964
951
  }
@@ -978,7 +965,7 @@ EXAMPLES:
978
965
  effectiveWalletName = list.defaultWallet;
979
966
  }
980
967
  if (!effectiveWalletName) {
981
- errorOutput('No wallet found. Create one with: nansen wallet create');
968
+ log('No wallet found. Create one with: nansen wallet create');
982
969
  exit(1);
983
970
  return;
984
971
  }
@@ -987,13 +974,13 @@ EXAMPLES:
987
974
  } else {
988
975
  // Verify WalletConnect session is still active and address matches quote
989
976
  if (chainType !== 'evm') {
990
- errorOutput('WalletConnect is only supported for EVM chains');
977
+ log('WalletConnect is only supported for EVM chains');
991
978
  exit(1);
992
979
  return;
993
980
  }
994
981
  const wcAddress = await getWalletConnectAddress();
995
982
  if (!wcAddress) {
996
- errorOutput('No WalletConnect session active. Run: walletconnect connect');
983
+ log('No WalletConnect session active. Run: walletconnect connect');
997
984
  exit(1);
998
985
  return;
999
986
  }
@@ -1001,7 +988,7 @@ EXAMPLES:
1001
988
  const quoteWallet = quoteData.response?.quotes?.[0]?.transaction?.from
1002
989
  || quoteData.response?.metadata?.userWalletAddress;
1003
990
  if (quoteWallet && wcAddress.toLowerCase() !== quoteWallet.toLowerCase()) {
1004
- errorOutput(`Connected wallet (${wcAddress}) doesn't match quote. Get a new quote with --wallet walletconnect`);
991
+ log(`Connected wallet (${wcAddress}) doesn't match quote. Get a new quote with --wallet walletconnect`);
1005
992
  exit(1);
1006
993
  return;
1007
994
  }
@@ -1017,17 +1004,17 @@ EXAMPLES:
1017
1004
 
1018
1005
  // Verify transaction data exists
1019
1006
  if (!currentQuote.transaction) {
1020
- errorOutput(` ⚠ Quote ${quoteName}: no transaction data, skipping...`);
1007
+ log(` ⚠ Quote ${quoteName}: no transaction data, skipping...`);
1021
1008
  lastQuoteError = `Quote ${quoteName} has no transaction data`;
1022
1009
  continue;
1023
1010
  }
1024
1011
 
1025
- errorOutput(`\nExecuting trade on ${chainConfig.name}...`);
1012
+ log(`\nExecuting trade on ${chainConfig.name}...`);
1026
1013
  if (endIndex - startIndex > 1) {
1027
- errorOutput(` Trying quote ${qi + 1}/${allQuotes.length} (${quoteName})...`);
1014
+ log(` Trying quote ${qi + 1}/${allQuotes.length} (${quoteName})...`);
1028
1015
  }
1029
- errorOutput(formatQuote(currentQuote));
1030
- errorOutput('');
1016
+ log(formatQuote(currentQuote));
1017
+ log('');
1031
1018
 
1032
1019
  try {
1033
1020
  let signedTransaction;
@@ -1040,7 +1027,7 @@ EXAMPLES:
1040
1027
  if (typeof txBase64 === 'object' && txBase64.data) {
1041
1028
  txBase64 = base58Decode(txBase64.data).toString('base64');
1042
1029
  }
1043
- errorOutput(' Signing Solana transaction...');
1030
+ log(' Signing Solana transaction...');
1044
1031
  signedTransaction = signSolanaTransaction(txBase64, exported.solana.privateKey);
1045
1032
  requestId = currentQuote.metadata?.requestId;
1046
1033
 
@@ -1054,15 +1041,15 @@ EXAMPLES:
1054
1041
  if (isNative) {
1055
1042
  const expectedValue = BigInt(currentQuote.inAmount || currentQuote.inputAmount || '0');
1056
1043
  if (txValue !== expectedValue) {
1057
- errorOutput(` ❌ Transaction value mismatch for ${quoteName}: tx.value=${txValue}, expected=${expectedValue}`);
1058
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1044
+ log(` ❌ Transaction value mismatch for ${quoteName}: tx.value=${txValue}, expected=${expectedValue}`);
1045
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1059
1046
  lastQuoteError = `${quoteName} transaction value mismatch`;
1060
1047
  continue;
1061
1048
  }
1062
1049
  } else {
1063
1050
  if (txValue > 0n) {
1064
- errorOutput(` ❌ ERC-20 swap has non-zero tx.value (${txValue}) for ${quoteName} — aborting`);
1065
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1051
+ log(` ❌ ERC-20 swap has non-zero tx.value (${txValue}) for ${quoteName} — aborting`);
1052
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1066
1053
  lastQuoteError = `${quoteName} unexpected tx.value`;
1067
1054
  continue;
1068
1055
  }
@@ -1076,10 +1063,10 @@ EXAMPLES:
1076
1063
  );
1077
1064
 
1078
1065
  if (existingAllowance >= inputAmount && existingAllowance > 0n) {
1079
- errorOutput(` ✓ Sufficient allowance exists for ${quoteName}, skipping approval`);
1066
+ log(` ✓ Sufficient allowance exists for ${quoteName}, skipping approval`);
1080
1067
  } else {
1081
- errorOutput(` ⚠ Approval required → ${currentQuote.approvalAddress}`);
1082
- errorOutput(` Sending approval via WalletConnect...`);
1068
+ log(` ⚠ Approval required → ${currentQuote.approvalAddress}`);
1069
+ log(` Sending approval via WalletConnect...`);
1083
1070
  try {
1084
1071
  const approvalResult = await sendApprovalViaWalletConnect(
1085
1072
  currentQuote.inputMint,
@@ -1089,7 +1076,7 @@ EXAMPLES:
1089
1076
  let approvalTxHash = approvalResult.txHash;
1090
1077
  if (!approvalTxHash && approvalResult.signedTransaction) {
1091
1078
  // Wallet returned a signed tx instead of broadcasting — broadcast via Trading API
1092
- errorOutput(` Broadcasting approval via Trading API...`);
1079
+ log(` Broadcasting approval via Trading API...`);
1093
1080
  const broadcastResult = await executeTransaction({
1094
1081
  signedTransaction: approvalResult.signedTransaction,
1095
1082
  chain,
@@ -1101,18 +1088,18 @@ EXAMPLES:
1101
1088
  approvalTxHash = broadcastResult.txHash;
1102
1089
  }
1103
1090
  if (approvalTxHash) {
1104
- errorOutput(` Waiting for approval confirmation...`);
1091
+ log(` Waiting for approval confirmation...`);
1105
1092
  const receipt = await waitForReceipt(chain, approvalTxHash);
1106
- errorOutput(` ✓ Approval confirmed in block ${parseInt(receipt.blockNumber, 16)}: ${approvalTxHash}`);
1093
+ log(` ✓ Approval confirmed in block ${parseInt(receipt.blockNumber, 16)}: ${approvalTxHash}`);
1107
1094
  }
1108
1095
  } catch (approvalErr) {
1109
- errorOutput(` ❌ Approval failed for ${quoteName}: ${approvalErr.message}`);
1110
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1096
+ log(` ❌ Approval failed for ${quoteName}: ${approvalErr.message}`);
1097
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1111
1098
  lastQuoteError = `${quoteName} approval failed`;
1112
1099
  continue;
1113
1100
  }
1114
1101
  await new Promise(r => setTimeout(r, 2000));
1115
- errorOutput('');
1102
+ log('');
1116
1103
  }
1117
1104
  }
1118
1105
 
@@ -1126,8 +1113,8 @@ EXAMPLES:
1126
1113
  value: txData.value ? '0x' + BigInt(txData.value).toString(16) : '0x0',
1127
1114
  });
1128
1115
  if (!sim.success) {
1129
- errorOutput(` ⚠ Simulation failed for ${quoteName}: ${sim.reason}`);
1130
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1116
+ log(` ⚠ Simulation failed for ${quoteName}: ${sim.reason}`);
1117
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1131
1118
  lastQuoteError = `${quoteName} simulation failed: ${sim.reason}`;
1132
1119
  continue;
1133
1120
  }
@@ -1140,7 +1127,7 @@ EXAMPLES:
1140
1127
  const finalGas = apiGas > 0 ? apiGas : txGas;
1141
1128
 
1142
1129
  // Send transaction via WalletConnect
1143
- errorOutput(' Sending transaction via WalletConnect...');
1130
+ log(' Sending transaction via WalletConnect...');
1144
1131
  let wcResult;
1145
1132
  try {
1146
1133
  wcResult = await sendTransactionViaWalletConnect({
@@ -1151,24 +1138,24 @@ EXAMPLES:
1151
1138
  chainId: chainConfig.chainId,
1152
1139
  });
1153
1140
  } catch (wcErr) {
1154
- errorOutput(` ❌ WalletConnect transaction failed for ${quoteName}: ${wcErr.message}`);
1155
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1141
+ log(` ❌ WalletConnect transaction failed for ${quoteName}: ${wcErr.message}`);
1142
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1156
1143
  lastQuoteError = `${quoteName}: ${wcErr.message}`;
1157
1144
  continue;
1158
1145
  }
1159
1146
 
1160
1147
  if (wcResult.txHash) {
1161
1148
  // Wallet broadcast — verify on-chain
1162
- errorOutput(' Verifying on-chain status...');
1149
+ log(' Verifying on-chain status...');
1163
1150
  try {
1164
1151
  await waitForReceipt(chain, wcResult.txHash);
1165
1152
  } catch (receiptErr) {
1166
- errorOutput(`\n ⚠ Transaction was broadcast but REVERTED on-chain!`);
1167
- errorOutput(` Tx Hash: ${wcResult.txHash}`);
1168
- errorOutput(` Explorer: ${chainConfig.explorer}${wcResult.txHash}`);
1169
- errorOutput(` Error: ${receiptErr.message}`);
1153
+ log(`\n ⚠ Transaction was broadcast but REVERTED on-chain!`);
1154
+ log(` Tx Hash: ${wcResult.txHash}`);
1155
+ log(` Explorer: ${chainConfig.explorer}${wcResult.txHash}`);
1156
+ log(` Error: ${receiptErr.message}`);
1170
1157
  if (qi + 1 < endIndex) {
1171
- errorOutput(` Trying next quote...`);
1158
+ log(` Trying next quote...`);
1172
1159
  lastQuoteError = `${quoteName} reverted on-chain`;
1173
1160
  continue;
1174
1161
  }
@@ -1176,11 +1163,11 @@ EXAMPLES:
1176
1163
  return;
1177
1164
  }
1178
1165
 
1179
- errorOutput(`\n ✓ Transaction successful!`);
1180
- errorOutput(` Tx Hash: ${wcResult.txHash}`);
1181
- errorOutput(` Chain: ${chainConfig.name}`);
1182
- errorOutput(` Explorer: ${chainConfig.explorer}${wcResult.txHash}`);
1183
- errorOutput('');
1166
+ log(`\n ✓ Transaction successful!`);
1167
+ log(` Tx Hash: ${wcResult.txHash}`);
1168
+ log(` Chain: ${chainConfig.name}`);
1169
+ log(` Explorer: ${chainConfig.explorer}${wcResult.txHash}`);
1170
+ log('');
1184
1171
  return undefined; // Success
1185
1172
  }
1186
1173
 
@@ -1204,15 +1191,15 @@ EXAMPLES:
1204
1191
  if (isNative) {
1205
1192
  const expectedValue = BigInt(currentQuote.inAmount || currentQuote.inputAmount || '0');
1206
1193
  if (txValue !== expectedValue) {
1207
- errorOutput(` ❌ Transaction value mismatch for ${quoteName}: tx.value=${txValue}, expected=${expectedValue}`);
1208
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1194
+ log(` ❌ Transaction value mismatch for ${quoteName}: tx.value=${txValue}, expected=${expectedValue}`);
1195
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1209
1196
  lastQuoteError = `${quoteName} transaction value mismatch`;
1210
1197
  continue;
1211
1198
  }
1212
1199
  } else {
1213
1200
  if (txValue > 0n) {
1214
- errorOutput(` ❌ ERC-20 swap has non-zero tx.value (${txValue}) for ${quoteName} — aborting`);
1215
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1201
+ log(` ❌ ERC-20 swap has non-zero tx.value (${txValue}) for ${quoteName} — aborting`);
1202
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1216
1203
  lastQuoteError = `${quoteName} unexpected tx.value`;
1217
1204
  continue;
1218
1205
  }
@@ -1226,10 +1213,10 @@ EXAMPLES:
1226
1213
  );
1227
1214
 
1228
1215
  if (existingAllowance >= inputAmount && existingAllowance > 0n) {
1229
- errorOutput(` ✓ Sufficient allowance exists for ${quoteName}, skipping approval`);
1216
+ log(` ✓ Sufficient allowance exists for ${quoteName}, skipping approval`);
1230
1217
  } else {
1231
- errorOutput(` ⚠ Approval required → ${currentQuote.approvalAddress}`);
1232
- errorOutput(` Sending approval tx...`);
1218
+ log(` ⚠ Approval required → ${currentQuote.approvalAddress}`);
1219
+ log(` Sending approval tx...`);
1233
1220
  const approvalNonce = await getEvmNonce(chain, walletAddress);
1234
1221
 
1235
1222
  const approvalGasPrice = currentQuote.transaction?.gasPrice || currentQuote.transaction?.maxFeePerGas || '1000000';
@@ -1249,25 +1236,25 @@ EXAMPLES:
1249
1236
  });
1250
1237
 
1251
1238
  if (approvalResult.status !== 'Success') {
1252
- errorOutput(` ❌ Approval failed for ${quoteName}: ${approvalResult.error || 'unknown error'}`);
1253
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1239
+ log(` ❌ Approval failed for ${quoteName}: ${approvalResult.error || 'unknown error'}`);
1240
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1254
1241
  lastQuoteError = `${quoteName} approval failed`;
1255
1242
  continue;
1256
1243
  }
1257
1244
 
1258
- errorOutput(` Waiting for approval confirmation...`);
1245
+ log(` Waiting for approval confirmation...`);
1259
1246
  try {
1260
1247
  const receipt = await waitForReceipt(chain, approvalResult.txHash);
1261
- errorOutput(` ✓ Approval confirmed in block ${parseInt(receipt.blockNumber, 16)}: ${approvalResult.txHash}`);
1248
+ log(` ✓ Approval confirmed in block ${parseInt(receipt.blockNumber, 16)}: ${approvalResult.txHash}`);
1262
1249
  } catch (receiptErr) {
1263
- errorOutput(` ❌ Approval may not have confirmed: ${receiptErr.message}`);
1264
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1250
+ log(` ❌ Approval may not have confirmed: ${receiptErr.message}`);
1251
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1265
1252
  lastQuoteError = `${quoteName} approval unconfirmed`;
1266
1253
  continue;
1267
1254
  }
1268
1255
  // Wait for RPC state propagation after approval
1269
1256
  await new Promise(r => setTimeout(r, 2000));
1270
- errorOutput('');
1257
+ log('');
1271
1258
  }
1272
1259
  }
1273
1260
 
@@ -1283,8 +1270,8 @@ EXAMPLES:
1283
1270
  value: txData.value ? '0x' + BigInt(txData.value).toString(16) : '0x0',
1284
1271
  });
1285
1272
  if (!sim.success) {
1286
- errorOutput(` ⚠ Simulation failed for ${quoteName}: ${sim.reason}`);
1287
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1273
+ log(` ⚠ Simulation failed for ${quoteName}: ${sim.reason}`);
1274
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1288
1275
  lastQuoteError = `${quoteName} simulation failed: ${sim.reason}`;
1289
1276
  continue;
1290
1277
  }
@@ -1298,17 +1285,17 @@ EXAMPLES:
1298
1285
  const txGas = parseInt(txData.gas || txData.gasLimit || "0");
1299
1286
  const finalGas = apiGas > 0 ? apiGas : txGas;
1300
1287
  if (finalGas !== txGas) {
1301
- errorOutput(` ℹ Using API gas ${finalGas} (tx.gas was ${txGas})`);
1288
+ log(` ℹ Using API gas ${finalGas} (tx.gas was ${txGas})`);
1302
1289
  }
1303
1290
  if (txData.gasLimit) txData.gasLimit = String(finalGas);
1304
1291
  else txData.gas = String(finalGas);
1305
1292
 
1306
- errorOutput(' Fetching nonce...');
1293
+ log(' Fetching nonce...');
1307
1294
  await new Promise(r => setTimeout(r, 1000));
1308
1295
  const nonce = await getEvmNonce(chain, walletAddress);
1309
- errorOutput(` Nonce: ${nonce}`);
1296
+ log(` Nonce: ${nonce}`);
1310
1297
 
1311
- errorOutput(' Signing EVM transaction...');
1298
+ log(' Signing EVM transaction...');
1312
1299
  signedTransaction = signEvmTransaction(
1313
1300
  currentQuote.transaction,
1314
1301
  exported.evm.privateKey,
@@ -1317,7 +1304,7 @@ EXAMPLES:
1317
1304
  );
1318
1305
  }
1319
1306
 
1320
- errorOutput(' Broadcasting...');
1307
+ log(' Broadcasting...');
1321
1308
  const execParams = {
1322
1309
  signedTransaction,
1323
1310
  chain,
@@ -1333,64 +1320,64 @@ EXAMPLES:
1333
1320
 
1334
1321
  // For EVM: verify the tx actually succeeded on-chain
1335
1322
  if (chainType === 'evm' && result.txHash) {
1336
- errorOutput(' Verifying on-chain status...');
1323
+ log(' Verifying on-chain status...');
1337
1324
  try {
1338
1325
  await waitForReceipt(chain, result.txHash);
1339
1326
  } catch (receiptErr) {
1340
- errorOutput(`\n ⚠ Transaction was broadcast but REVERTED on-chain!`);
1341
- errorOutput(` Tx Hash: ${result.txHash}`);
1342
- errorOutput(` Explorer: ${explorerUrl}`);
1343
- errorOutput(` Error: ${receiptErr.message}`);
1327
+ log(`\n ⚠ Transaction was broadcast but REVERTED on-chain!`);
1328
+ log(` Tx Hash: ${result.txHash}`);
1329
+ log(` Explorer: ${explorerUrl}`);
1330
+ log(` Error: ${receiptErr.message}`);
1344
1331
  if (qi + 1 < endIndex) {
1345
- errorOutput(` Trying next quote...`);
1332
+ log(` Trying next quote...`);
1346
1333
  lastQuoteError = `${quoteName} reverted on-chain`;
1347
1334
  continue;
1348
1335
  }
1349
- errorOutput(`\n The trading API reported success, but the contract execution failed.`);
1350
- errorOutput(` This can happen due to: stale quotes, insufficient gas, or liquidity changes.`);
1336
+ log(`\n The trading API reported success, but the contract execution failed.`);
1337
+ log(` This can happen due to: stale quotes, insufficient gas, or liquidity changes.`);
1351
1338
  exit(1);
1352
1339
  return;
1353
1340
  }
1354
1341
  }
1355
1342
 
1356
- errorOutput(`\n ✓ Transaction successful!`);
1357
- errorOutput(` Status: ${result.status}`);
1358
- errorOutput(` ${result.signature ? 'Signature' : 'Tx Hash'}: ${txId}`);
1359
- errorOutput(` Chain: ${chainConfig.name} (${result.chainType})`);
1360
- errorOutput(` Broadcaster: ${result.broadcaster}`);
1361
- errorOutput(` Explorer: ${explorerUrl}`);
1343
+ log(`\n ✓ Transaction successful!`);
1344
+ log(` Status: ${result.status}`);
1345
+ log(` ${result.signature ? 'Signature' : 'Tx Hash'}: ${txId}`);
1346
+ log(` Chain: ${chainConfig.name} (${result.chainType})`);
1347
+ log(` Broadcaster: ${result.broadcaster}`);
1348
+ log(` Explorer: ${explorerUrl}`);
1362
1349
 
1363
1350
  if (result.swapEvents?.length) {
1364
- errorOutput(` Swaps:`);
1351
+ log(` Swaps:`);
1365
1352
  result.swapEvents.forEach(e => {
1366
- errorOutput(` ${e.inputAmount} ${e.inputMint?.slice(0, 8)}... → ${e.outputAmount} ${e.outputMint?.slice(0, 8)}...`);
1353
+ log(` ${e.inputAmount} ${e.inputMint?.slice(0, 8)}... → ${e.outputAmount} ${e.outputMint?.slice(0, 8)}...`);
1367
1354
  });
1368
1355
  }
1369
- errorOutput('');
1356
+ log('');
1370
1357
  return undefined; // Success — done
1371
1358
  } else {
1372
- errorOutput(`\n ✗ Quote ${quoteName} failed: ${result.status}`);
1373
- if (result.error) errorOutput(` Error: ${result.error}`);
1359
+ log(`\n ✗ Quote ${quoteName} failed: ${result.status}`);
1360
+ if (result.error) log(` Error: ${result.error}`);
1374
1361
  lastQuoteError = `${quoteName}: ${result.error || result.status}`;
1375
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1362
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1376
1363
  }
1377
1364
 
1378
1365
  } catch (quoteErr) {
1379
- errorOutput(` ❌ Quote ${quoteName} failed: ${quoteErr.message}`);
1366
+ log(` ❌ Quote ${quoteName} failed: ${quoteErr.message}`);
1380
1367
  lastQuoteError = `${quoteName}: ${quoteErr.message}`;
1381
- if (qi + 1 < endIndex) errorOutput(` Trying next quote...`);
1368
+ if (qi + 1 < endIndex) log(` Trying next quote...`);
1382
1369
  }
1383
1370
  }
1384
1371
 
1385
1372
  // All quotes exhausted
1386
- errorOutput(`\n❌ All quotes failed. Last error: ${lastQuoteError || 'unknown'}`);
1387
- errorOutput('');
1373
+ log(`\n❌ All quotes failed. Last error: ${lastQuoteError || 'unknown'}`);
1374
+ log('');
1388
1375
  exit(1);
1389
1376
  return undefined;
1390
1377
 
1391
1378
  } catch (err) {
1392
- errorOutput(`Error: ${err.message}`);
1393
- if (err.details) errorOutput(` Details: ${JSON.stringify(err.details)}`);
1379
+ log(`Error: ${err.message}`);
1380
+ if (err.details) log(` Details: ${JSON.stringify(err.details)}`);
1394
1381
  exit(1);
1395
1382
  }
1396
1383
  },