nansen-cli 1.10.1 → 1.11.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 +20 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/api.js +18 -5
- package/src/cli.js +52 -22
- package/src/index.js +4 -4
- package/src/schema.json +197 -1
- package/src/trading.js +111 -111
- package/src/wallet.js +37 -39
- package/src/walletconnect-x402.js +3 -1
package/src/trading.js
CHANGED
|
@@ -756,7 +756,7 @@ export function formatQuote(quote, index) {
|
|
|
756
756
|
* Build trading command handlers for CLI integration.
|
|
757
757
|
*/
|
|
758
758
|
export function buildTradingCommands(deps = {}) {
|
|
759
|
-
const {
|
|
759
|
+
const { log = console.log, exit = process.exit } = deps;
|
|
760
760
|
|
|
761
761
|
return {
|
|
762
762
|
'quote': async (args, apiInstance, flags, options) => {
|
|
@@ -773,7 +773,7 @@ export function buildTradingCommands(deps = {}) {
|
|
|
773
773
|
const swapMode = options['swap-mode'] || 'exactIn';
|
|
774
774
|
|
|
775
775
|
if (!chain || !from || !to || !amount) {
|
|
776
|
-
|
|
776
|
+
log(`
|
|
777
777
|
Usage: nansen trade quote --chain <chain> --from <token> --to <token> --amount <baseUnits>
|
|
778
778
|
|
|
779
779
|
PREREQUISITE:
|
|
@@ -803,7 +803,7 @@ EXAMPLES:
|
|
|
803
803
|
|
|
804
804
|
const amountError = validateBaseUnitAmount(amount);
|
|
805
805
|
if (amountError) {
|
|
806
|
-
|
|
806
|
+
log(`Error: ${amountError}`);
|
|
807
807
|
exit(1);
|
|
808
808
|
return;
|
|
809
809
|
}
|
|
@@ -817,13 +817,13 @@ EXAMPLES:
|
|
|
817
817
|
let walletAddress;
|
|
818
818
|
if (isWalletConnect) {
|
|
819
819
|
if (chainType !== 'evm') {
|
|
820
|
-
|
|
820
|
+
log('WalletConnect is only supported for EVM chains');
|
|
821
821
|
exit(1);
|
|
822
822
|
return;
|
|
823
823
|
}
|
|
824
824
|
walletAddress = await getWalletConnectAddress();
|
|
825
825
|
if (!walletAddress) {
|
|
826
|
-
|
|
826
|
+
log('No WalletConnect session active. Run: walletconnect connect');
|
|
827
827
|
exit(1);
|
|
828
828
|
return;
|
|
829
829
|
}
|
|
@@ -839,16 +839,16 @@ EXAMPLES:
|
|
|
839
839
|
}
|
|
840
840
|
|
|
841
841
|
if (!walletAddress) {
|
|
842
|
-
|
|
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');
|
|
843
843
|
exit(1);
|
|
844
844
|
return;
|
|
845
845
|
}
|
|
846
846
|
|
|
847
|
-
|
|
848
|
-
|
|
847
|
+
log(`\nFetching quote on ${chainConfig.name}...`);
|
|
848
|
+
log(` Wallet: ${walletAddress}`);
|
|
849
849
|
|
|
850
850
|
const fromWarning = getWrappedNativeFromWarning(from, chain);
|
|
851
|
-
if (fromWarning)
|
|
851
|
+
if (fromWarning) log(` ${fromWarning}`);
|
|
852
852
|
|
|
853
853
|
const params = {
|
|
854
854
|
chainIndex: chainConfig.index,
|
|
@@ -865,30 +865,30 @@ EXAMPLES:
|
|
|
865
865
|
const response = await getQuote(params);
|
|
866
866
|
|
|
867
867
|
if (!response.success || !response.quotes?.length) {
|
|
868
|
-
|
|
868
|
+
log('No quotes available');
|
|
869
869
|
if (response.warnings?.length) {
|
|
870
|
-
response.warnings.forEach(w =>
|
|
870
|
+
response.warnings.forEach(w => log(` Warning: ${w}`));
|
|
871
871
|
}
|
|
872
872
|
exit(1);
|
|
873
873
|
return;
|
|
874
874
|
}
|
|
875
875
|
|
|
876
|
-
|
|
877
|
-
response.quotes.forEach((q, i) =>
|
|
876
|
+
log('');
|
|
877
|
+
response.quotes.forEach((q, i) => log(formatQuote(q, i)));
|
|
878
878
|
|
|
879
879
|
const quoteId = saveQuote(response, chain, isWalletConnect ? 'walletconnect' : 'local');
|
|
880
|
-
|
|
881
|
-
|
|
880
|
+
log(`\n Quote ID: ${quoteId}`);
|
|
881
|
+
log(` Execute: nansen trade execute --quote ${quoteId}`);
|
|
882
882
|
if (response.quotes.length > 1) {
|
|
883
|
-
|
|
883
|
+
log(` Pin #1: nansen trade execute --quote ${quoteId} --quote-index 0`);
|
|
884
884
|
}
|
|
885
885
|
|
|
886
886
|
if (response.quotes[0]?.approvalAddress && !isNativeToken(response.quotes[0]?.inputMint)) {
|
|
887
|
-
|
|
888
|
-
|
|
887
|
+
log(`\n Warning: This token swap requires an ERC-20 approval step.`);
|
|
888
|
+
log(` The execute command will handle this automatically.`);
|
|
889
889
|
}
|
|
890
890
|
|
|
891
|
-
|
|
891
|
+
log('');
|
|
892
892
|
return undefined; // Output already printed above
|
|
893
893
|
|
|
894
894
|
} catch (err) {
|
|
@@ -896,8 +896,8 @@ EXAMPLES:
|
|
|
896
896
|
if (err.code === 'INVALID_AMOUNT' || /amount/i.test(err.message)) {
|
|
897
897
|
message += '. Amounts must be in base units (e.g., 1000000000 lamports for 1 SOL, 1000000000000000000 wei for 1 ETH)';
|
|
898
898
|
}
|
|
899
|
-
|
|
900
|
-
if (err.details)
|
|
899
|
+
log(`Error: ${message}`);
|
|
900
|
+
if (err.details) log(` Details: ${JSON.stringify(err.details)}`);
|
|
901
901
|
exit(1);
|
|
902
902
|
}
|
|
903
903
|
},
|
|
@@ -908,7 +908,7 @@ EXAMPLES:
|
|
|
908
908
|
const noSimulate = flags['no-simulate'] || flags.noSimulate;
|
|
909
909
|
|
|
910
910
|
if (!quoteId) {
|
|
911
|
-
|
|
911
|
+
log(`
|
|
912
912
|
Usage: nansen trade execute --quote <quoteId> [options]
|
|
913
913
|
|
|
914
914
|
OPTIONS:
|
|
@@ -931,7 +931,7 @@ EXAMPLES:
|
|
|
931
931
|
|
|
932
932
|
const allQuotes = quoteData.response.quotes || [];
|
|
933
933
|
if (!allQuotes.length) {
|
|
934
|
-
|
|
934
|
+
log('❌ No quote data found');
|
|
935
935
|
exit(1);
|
|
936
936
|
return;
|
|
937
937
|
}
|
|
@@ -944,8 +944,8 @@ EXAMPLES:
|
|
|
944
944
|
// Check if any quote in range has transaction data before prompting for password
|
|
945
945
|
const hasAnyTransaction = allQuotes.slice(startIndex, endIndex).some(q => q?.transaction);
|
|
946
946
|
if (!hasAnyTransaction) {
|
|
947
|
-
|
|
948
|
-
|
|
947
|
+
log('❌ No quotes contain transaction data.');
|
|
948
|
+
log(' Ensure userWalletAddress was provided when fetching the quote.');
|
|
949
949
|
exit(1);
|
|
950
950
|
return;
|
|
951
951
|
}
|
|
@@ -965,7 +965,7 @@ EXAMPLES:
|
|
|
965
965
|
effectiveWalletName = list.defaultWallet;
|
|
966
966
|
}
|
|
967
967
|
if (!effectiveWalletName) {
|
|
968
|
-
|
|
968
|
+
log('No wallet found. Create one with: nansen wallet create');
|
|
969
969
|
exit(1);
|
|
970
970
|
return;
|
|
971
971
|
}
|
|
@@ -974,13 +974,13 @@ EXAMPLES:
|
|
|
974
974
|
} else {
|
|
975
975
|
// Verify WalletConnect session is still active and address matches quote
|
|
976
976
|
if (chainType !== 'evm') {
|
|
977
|
-
|
|
977
|
+
log('WalletConnect is only supported for EVM chains');
|
|
978
978
|
exit(1);
|
|
979
979
|
return;
|
|
980
980
|
}
|
|
981
981
|
const wcAddress = await getWalletConnectAddress();
|
|
982
982
|
if (!wcAddress) {
|
|
983
|
-
|
|
983
|
+
log('No WalletConnect session active. Run: walletconnect connect');
|
|
984
984
|
exit(1);
|
|
985
985
|
return;
|
|
986
986
|
}
|
|
@@ -988,7 +988,7 @@ EXAMPLES:
|
|
|
988
988
|
const quoteWallet = quoteData.response?.quotes?.[0]?.transaction?.from
|
|
989
989
|
|| quoteData.response?.metadata?.userWalletAddress;
|
|
990
990
|
if (quoteWallet && wcAddress.toLowerCase() !== quoteWallet.toLowerCase()) {
|
|
991
|
-
|
|
991
|
+
log(`Connected wallet (${wcAddress}) doesn't match quote. Get a new quote with --wallet walletconnect`);
|
|
992
992
|
exit(1);
|
|
993
993
|
return;
|
|
994
994
|
}
|
|
@@ -1004,17 +1004,17 @@ EXAMPLES:
|
|
|
1004
1004
|
|
|
1005
1005
|
// Verify transaction data exists
|
|
1006
1006
|
if (!currentQuote.transaction) {
|
|
1007
|
-
|
|
1007
|
+
log(` ⚠ Quote ${quoteName}: no transaction data, skipping...`);
|
|
1008
1008
|
lastQuoteError = `Quote ${quoteName} has no transaction data`;
|
|
1009
1009
|
continue;
|
|
1010
1010
|
}
|
|
1011
1011
|
|
|
1012
|
-
|
|
1012
|
+
log(`\nExecuting trade on ${chainConfig.name}...`);
|
|
1013
1013
|
if (endIndex - startIndex > 1) {
|
|
1014
|
-
|
|
1014
|
+
log(` Trying quote ${qi + 1}/${allQuotes.length} (${quoteName})...`);
|
|
1015
1015
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
1016
|
+
log(formatQuote(currentQuote));
|
|
1017
|
+
log('');
|
|
1018
1018
|
|
|
1019
1019
|
try {
|
|
1020
1020
|
let signedTransaction;
|
|
@@ -1027,7 +1027,7 @@ EXAMPLES:
|
|
|
1027
1027
|
if (typeof txBase64 === 'object' && txBase64.data) {
|
|
1028
1028
|
txBase64 = base58Decode(txBase64.data).toString('base64');
|
|
1029
1029
|
}
|
|
1030
|
-
|
|
1030
|
+
log(' Signing Solana transaction...');
|
|
1031
1031
|
signedTransaction = signSolanaTransaction(txBase64, exported.solana.privateKey);
|
|
1032
1032
|
requestId = currentQuote.metadata?.requestId;
|
|
1033
1033
|
|
|
@@ -1041,15 +1041,15 @@ EXAMPLES:
|
|
|
1041
1041
|
if (isNative) {
|
|
1042
1042
|
const expectedValue = BigInt(currentQuote.inAmount || currentQuote.inputAmount || '0');
|
|
1043
1043
|
if (txValue !== expectedValue) {
|
|
1044
|
-
|
|
1045
|
-
if (qi + 1 < endIndex)
|
|
1044
|
+
log(` ❌ Transaction value mismatch for ${quoteName}: tx.value=${txValue}, expected=${expectedValue}`);
|
|
1045
|
+
if (qi + 1 < endIndex) log(` Trying next quote...`);
|
|
1046
1046
|
lastQuoteError = `${quoteName} transaction value mismatch`;
|
|
1047
1047
|
continue;
|
|
1048
1048
|
}
|
|
1049
1049
|
} else {
|
|
1050
1050
|
if (txValue > 0n) {
|
|
1051
|
-
|
|
1052
|
-
if (qi + 1 < endIndex)
|
|
1051
|
+
log(` ❌ ERC-20 swap has non-zero tx.value (${txValue}) for ${quoteName} — aborting`);
|
|
1052
|
+
if (qi + 1 < endIndex) log(` Trying next quote...`);
|
|
1053
1053
|
lastQuoteError = `${quoteName} unexpected tx.value`;
|
|
1054
1054
|
continue;
|
|
1055
1055
|
}
|
|
@@ -1063,10 +1063,10 @@ EXAMPLES:
|
|
|
1063
1063
|
);
|
|
1064
1064
|
|
|
1065
1065
|
if (existingAllowance >= inputAmount && existingAllowance > 0n) {
|
|
1066
|
-
|
|
1066
|
+
log(` ✓ Sufficient allowance exists for ${quoteName}, skipping approval`);
|
|
1067
1067
|
} else {
|
|
1068
|
-
|
|
1069
|
-
|
|
1068
|
+
log(` ⚠ Approval required → ${currentQuote.approvalAddress}`);
|
|
1069
|
+
log(` Sending approval via WalletConnect...`);
|
|
1070
1070
|
try {
|
|
1071
1071
|
const approvalResult = await sendApprovalViaWalletConnect(
|
|
1072
1072
|
currentQuote.inputMint,
|
|
@@ -1076,7 +1076,7 @@ EXAMPLES:
|
|
|
1076
1076
|
let approvalTxHash = approvalResult.txHash;
|
|
1077
1077
|
if (!approvalTxHash && approvalResult.signedTransaction) {
|
|
1078
1078
|
// Wallet returned a signed tx instead of broadcasting — broadcast via Trading API
|
|
1079
|
-
|
|
1079
|
+
log(` Broadcasting approval via Trading API...`);
|
|
1080
1080
|
const broadcastResult = await executeTransaction({
|
|
1081
1081
|
signedTransaction: approvalResult.signedTransaction,
|
|
1082
1082
|
chain,
|
|
@@ -1088,18 +1088,18 @@ EXAMPLES:
|
|
|
1088
1088
|
approvalTxHash = broadcastResult.txHash;
|
|
1089
1089
|
}
|
|
1090
1090
|
if (approvalTxHash) {
|
|
1091
|
-
|
|
1091
|
+
log(` Waiting for approval confirmation...`);
|
|
1092
1092
|
const receipt = await waitForReceipt(chain, approvalTxHash);
|
|
1093
|
-
|
|
1093
|
+
log(` ✓ Approval confirmed in block ${parseInt(receipt.blockNumber, 16)}: ${approvalTxHash}`);
|
|
1094
1094
|
}
|
|
1095
1095
|
} catch (approvalErr) {
|
|
1096
|
-
|
|
1097
|
-
if (qi + 1 < endIndex)
|
|
1096
|
+
log(` ❌ Approval failed for ${quoteName}: ${approvalErr.message}`);
|
|
1097
|
+
if (qi + 1 < endIndex) log(` Trying next quote...`);
|
|
1098
1098
|
lastQuoteError = `${quoteName} approval failed`;
|
|
1099
1099
|
continue;
|
|
1100
1100
|
}
|
|
1101
1101
|
await new Promise(r => setTimeout(r, 2000));
|
|
1102
|
-
|
|
1102
|
+
log('');
|
|
1103
1103
|
}
|
|
1104
1104
|
}
|
|
1105
1105
|
|
|
@@ -1113,8 +1113,8 @@ EXAMPLES:
|
|
|
1113
1113
|
value: txData.value ? '0x' + BigInt(txData.value).toString(16) : '0x0',
|
|
1114
1114
|
});
|
|
1115
1115
|
if (!sim.success) {
|
|
1116
|
-
|
|
1117
|
-
if (qi + 1 < endIndex)
|
|
1116
|
+
log(` ⚠ Simulation failed for ${quoteName}: ${sim.reason}`);
|
|
1117
|
+
if (qi + 1 < endIndex) log(` Trying next quote...`);
|
|
1118
1118
|
lastQuoteError = `${quoteName} simulation failed: ${sim.reason}`;
|
|
1119
1119
|
continue;
|
|
1120
1120
|
}
|
|
@@ -1127,7 +1127,7 @@ EXAMPLES:
|
|
|
1127
1127
|
const finalGas = apiGas > 0 ? apiGas : txGas;
|
|
1128
1128
|
|
|
1129
1129
|
// Send transaction via WalletConnect
|
|
1130
|
-
|
|
1130
|
+
log(' Sending transaction via WalletConnect...');
|
|
1131
1131
|
let wcResult;
|
|
1132
1132
|
try {
|
|
1133
1133
|
wcResult = await sendTransactionViaWalletConnect({
|
|
@@ -1138,24 +1138,24 @@ EXAMPLES:
|
|
|
1138
1138
|
chainId: chainConfig.chainId,
|
|
1139
1139
|
});
|
|
1140
1140
|
} catch (wcErr) {
|
|
1141
|
-
|
|
1142
|
-
if (qi + 1 < endIndex)
|
|
1141
|
+
log(` ❌ WalletConnect transaction failed for ${quoteName}: ${wcErr.message}`);
|
|
1142
|
+
if (qi + 1 < endIndex) log(` Trying next quote...`);
|
|
1143
1143
|
lastQuoteError = `${quoteName}: ${wcErr.message}`;
|
|
1144
1144
|
continue;
|
|
1145
1145
|
}
|
|
1146
1146
|
|
|
1147
1147
|
if (wcResult.txHash) {
|
|
1148
1148
|
// Wallet broadcast — verify on-chain
|
|
1149
|
-
|
|
1149
|
+
log(' Verifying on-chain status...');
|
|
1150
1150
|
try {
|
|
1151
1151
|
await waitForReceipt(chain, wcResult.txHash);
|
|
1152
1152
|
} catch (receiptErr) {
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
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}`);
|
|
1157
1157
|
if (qi + 1 < endIndex) {
|
|
1158
|
-
|
|
1158
|
+
log(` Trying next quote...`);
|
|
1159
1159
|
lastQuoteError = `${quoteName} reverted on-chain`;
|
|
1160
1160
|
continue;
|
|
1161
1161
|
}
|
|
@@ -1163,11 +1163,11 @@ EXAMPLES:
|
|
|
1163
1163
|
return;
|
|
1164
1164
|
}
|
|
1165
1165
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
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('');
|
|
1171
1171
|
return undefined; // Success
|
|
1172
1172
|
}
|
|
1173
1173
|
|
|
@@ -1191,15 +1191,15 @@ EXAMPLES:
|
|
|
1191
1191
|
if (isNative) {
|
|
1192
1192
|
const expectedValue = BigInt(currentQuote.inAmount || currentQuote.inputAmount || '0');
|
|
1193
1193
|
if (txValue !== expectedValue) {
|
|
1194
|
-
|
|
1195
|
-
if (qi + 1 < endIndex)
|
|
1194
|
+
log(` ❌ Transaction value mismatch for ${quoteName}: tx.value=${txValue}, expected=${expectedValue}`);
|
|
1195
|
+
if (qi + 1 < endIndex) log(` Trying next quote...`);
|
|
1196
1196
|
lastQuoteError = `${quoteName} transaction value mismatch`;
|
|
1197
1197
|
continue;
|
|
1198
1198
|
}
|
|
1199
1199
|
} else {
|
|
1200
1200
|
if (txValue > 0n) {
|
|
1201
|
-
|
|
1202
|
-
if (qi + 1 < endIndex)
|
|
1201
|
+
log(` ❌ ERC-20 swap has non-zero tx.value (${txValue}) for ${quoteName} — aborting`);
|
|
1202
|
+
if (qi + 1 < endIndex) log(` Trying next quote...`);
|
|
1203
1203
|
lastQuoteError = `${quoteName} unexpected tx.value`;
|
|
1204
1204
|
continue;
|
|
1205
1205
|
}
|
|
@@ -1213,10 +1213,10 @@ EXAMPLES:
|
|
|
1213
1213
|
);
|
|
1214
1214
|
|
|
1215
1215
|
if (existingAllowance >= inputAmount && existingAllowance > 0n) {
|
|
1216
|
-
|
|
1216
|
+
log(` ✓ Sufficient allowance exists for ${quoteName}, skipping approval`);
|
|
1217
1217
|
} else {
|
|
1218
|
-
|
|
1219
|
-
|
|
1218
|
+
log(` ⚠ Approval required → ${currentQuote.approvalAddress}`);
|
|
1219
|
+
log(` Sending approval tx...`);
|
|
1220
1220
|
const approvalNonce = await getEvmNonce(chain, walletAddress);
|
|
1221
1221
|
|
|
1222
1222
|
const approvalGasPrice = currentQuote.transaction?.gasPrice || currentQuote.transaction?.maxFeePerGas || '1000000';
|
|
@@ -1236,25 +1236,25 @@ EXAMPLES:
|
|
|
1236
1236
|
});
|
|
1237
1237
|
|
|
1238
1238
|
if (approvalResult.status !== 'Success') {
|
|
1239
|
-
|
|
1240
|
-
if (qi + 1 < endIndex)
|
|
1239
|
+
log(` ❌ Approval failed for ${quoteName}: ${approvalResult.error || 'unknown error'}`);
|
|
1240
|
+
if (qi + 1 < endIndex) log(` Trying next quote...`);
|
|
1241
1241
|
lastQuoteError = `${quoteName} approval failed`;
|
|
1242
1242
|
continue;
|
|
1243
1243
|
}
|
|
1244
1244
|
|
|
1245
|
-
|
|
1245
|
+
log(` Waiting for approval confirmation...`);
|
|
1246
1246
|
try {
|
|
1247
1247
|
const receipt = await waitForReceipt(chain, approvalResult.txHash);
|
|
1248
|
-
|
|
1248
|
+
log(` ✓ Approval confirmed in block ${parseInt(receipt.blockNumber, 16)}: ${approvalResult.txHash}`);
|
|
1249
1249
|
} catch (receiptErr) {
|
|
1250
|
-
|
|
1251
|
-
if (qi + 1 < endIndex)
|
|
1250
|
+
log(` ❌ Approval may not have confirmed: ${receiptErr.message}`);
|
|
1251
|
+
if (qi + 1 < endIndex) log(` Trying next quote...`);
|
|
1252
1252
|
lastQuoteError = `${quoteName} approval unconfirmed`;
|
|
1253
1253
|
continue;
|
|
1254
1254
|
}
|
|
1255
1255
|
// Wait for RPC state propagation after approval
|
|
1256
1256
|
await new Promise(r => setTimeout(r, 2000));
|
|
1257
|
-
|
|
1257
|
+
log('');
|
|
1258
1258
|
}
|
|
1259
1259
|
}
|
|
1260
1260
|
|
|
@@ -1270,8 +1270,8 @@ EXAMPLES:
|
|
|
1270
1270
|
value: txData.value ? '0x' + BigInt(txData.value).toString(16) : '0x0',
|
|
1271
1271
|
});
|
|
1272
1272
|
if (!sim.success) {
|
|
1273
|
-
|
|
1274
|
-
if (qi + 1 < endIndex)
|
|
1273
|
+
log(` ⚠ Simulation failed for ${quoteName}: ${sim.reason}`);
|
|
1274
|
+
if (qi + 1 < endIndex) log(` Trying next quote...`);
|
|
1275
1275
|
lastQuoteError = `${quoteName} simulation failed: ${sim.reason}`;
|
|
1276
1276
|
continue;
|
|
1277
1277
|
}
|
|
@@ -1285,17 +1285,17 @@ EXAMPLES:
|
|
|
1285
1285
|
const txGas = parseInt(txData.gas || txData.gasLimit || "0");
|
|
1286
1286
|
const finalGas = apiGas > 0 ? apiGas : txGas;
|
|
1287
1287
|
if (finalGas !== txGas) {
|
|
1288
|
-
|
|
1288
|
+
log(` ℹ Using API gas ${finalGas} (tx.gas was ${txGas})`);
|
|
1289
1289
|
}
|
|
1290
1290
|
if (txData.gasLimit) txData.gasLimit = String(finalGas);
|
|
1291
1291
|
else txData.gas = String(finalGas);
|
|
1292
1292
|
|
|
1293
|
-
|
|
1293
|
+
log(' Fetching nonce...');
|
|
1294
1294
|
await new Promise(r => setTimeout(r, 1000));
|
|
1295
1295
|
const nonce = await getEvmNonce(chain, walletAddress);
|
|
1296
|
-
|
|
1296
|
+
log(` Nonce: ${nonce}`);
|
|
1297
1297
|
|
|
1298
|
-
|
|
1298
|
+
log(' Signing EVM transaction...');
|
|
1299
1299
|
signedTransaction = signEvmTransaction(
|
|
1300
1300
|
currentQuote.transaction,
|
|
1301
1301
|
exported.evm.privateKey,
|
|
@@ -1304,7 +1304,7 @@ EXAMPLES:
|
|
|
1304
1304
|
);
|
|
1305
1305
|
}
|
|
1306
1306
|
|
|
1307
|
-
|
|
1307
|
+
log(' Broadcasting...');
|
|
1308
1308
|
const execParams = {
|
|
1309
1309
|
signedTransaction,
|
|
1310
1310
|
chain,
|
|
@@ -1320,64 +1320,64 @@ EXAMPLES:
|
|
|
1320
1320
|
|
|
1321
1321
|
// For EVM: verify the tx actually succeeded on-chain
|
|
1322
1322
|
if (chainType === 'evm' && result.txHash) {
|
|
1323
|
-
|
|
1323
|
+
log(' Verifying on-chain status...');
|
|
1324
1324
|
try {
|
|
1325
1325
|
await waitForReceipt(chain, result.txHash);
|
|
1326
1326
|
} catch (receiptErr) {
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
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}`);
|
|
1331
1331
|
if (qi + 1 < endIndex) {
|
|
1332
|
-
|
|
1332
|
+
log(` Trying next quote...`);
|
|
1333
1333
|
lastQuoteError = `${quoteName} reverted on-chain`;
|
|
1334
1334
|
continue;
|
|
1335
1335
|
}
|
|
1336
|
-
|
|
1337
|
-
|
|
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.`);
|
|
1338
1338
|
exit(1);
|
|
1339
1339
|
return;
|
|
1340
1340
|
}
|
|
1341
1341
|
}
|
|
1342
1342
|
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
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}`);
|
|
1349
1349
|
|
|
1350
1350
|
if (result.swapEvents?.length) {
|
|
1351
|
-
|
|
1351
|
+
log(` Swaps:`);
|
|
1352
1352
|
result.swapEvents.forEach(e => {
|
|
1353
|
-
|
|
1353
|
+
log(` ${e.inputAmount} ${e.inputMint?.slice(0, 8)}... → ${e.outputAmount} ${e.outputMint?.slice(0, 8)}...`);
|
|
1354
1354
|
});
|
|
1355
1355
|
}
|
|
1356
|
-
|
|
1356
|
+
log('');
|
|
1357
1357
|
return undefined; // Success — done
|
|
1358
1358
|
} else {
|
|
1359
|
-
|
|
1360
|
-
if (result.error)
|
|
1359
|
+
log(`\n ✗ Quote ${quoteName} failed: ${result.status}`);
|
|
1360
|
+
if (result.error) log(` Error: ${result.error}`);
|
|
1361
1361
|
lastQuoteError = `${quoteName}: ${result.error || result.status}`;
|
|
1362
|
-
if (qi + 1 < endIndex)
|
|
1362
|
+
if (qi + 1 < endIndex) log(` Trying next quote...`);
|
|
1363
1363
|
}
|
|
1364
1364
|
|
|
1365
1365
|
} catch (quoteErr) {
|
|
1366
|
-
|
|
1366
|
+
log(` ❌ Quote ${quoteName} failed: ${quoteErr.message}`);
|
|
1367
1367
|
lastQuoteError = `${quoteName}: ${quoteErr.message}`;
|
|
1368
|
-
if (qi + 1 < endIndex)
|
|
1368
|
+
if (qi + 1 < endIndex) log(` Trying next quote...`);
|
|
1369
1369
|
}
|
|
1370
1370
|
}
|
|
1371
1371
|
|
|
1372
1372
|
// All quotes exhausted
|
|
1373
|
-
|
|
1374
|
-
|
|
1373
|
+
log(`\n❌ All quotes failed. Last error: ${lastQuoteError || 'unknown'}`);
|
|
1374
|
+
log('');
|
|
1375
1375
|
exit(1);
|
|
1376
1376
|
return undefined;
|
|
1377
1377
|
|
|
1378
1378
|
} catch (err) {
|
|
1379
|
-
|
|
1380
|
-
if (err.details)
|
|
1379
|
+
log(`Error: ${err.message}`);
|
|
1380
|
+
if (err.details) log(` Details: ${JSON.stringify(err.details)}`);
|
|
1381
1381
|
exit(1);
|
|
1382
1382
|
}
|
|
1383
1383
|
},
|