class SwapInterface {
constructor(aggregator, walletManager) {
this.aggregator = aggregator;
this.walletManager = walletManager;
this.quotes = [];
this.selectedQuote = null;
}
async getSwapQuotes(tokenIn, tokenOut, amountIn) {
try {
// Show loading state
this.onLoadingStart('Getting quotes...');
// Get quotes from all DEXes
const quotes = await this.aggregator.getAllQuotes(tokenIn, tokenOut, amountIn);
// Filter and sort quotes
this.quotes = quotes
.filter(quote => quote.success && quote.amountOut > 0)
.sort((a, b) => parseFloat(b.amountOut) - parseFloat(a.amountOut))
.map(quote => ({
...quote,
dexName: this.getDEXName(quote.routerType),
amountOutFormatted: ethers.utils.formatUnits(quote.amountOut, 18),
priceImpactFormatted: `${(quote.priceImpact / 100).toFixed(2)}%`,
gasEstimateETH: ethers.utils.formatEther(quote.gasEstimate || 0)
}));
this.selectedQuote = this.quotes[0]; // Default to best quote
this.onQuotesUpdated(this.quotes);
return this.quotes;
} catch (error) {
this.onError('Failed to get quotes', error);
throw error;
} finally {
this.onLoadingEnd();
}
}
async executeSwap(slippageTolerance = 0.5) {
if (!this.selectedQuote) {
throw new Error('No quote selected');
}
try {
this.onLoadingStart('Executing swap...');
const { tokenIn, tokenOut, amountIn } = this.selectedQuote;
// Calculate minimum amount out with slippage
const minAmountOut = ethers.BigNumber.from(this.selectedQuote.amountOut)
.mul(Math.floor((100 - slippageTolerance) * 100))
.div(10000);
// Check and approve token if needed
await this.ensureTokenApproval(tokenIn, amountIn);
// Execute swap
const tx = await this.aggregator.executeSwap({
tokenIn,
tokenOut,
amountIn,
minAmountOut,
routerType: this.selectedQuote.routerType,
to: this.walletManager.account
});
this.onTransactionSubmitted(tx.hash);
// Wait for confirmation
const receipt = await tx.wait();
this.onTransactionConfirmed(receipt);
return receipt;
} catch (error) {
this.onError('Swap failed', error);
throw error;
} finally {
this.onLoadingEnd();
}
}
async ensureTokenApproval(tokenAddress, amount) {
if (tokenAddress === ethers.constants.AddressZero) {
return; // No approval needed for ETH
}
const tokenContract = new ethers.Contract(
tokenAddress,
['function allowance(address,address) view returns (uint256)', 'function approve(address,uint256) returns (bool)'],
this.walletManager.signer
);
const currentAllowance = await tokenContract.allowance(
this.walletManager.account,
this.aggregator.address
);
if (currentAllowance.lt(amount)) {
const approveTx = await tokenContract.approve(this.aggregator.address, amount);
await approveTx.wait();
this.onApprovalConfirmed(tokenAddress, amount);
}
}
getDEXName(routerType) {
const dexNames = {
0: 'Uniswap V2',
1: 'SushiSwap V2',
2: 'PancakeSwap V2',
3: 'QuickSwap',
10: 'Uniswap V3',
11: 'SushiSwap V3',
12: 'PancakeSwap V3',
20: 'Velodrome',
21: 'Aerodrome',
30: 'Curve',
35: 'Balancer V2',
36: '1inch'
};
return dexNames[routerType] || `Router ${routerType}`;
}
// Event handlers (override in implementation)
onLoadingStart(message) {
console.log('Loading:', message);
}
onLoadingEnd() {
console.log('Loading complete');
}
onQuotesUpdated(quotes) {
console.log('Quotes updated:', quotes.length);
}
onTransactionSubmitted(hash) {
console.log('Transaction submitted:', hash);
}
onTransactionConfirmed(receipt) {
console.log('Transaction confirmed:', receipt.transactionHash);
}
onApprovalConfirmed(token, amount) {
console.log('Approval confirmed:', token, amount);
}
onError(message, error) {
console.error(message, error);
}
}