163 lines
5.1 KiB
TypeScript
163 lines
5.1 KiB
TypeScript
/**
|
|
* Uniswap v3: Exact input swap via SwapRouter02
|
|
*
|
|
* This example demonstrates how to execute a swap on Uniswap v3
|
|
* using the SwapRouter02 contract.
|
|
*/
|
|
|
|
import { createWalletRpcClient } from '../../src/utils/chain-config.js';
|
|
import { getUniswapSwapRouter02 } from '../../src/utils/addresses.js';
|
|
import { getTokenMetadata, parseTokenAmount } from '../../src/utils/tokens.js';
|
|
import { waitForTransaction } from '../../src/utils/rpc.js';
|
|
import type { Address } from 'viem';
|
|
|
|
const CHAIN_ID = 1; // Mainnet (change to 8453 for Base, etc.)
|
|
const PRIVATE_KEY = process.env.PRIVATE_KEY as `0x${string}`;
|
|
|
|
// Uniswap v3 SwapRouter02 ABI
|
|
const SWAP_ROUTER_ABI = [
|
|
{
|
|
name: 'exactInputSingle',
|
|
type: 'function',
|
|
stateMutability: 'payable',
|
|
inputs: [
|
|
{
|
|
components: [
|
|
{ name: 'tokenIn', type: 'address' },
|
|
{ name: 'tokenOut', type: 'address' },
|
|
{ name: 'fee', type: 'uint24' },
|
|
{ name: 'recipient', type: 'address' },
|
|
{ name: 'deadline', type: 'uint256' },
|
|
{ name: 'amountIn', type: 'uint256' },
|
|
{ name: 'amountOutMinimum', type: 'uint256' },
|
|
{ name: 'sqrtPriceLimitX96', type: 'uint160' },
|
|
],
|
|
name: 'params',
|
|
type: 'tuple',
|
|
},
|
|
],
|
|
outputs: [{ name: 'amountOut', type: 'uint256' }],
|
|
},
|
|
{
|
|
name: 'exactInput',
|
|
type: 'function',
|
|
stateMutability: 'payable',
|
|
inputs: [
|
|
{
|
|
components: [
|
|
{
|
|
components: [
|
|
{ name: 'tokenIn', type: 'address' },
|
|
{ name: 'tokenOut', type: 'address' },
|
|
{ name: 'fee', type: 'uint24' },
|
|
],
|
|
name: 'path',
|
|
type: 'tuple[]',
|
|
},
|
|
{ name: 'recipient', type: 'address' },
|
|
{ name: 'deadline', type: 'uint256' },
|
|
{ name: 'amountIn', type: 'uint256' },
|
|
{ name: 'amountOutMinimum', type: 'uint256' },
|
|
],
|
|
name: 'params',
|
|
type: 'tuple',
|
|
},
|
|
],
|
|
outputs: [{ name: 'amountOut', type: 'uint256' }],
|
|
},
|
|
] as const;
|
|
|
|
// ERC20 ABI for approvals
|
|
const ERC20_ABI = [
|
|
{
|
|
name: 'approve',
|
|
type: 'function',
|
|
stateMutability: 'nonpayable',
|
|
inputs: [
|
|
{ name: 'spender', type: 'address' },
|
|
{ name: 'amount', type: 'uint256' },
|
|
],
|
|
outputs: [{ name: '', type: 'bool' }],
|
|
},
|
|
] as const;
|
|
|
|
// Uniswap v3 fee tiers (0.01%, 0.05%, 0.3%, 1%)
|
|
const FEE_TIER_LOW = 100; // 0.01%
|
|
const FEE_TIER_MEDIUM = 500; // 0.05%
|
|
const FEE_TIER_STANDARD = 3000; // 0.3%
|
|
const FEE_TIER_HIGH = 10000; // 1%
|
|
|
|
async function swapExactInputSingle() {
|
|
const walletClient = createWalletRpcClient(CHAIN_ID, PRIVATE_KEY);
|
|
const publicClient = walletClient as any;
|
|
const account = walletClient.account?.address;
|
|
|
|
if (!account) {
|
|
throw new Error('No account available');
|
|
}
|
|
|
|
const routerAddress = getUniswapSwapRouter02(CHAIN_ID);
|
|
|
|
// Token configuration
|
|
const tokenIn = getTokenMetadata(CHAIN_ID, 'USDC');
|
|
const tokenOut = getTokenMetadata(CHAIN_ID, 'WETH');
|
|
|
|
// Swap parameters
|
|
const amountIn = parseTokenAmount('1000', tokenIn.decimals); // 1000 USDC
|
|
const slippageTolerance = 50; // 0.5% in basis points (adjust based on market conditions)
|
|
const fee = FEE_TIER_STANDARD; // 0.3% fee tier (most liquid for major pairs)
|
|
const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); // 10 minutes
|
|
|
|
console.log(`Swapping ${amountIn} ${tokenIn.symbol} for ${tokenOut.symbol}`);
|
|
console.log(`Router: ${routerAddress}`);
|
|
console.log(`Fee tier: ${fee} (0.3%)`);
|
|
console.log(`Slippage tolerance: ${slippageTolerance / 100}%`);
|
|
|
|
// Step 1: Get quote (in production, use QuoterV2 contract)
|
|
// For now, we'll set amountOutMinimum to 0 (not recommended in production!)
|
|
// In production, always query the pool first to get expected output
|
|
const amountOutMinimum = 0n; // TODO: Query QuoterV2 for expected output and apply slippage
|
|
|
|
// Step 2: Approve token spending
|
|
console.log('\n1. Approving token spending...');
|
|
const approveTx = await walletClient.writeContract({
|
|
address: tokenIn.address,
|
|
abi: ERC20_ABI,
|
|
functionName: 'approve',
|
|
args: [routerAddress, amountIn],
|
|
});
|
|
await waitForTransaction(publicClient, approveTx);
|
|
console.log(`Approved: ${approveTx}`);
|
|
|
|
// Step 3: Execute swap
|
|
console.log('\n2. Executing swap...');
|
|
const swapTx = await walletClient.writeContract({
|
|
address: routerAddress,
|
|
abi: SWAP_ROUTER_ABI,
|
|
functionName: 'exactInputSingle',
|
|
args: [
|
|
{
|
|
tokenIn: tokenIn.address,
|
|
tokenOut: tokenOut.address,
|
|
fee,
|
|
recipient: account,
|
|
deadline,
|
|
amountIn,
|
|
amountOutMinimum,
|
|
sqrtPriceLimitX96: 0n, // No price limit
|
|
},
|
|
],
|
|
});
|
|
await waitForTransaction(publicClient, swapTx);
|
|
console.log(`Swap executed: ${swapTx}`);
|
|
console.log('\n✅ Swap completed successfully!');
|
|
}
|
|
|
|
// Run if executed directly
|
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
swapExactInputSingle().catch(console.error);
|
|
}
|
|
|
|
export { swapExactInputSingle };
|
|
|