@okx-dex/okx-dex-sdk
Version:
OKX DEX SDK
187 lines (186 loc) • 9.22 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.EVMSwapExecutor = void 0;
class EVMSwapExecutor {
constructor(config, networkConfig) {
var _a;
this.config = config;
this.networkConfig = networkConfig;
this.DEFAULT_GAS_MULTIPLIER = BigInt(150); // 1.5x
if (!((_a = this.config.evm) === null || _a === void 0 ? void 0 : _a.wallet)) {
throw new Error("EVM configuration required");
}
this.provider = this.config.evm.wallet.provider;
}
async executeSwap(swapData, params) {
var _a;
const quoteData = (_a = swapData.data) === null || _a === void 0 ? void 0 : _a[0];
if (!(quoteData === null || quoteData === void 0 ? void 0 : quoteData.routerResult)) {
throw new Error("Invalid swap data: missing router result");
}
const { routerResult } = quoteData;
const tx = quoteData.tx;
if (!tx) {
throw new Error("Missing transaction data");
}
try {
const result = await this.executeEvmTransaction(tx);
return this.formatSwapResult(result.hash, routerResult);
}
catch (error) {
console.error("Swap execution failed:", error);
throw error;
}
}
async executeEvmTransaction(tx) {
var _a, _b;
if (!((_a = this.config.evm) === null || _a === void 0 ? void 0 : _a.wallet)) {
throw new Error("EVM wallet required");
}
let retryCount = 0;
while (retryCount < (this.networkConfig.maxRetries || 3)) {
try {
console.log("Preparing transaction...");
const gasMultiplier = BigInt(500); // 5x standard multiplier
// Get current nonce
const nonce = await this.provider.getTransactionCount(this.config.evm.wallet.address);
const baseTransaction = {
data: tx.data,
to: tx.to,
value: tx.value || '0',
nonce: nonce + retryCount, // Increment nonce for each retry
gasLimit: BigInt(tx.gas || 0) * gasMultiplier / BigInt(100),
};
// Prefer gas prices from the API response, which already reflect the
// requested gasLevel (slow / standard / fast). Fall back to fetching
// current network fee data only when the API returns empty/zero values.
const apiGasPrice = tx.gasPrice && tx.gasPrice !== '0'
? BigInt(tx.gasPrice) : null;
const apiMaxPriorityFee = tx.maxPriorityFeePerGas && tx.maxPriorityFeePerGas !== '0'
? BigInt(tx.maxPriorityFeePerGas) : null;
let gasPriceFields;
if (apiGasPrice !== null) {
if (apiMaxPriorityFee !== null) {
// EIP-1559: both fields present in API response
gasPriceFields = {
maxFeePerGas: apiGasPrice,
maxPriorityFeePerGas: apiMaxPriorityFee,
};
}
else {
// Legacy chain: only gasPrice present
gasPriceFields = {
gasPrice: apiGasPrice,
};
}
}
else {
// Fallback: fetch current gas prices from the network
const feeData = await this.provider.getFeeData();
if (feeData.maxFeePerGas != null && feeData.maxPriorityFeePerGas != null) {
// EIP-1559 chain
gasPriceFields = {
maxFeePerGas: (feeData.maxFeePerGas * gasMultiplier) / BigInt(100),
maxPriorityFeePerGas: (feeData.maxPriorityFeePerGas * gasMultiplier) / BigInt(100),
};
}
else {
// Legacy chain (e.g. BSC)
const legacyGasPrice = (_b = feeData.gasPrice) !== null && _b !== void 0 ? _b : BigInt(3000000000);
gasPriceFields = {
gasPrice: (legacyGasPrice * gasMultiplier) / BigInt(100),
};
}
}
const transaction = { ...baseTransaction, ...gasPriceFields };
console.log("Transaction details:", {
to: transaction.to,
value: transaction.value,
nonce: transaction.nonce,
gasLimit: transaction.gasLimit.toString(),
...Object.fromEntries(Object.entries(gasPriceFields).map(([k, v]) => [k, v.toString()])),
});
console.log("Sending transaction...");
const response = await this.config.evm.wallet.sendTransaction(transaction);
console.log("Transaction sent! Hash:", response.hash);
// Wait a bit before checking status to allow transaction to be mined
await new Promise(resolve => setTimeout(resolve, 5000));
console.log("Waiting for transaction confirmation...");
try {
// Poll for transaction status
let receipt = null;
let attempts = 0;
const maxAttempts = 30; // 30 attempts * 2 seconds = 60 seconds total
while (attempts < maxAttempts) {
receipt = await this.provider.getTransactionReceipt(response.hash);
if (receipt) {
console.log("Transaction confirmed! Block number:", receipt.blockNumber);
return receipt;
}
// Check if transaction is still pending
const tx = await this.provider.getTransaction(response.hash);
if (!tx) {
// Check if we're on a different network than expected
const network = await this.provider.getNetwork();
console.error(`Transaction dropped. Network: ${network.name} (${network.chainId})`);
throw new Error("Transaction dropped - check network and gas prices");
}
console.log(`Transaction still pending... (attempt ${attempts + 1}/${maxAttempts})`);
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds between checks
attempts++;
}
throw new Error("Transaction confirmation timed out - check explorer for status");
}
catch (waitError) {
console.error("Error waiting for confirmation:", waitError.message);
throw waitError;
}
}
catch (error) {
retryCount++;
console.error(`Transaction attempt ${retryCount} failed:`, error.message);
if (error.code === 'INSUFFICIENT_FUNDS') {
throw new Error("Insufficient funds for transaction");
}
if (error.code === 'NONCE_EXPIRED') {
throw new Error("Transaction nonce expired");
}
if (retryCount === this.networkConfig.maxRetries) {
console.error("Max retries reached. Last error:", error);
throw error;
}
const delay = 2000 * retryCount;
console.log(`Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error('Max retries exceeded');
}
formatSwapResult(txHash, routerResult) {
const fromDecimals = parseInt(routerResult.fromToken.decimal);
const toDecimals = parseInt(routerResult.toToken.decimal);
const displayFromAmount = (Number(routerResult.fromTokenAmount) /
Math.pow(10, fromDecimals)).toFixed(6);
const displayToAmount = (Number(routerResult.toTokenAmount) /
Math.pow(10, toDecimals)).toFixed(6);
return {
success: true,
transactionId: txHash,
explorerUrl: `${this.networkConfig.explorer}/${txHash}`,
details: {
fromToken: {
symbol: routerResult.fromToken.tokenSymbol,
amount: displayFromAmount,
decimal: routerResult.fromToken.decimal,
},
toToken: {
symbol: routerResult.toToken.tokenSymbol,
amount: displayToAmount,
decimal: routerResult.toToken.decimal,
},
priceImpact: routerResult.priceImpactPercent,
},
};
}
}
exports.EVMSwapExecutor = EVMSwapExecutor;