package execution import ( "fmt" "math/big" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/your-org/mev-bot/pkg/arbitrage" ) // UniswapV2 Router address on Arbitrum var UniswapV2RouterAddress = common.HexToAddress("0x4752ba5dbc23f44d87826276bf6fd6b1c372ad24") // UniswapV2Encoder encodes transactions for UniswapV2-style DEXes type UniswapV2Encoder struct { routerAddress common.Address } // NewUniswapV2Encoder creates a new UniswapV2 encoder func NewUniswapV2Encoder() *UniswapV2Encoder { return &UniswapV2Encoder{ routerAddress: UniswapV2RouterAddress, } } // EncodeSwap encodes a single UniswapV2 swap func (e *UniswapV2Encoder) EncodeSwap( tokenIn common.Address, tokenOut common.Address, amountIn *big.Int, minAmountOut *big.Int, poolAddress common.Address, recipient common.Address, deadline time.Time, ) (common.Address, []byte, error) { // swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address[] path, address to, uint256 deadline) methodID := crypto.Keccak256([]byte("swapExactTokensForTokens(uint256,uint256,address[],address,uint256)"))[:4] // Build path array path := []common.Address{tokenIn, tokenOut} // Encode parameters data := make([]byte, 0) data = append(data, methodID...) // Offset to dynamic array (5 * 32 bytes) offset := padLeft(big.NewInt(160).Bytes(), 32) data = append(data, offset...) // amountIn data = append(data, padLeft(amountIn.Bytes(), 32)...) // amountOutMin data = append(data, padLeft(minAmountOut.Bytes(), 32)...) // to (recipient) data = append(data, padLeft(recipient.Bytes(), 32)...) // deadline deadlineUnix := big.NewInt(deadline.Unix()) data = append(data, padLeft(deadlineUnix.Bytes(), 32)...) // Path array length data = append(data, padLeft(big.NewInt(int64(len(path))).Bytes(), 32)...) // Path elements for _, addr := range path { data = append(data, padLeft(addr.Bytes(), 32)...) } return e.routerAddress, data, nil } // EncodeMultiHopSwap encodes a multi-hop UniswapV2 swap func (e *UniswapV2Encoder) EncodeMultiHopSwap( opp *arbitrage.Opportunity, recipient common.Address, minAmountOut *big.Int, deadline time.Time, ) (common.Address, []byte, error) { if len(opp.Path) < 2 { return common.Address{}, nil, fmt.Errorf("multi-hop requires at least 2 steps") } // Build token path from opportunity path path := make([]common.Address, len(opp.Path)+1) path[0] = opp.Path[0].TokenIn for i, step := range opp.Path { path[i+1] = step.TokenOut } // swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address[] path, address to, uint256 deadline) methodID := crypto.Keccak256([]byte("swapExactTokensForTokens(uint256,uint256,address[],address,uint256)"))[:4] data := make([]byte, 0) data = append(data, methodID...) // Offset to path array (5 * 32 bytes) offset := padLeft(big.NewInt(160).Bytes(), 32) data = append(data, offset...) // amountIn data = append(data, padLeft(opp.InputAmount.Bytes(), 32)...) // amountOutMin data = append(data, padLeft(minAmountOut.Bytes(), 32)...) // to (recipient) data = append(data, padLeft(recipient.Bytes(), 32)...) // deadline deadlineUnix := big.NewInt(deadline.Unix()) data = append(data, padLeft(deadlineUnix.Bytes(), 32)...) // Path array length data = append(data, padLeft(big.NewInt(int64(len(path))).Bytes(), 32)...) // Path elements for _, addr := range path { data = append(data, padLeft(addr.Bytes(), 32)...) } return e.routerAddress, data, nil } // EncodeSwapWithETH encodes a swap involving ETH func (e *UniswapV2Encoder) EncodeSwapWithETH( tokenIn common.Address, tokenOut common.Address, amountIn *big.Int, minAmountOut *big.Int, recipient common.Address, deadline time.Time, isETHInput bool, ) (common.Address, []byte, *big.Int, error) { var methodSig string var value *big.Int if isETHInput { // swapExactETHForTokens(uint256 amountOutMin, address[] path, address to, uint256 deadline) methodSig = "swapExactETHForTokens(uint256,address[],address,uint256)" value = amountIn } else { // swapExactTokensForETH(uint256 amountIn, uint256 amountOutMin, address[] path, address to, uint256 deadline) methodSig = "swapExactTokensForETH(uint256,uint256,address[],address,uint256)" value = big.NewInt(0) } methodID := crypto.Keccak256([]byte(methodSig))[:4] path := []common.Address{tokenIn, tokenOut} data := make([]byte, 0) data = append(data, methodID...) if isETHInput { // Offset to path array (4 * 32 bytes for ETH input) offset := padLeft(big.NewInt(128).Bytes(), 32) data = append(data, offset...) // amountOutMin data = append(data, padLeft(minAmountOut.Bytes(), 32)...) } else { // Offset to path array (5 * 32 bytes for token input) offset := padLeft(big.NewInt(160).Bytes(), 32) data = append(data, offset...) // amountIn data = append(data, padLeft(amountIn.Bytes(), 32)...) // amountOutMin data = append(data, padLeft(minAmountOut.Bytes(), 32)...) } // to (recipient) data = append(data, padLeft(recipient.Bytes(), 32)...) // deadline deadlineUnix := big.NewInt(deadline.Unix()) data = append(data, padLeft(deadlineUnix.Bytes(), 32)...) // Path array length data = append(data, padLeft(big.NewInt(int64(len(path))).Bytes(), 32)...) // Path elements for _, addr := range path { data = append(data, padLeft(addr.Bytes(), 32)...) } return e.routerAddress, data, value, nil } // padLeft pads bytes to the left with zeros to reach the specified length func padLeft(data []byte, length int) []byte { if len(data) >= length { return data } padded := make([]byte, length) copy(padded[length-len(data):], data) return padded }