diff --git a/config.yaml b/config.yaml index 839f47d..a4b8bd1 100644 --- a/config.yaml +++ b/config.yaml @@ -3,10 +3,10 @@ ethereum_node_url: "http://localhost:8545" chain_id: 31337 # Local development chain ID # Contract Addresses -swap_router_address: "0x8f86403A4DE0BB5791fa46B8e795C547942fE4Cf" -lp_router_address: "0x0E801D84Fa97b50751Dbf25036d067dCf18858bF" -manager_address: "0x4826533B4897376654Bb4d4AD88B7faFD0C98528" -hook_address: "0x2725685Ef2DefFBa748CAFF8665985BE635B8aC0" +swap_router_address: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" +lp_router_address: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" +manager_address: "0x5FbDB2315678afecb367f032d93F642f64180aa3" +hook_address: "0xD3F41c806202a1517a9B746F93B4f7cBd3468AC0" # Account Configuration (dont update this) private_key: "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" @@ -20,8 +20,8 @@ gas_limit: 500000 gas_price: 20 # 20 Gwei # Token Addresses -token0_address: "0x36C02dA8a0983159322a80FFE9F24b1acfF8B570" -token1_address: "0x5eb3Bc0a489C5A8288765d2336659EbCA68FCd00" +token0_address: "0x0165878A594ca255338adfa4d48449f69242Eb8F" +token1_address: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" # Pool Configuration default_fee: 3000 diff --git a/contracts/v4-hook/remappings.txt b/contracts/v4-hook/remappings.txt index 0bbe91e..6fb2208 100644 --- a/contracts/v4-hook/remappings.txt +++ b/contracts/v4-hook/remappings.txt @@ -1,6 +1,6 @@ @uniswap/v4-core/=lib/v4-core/ forge-gas-snapshot/=lib/v4-core/lib/forge-gas-snapshot/src/ -forge-std/=lib/v4-core/lib/forge-std/src/ +forge-std/=lib/forge-std/src/ permit2/=lib/v4-periphery/lib/permit2/ solmate/=lib/v4-core/lib/solmate/ v4-core/=lib/v4-core/ diff --git a/contracts/v4-hook/script/Anvil.s.sol b/contracts/v4-hook/script/Anvil.s.sol index 8470853..136feca 100644 --- a/contracts/v4-hook/script/Anvil.s.sol +++ b/contracts/v4-hook/script/Anvil.s.sol @@ -105,7 +105,7 @@ contract CounterScript is Script { console.log("TOKEN B", address(token1)); } - function testLifecycleWithPermit( + function testLifecycleWithPermit( IPoolManager manager, address hook, PoolModifyLiquidityTest lpRouter, @@ -113,85 +113,148 @@ contract CounterScript is Script { ) internal { (MOCKERC20PERMIT token0, MOCKERC20PERMIT token1) = deployTokens(); - // Transfer tokens to Alice + // Mint tokens as before token0.mint(msg.sender, 100_000 ether); token1.mint(msg.sender, 100_000 ether); token0.mint(alice, 100_000 ether); token1.mint(alice, 100_000 ether); - token0.mint(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266), 100_000 ether); - token1.mint(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266), 100_000 ether); - - // bytes memory ZERO_BYTES = new bytes(0); - - // // Initialize the pool - // int24 tickSpacing = 60; - // PoolKey memory poolKey = - // PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 3000, tickSpacing, IHooks(hook)); - // manager.initialize(poolKey, Constants.SQRT_PRICE_1_1, ZERO_BYTES); - - token0.approve(address(lpRouter), type(uint256).max); - token1.approve(address(lpRouter), type(uint256).max); - - // lpRouter.modifyLiquidity( - // poolKey, - // IPoolManager.ModifyLiquidityParams( - // TickMath.minUsableTick(tickSpacing), TickMath.maxUsableTick(tickSpacing), 100 ether, 0 - // ), - // ZERO_BYTES - // ); - - // // Prepare swap parameters - // bool zeroForOne = true; - // int256 amountSpecified = 1 ether; - // IPoolManager.SwapParams memory params = IPoolManager.SwapParams({ - // zeroForOne: zeroForOne, - // amountSpecified: amountSpecified, - // sqrtPriceLimitX96: zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1 - // }); - // PoolSwapTest.TestSettings memory testSettings = - // PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); - - // // Prepare permit data - // uint256 deadline = block.timestamp + 3600; // 1 hour from now - // uint256 value = uint256(amountSpecified) * 11 / 10; // Increase by 10% to account for fees and slippage - - // console.log("VALUE", value); - // console.log("swap router", address(swapRouter)); - // // vm.startPrank(alice); - - // (uint8 v, bytes32 r, bytes32 s) = generatePermitSignature( - // IERC20Permit(address(token0)), alice, address(swapRouter), value, deadline, alicePrivateKey - // ); - - // // vm.stopPrank(); - - // console.log("Alice address:", alice); - // console.log("bob address", address(bob)); - - // // Perform the swap with permit (as bob, the relayer) - // vm.startBroadcast(bob); - - // // Then, perform the swap on behalf of Alice - // // Call swapWithPermit - // swapRouter.swapWithPermit( - // alice, // user - // poolKey, - // params, - // testSettings, - // ZERO_BYTES, // hookData - // deadline, - // v, - // r, - // s - // ); - - // vm.stopBroadcast(); - - // // Verify the swap results (you may want to add more assertions) - // console.log("Swap completed successfully"); - // console.log("Token0 balance of Alice:", token0.balanceOf(alice)); - // console.log("Token1 balance of Alice:", token1.balanceOf(alice)); + + bytes memory ZERO_BYTES = new bytes(0); + int24 tickSpacing = 60; + PoolKey memory poolKey = PoolKey( + Currency.wrap(address(token0)), + Currency.wrap(address(token1)), + 3000, + tickSpacing, + IHooks(hook) + ); + + manager.initialize(poolKey, Constants.SQRT_PRICE_1_1, ZERO_BYTES); + + // Generate signatures for adding liquidity + console.log("\n=== ADD LIQUIDITY PERMIT PARAMETERS ==="); + console.log("User Address:", alice); + console.log("Token0:", address(token0)); + console.log("Token1:", address(token1)); + console.log("LP Router:", address(lpRouter)); + + uint256 lpAmount = 100 ether; + uint256 lpDeadline = block.timestamp + 3600; + console.log("Amount:", lpAmount); + console.log("Deadline:", lpDeadline); + + // Get permit signatures for both tokens for LP + (uint8 v0, bytes32 r0, bytes32 s0) = generatePermitSignature( + IERC20Permit(address(token0)), + alice, + address(lpRouter), + lpAmount, + lpDeadline, + alicePrivateKey + ); + + (uint8 v1, bytes32 r1, bytes32 s1) = generatePermitSignature( + IERC20Permit(address(token1)), + alice, + address(lpRouter), + lpAmount, + lpDeadline, + alicePrivateKey + ); + + console.log("\nToken0 Signature:"); + console.log("v:", uint256(v0)); + console.log("r: 0x%s", vm.toString(r0)); + console.log("s: 0x%s", vm.toString(s0)); + + console.log("\nToken1 Signature:"); + console.log("v:", uint256(v1)); + console.log("r: 0x%s", vm.toString(r1)); + console.log("s: 0x%s", vm.toString(s1)); + + console.log("\nJSON Format for Add Liquidity:"); + console.log("{"); + console.log(' "currency0": "%s",', address(token0)); + console.log(' "currency1": "%s",', address(token1)); + console.log(' "amount": "%d",', lpAmount); + console.log(' "userAddress": "%s",', alice); + console.log(' "deadline": "%d",', lpDeadline); + console.log(' "v0": %d,', uint256(v0)); + console.log(' "r0": "0x%s",', vm.toString(r0)); + console.log(' "s0": "0x%s",', vm.toString(s0)); + console.log(' "v1": %d,', uint256(v1)); + console.log(' "r1": "0x%s",', vm.toString(r1)); + console.log(' "s1": "0x%s"', vm.toString(s1)); + console.log("}"); + + // Generate swap permit signature + console.log("\n=== SWAP PERMIT PARAMETERS ==="); + int256 swapAmount = 1 ether; + uint256 swapDeadline = block.timestamp + 3600; + bool zeroForOne = true; + uint256 swapNonce = token0.nonces(alice); + + bytes32 SWAP_TYPEHASH = keccak256( + "SwapWithPermit(address owner,address currency0,address currency1,int256 amountSpecified,bool zeroForOne,uint256 sqrtPriceLimitX96,uint256 nonce,uint256 deadline)" + ); + + bytes32 domainSeparator = token0.DOMAIN_SEPARATOR(); + uint256 sqrtPriceLimitX96 = zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1; + + bytes32 structHash = keccak256( + abi.encode( + SWAP_TYPEHASH, + alice, + address(token0), + address(token1), + swapAmount, + zeroForOne, + sqrtPriceLimitX96, + swapNonce, + swapDeadline + ) + ); + + bytes32 digest = keccak256( + abi.encodePacked("\x19\x01", domainSeparator, structHash) + ); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePrivateKey, digest); + + console.log("User Address:", alice); + console.log("Token0:", address(token0)); + console.log("Token1:", address(token1)); + console.log("Swap Router:", address(swapRouter)); + console.log("Amount:", swapAmount); + console.log("ZeroForOne:", zeroForOne); + console.log("Deadline:", swapDeadline); + console.log("\nSwap Signature:"); + console.log("v:", uint256(v)); + console.log("r: 0x%s", vm.toString(r)); + console.log("s: 0x%s", vm.toString(s)); + + console.log("\nJSON Format for Swap:"); + console.log("{"); + console.log(' "currency0": "%s",', address(token0)); + console.log(' "currency1": "%s",', address(token1)); + console.log(' "amount": "%d",', swapAmount); + console.log(' "zeroForOne": %s,', zeroForOne ? "true" : "false"); + console.log(' "userAddress": "%s",', alice); + console.log(' "deadline": "%d",', swapDeadline); + console.log(' "v": %d,', uint256(v)); + console.log(' "r": "0x%s",', vm.toString(r)); + console.log(' "s": "0x%s"', vm.toString(s)); + console.log("}"); + + // Additional debug information + console.log("\n=== DEBUG INFORMATION ==="); + console.log("Domain Separator:", uint256(domainSeparator)); + console.log("Struct Hash:", uint256(structHash)); + console.log("Final Digest:", uint256(digest)); + console.log("Swap Nonce:", swapNonce); + console.log("SqrtPriceLimitX96:", sqrtPriceLimitX96); } +} function deployAndSeedTestContracts( IPoolManager manager, diff --git a/internal/handlers/addLiquidityPermit.go b/internal/handlers/addLiquidityPermit.go index fcfe825..a280277 100644 --- a/internal/handlers/addLiquidityPermit.go +++ b/internal/handlers/addLiquidityPermit.go @@ -5,86 +5,103 @@ import ( "fmt" "log" "math/big" - "time" "uniswap-v4-rpc/internal/ethereum" "uniswap-v4-rpc/pkg/utils" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" "github.com/gin-gonic/gin" ) +// AddLiquidityRequest represents the request structure for adding liquidity with permits type AddLiquidityRequest struct { - Currency0 string `json:"currency0" binding:"required"` - Currency1 string `json:"currency1" binding:"required"` - Amount string `json:"amount" binding:"required"` - UserAddress string `json:"userAddress" binding:"required"` - PrivateKey string `json:"privateKey" binding:"required"` + Currency0 string `json:"currency0" binding:"required"` + Currency1 string `json:"currency1" binding:"required"` + Amount string `json:"amount" binding:"required"` + UserAddress string `json:"userAddress" binding:"required"` + Deadline *big.Int `json:"deadline" binding:"required"` + // Signature for currency0 + V0 uint8 `json:"v0" binding:"required"` + R0 string `json:"r0" binding:"required"` + S0 string `json:"s0" binding:"required"` + // Signature for currency1 + V1 uint8 `json:"v1" binding:"required"` + R1 string `json:"r1" binding:"required"` + S1 string `json:"s1" binding:"required"` } +// AddLiquidityPermit handles the addition of liquidity using signed permits +// This allows users to add liquidity without needing to pre-approve tokens func AddLiquidityPermit(c *gin.Context) { + // Parse and validate request body var req AddLiquidityRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": "Invalid request body: " + err.Error()}) return } - // Convert string inputs to appropriate types + // Convert addresses and amount currency0 := common.HexToAddress(req.Currency0) currency1 := common.HexToAddress(req.Currency1) + userAddress := common.HexToAddress(req.UserAddress) amount, success := new(big.Int).SetString(req.Amount, 10) if !success { c.JSON(400, gin.H{"error": "Invalid amount value"}) return } - userAddress := common.HexToAddress(req.UserAddress) - // Parse private key - privateKey, err := crypto.HexToECDSA(req.PrivateKey) - if err != nil { - c.JSON(400, gin.H{"error": "Invalid private key: " + err.Error()}) - return - } - // Hardcoded tick range - minTick := big.NewInt(-887220) - maxTick := big.NewInt(887220) + // Convert signature components to the required format + var r0, s0, r1, s1 [32]byte + copy(r0[:], common.FromHex(req.R0)) + copy(s0[:], common.FromHex(req.S0)) + copy(r1[:], common.FromHex(req.R1)) + copy(s1[:], common.FromHex(req.S1)) - // Create the pool key - poolKey := createPoolKey(currency0, currency1, ethereum.HookAddress) + // Log initial parameters + log.Printf("Adding liquidity for user: %s", userAddress.Hex()) + log.Printf("Currency0: %s, Currency1: %s", currency0.Hex(), currency1.Hex()) + log.Printf("Amount: %s", amount.String()) - // Prepare modifyLiquidity parameters + // Define liquidity parameters + // Using full range for now (-887220 to 887220) params := struct { TickLower *big.Int TickUpper *big.Int LiquidityDelta *big.Int Salt [32]byte }{ - TickLower: minTick, - TickUpper: maxTick, + TickLower: big.NewInt(-887220), + TickUpper: big.NewInt(887220), LiquidityDelta: amount, Salt: [32]byte{}, } - // Prepare permit data - deadline := big.NewInt(time.Now().Unix() + 3600) // 1 hour from now - value := new(big.Int).Mul(amount, big.NewInt(10)) + // Create the pool key with standard parameters + poolKey := createPoolKey(currency0, currency1, ethereum.HookAddress) - // Generate permit signatures for both tokens - v0, r0, s0, err := utils.GeneratePermitSignature(currency0, userAddress, ethereum.LPRouterAddress, value, deadline, privateKey) + // Log pool key details for debugging + log.Printf("Pool Key - Currency0: %s, Currency1: %s, Hooks: %s", + poolKey.Currency0.Hex(), + poolKey.Currency1.Hex(), + poolKey.Hooks.Hex(), + ) + + // Get initial balances for comparison + balance0Before, err := utils.GetBalance(currency0, userAddress) if err != nil { - c.JSON(500, gin.H{"error": "Error generating permit signature for currency0: " + err.Error()}) + log.Printf("Error getting balance of currency0 before adding liquidity: %v", err) + c.JSON(500, gin.H{"error": "Failed to get initial balance"}) return } - - v1, r1, s1, err := utils.GeneratePermitSignature(currency1, userAddress, ethereum.LPRouterAddress, value, deadline, privateKey) + balance1Before, err := utils.GetBalance(currency1, userAddress) if err != nil { - c.JSON(500, gin.H{"error": "Error generating permit signature for currency1: " + err.Error()}) + log.Printf("Error getting balance of currency1 before adding liquidity: %v", err) + c.JSON(500, gin.H{"error": "Failed to get initial balance"}) return } - // Pack the data for the modifyLiquidityWithPermit function call + // Pack the transaction data data, err := ethereum.LPRouterABI.Pack("modifyLiquidityWithPermit", userAddress, poolKey, @@ -92,87 +109,98 @@ func AddLiquidityPermit(c *gin.Context) { []byte{}, // hookData false, // settleUsingBurn false, // takeClaims - deadline, - v0, r0, s0, - v1, r1, s1, + req.Deadline, + req.V0, r0, s0, // Currency0 signature + req.V1, r1, s1, // Currency1 signature ) if err != nil { - c.JSON(500, gin.H{"error": "Error packing data: " + err.Error()}) + c.JSON(500, gin.H{"error": "Error packing transaction data: " + err.Error()}) return } + // Prepare transaction parameters chainID, err := ethereum.Client.ChainID(context.Background()) if err != nil { - c.JSON(500, gin.H{"error": "Error getting chain ID: " + err.Error()}) - return - } - auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID) - if err != nil { - c.JSON(500, gin.H{"error": "Error creating transactor: " + err.Error()}) + c.JSON(500, gin.H{"error": "Failed to get chain ID: " + err.Error()}) return } - balance0Before, err := utils.GetBalance(currency0, userAddress) - if err != nil { - log.Printf("Error getting balance of currency0 before adding liquidity: %v", err) - c.JSON(500, gin.H{"error": "Internal server error"}) - return - } - balance1Before, err := utils.GetBalance(currency1, userAddress) + // Create transactor using server's private key + auth, err := bind.NewKeyedTransactorWithChainID(ethereum.PrivateKey, chainID) if err != nil { - log.Printf("Error getting balance of currency1 before adding liquidity: %v", err) - c.JSON(500, gin.H{"error": "Internal server error"}) + c.JSON(500, gin.H{"error": "Failed to create transactor: " + err.Error()}) return } - // Create and send the transaction + // Get the current nonce and gas price nonce, err := ethereum.Client.PendingNonceAt(context.Background(), auth.From) if err != nil { - c.JSON(500, gin.H{"error": fmt.Sprintf("Error fetching nonce: %v", err)}) + c.JSON(500, gin.H{"error": fmt.Sprintf("Failed to get nonce: %v", err)}) return } gasPrice, err := ethereum.Client.SuggestGasPrice(context.Background()) if err != nil { - c.JSON(500, gin.H{"error": fmt.Sprintf("Error fetching gas price: %v", err)}) + c.JSON(500, gin.H{"error": fmt.Sprintf("Failed to get gas price: %v", err)}) return } + // Create and sign transaction tx := types.NewTransaction(nonce, ethereum.LPRouterAddress, big.NewInt(0), 1000000, gasPrice, data) - - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey) + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), ethereum.PrivateKey) if err != nil { - c.JSON(500, gin.H{"error": fmt.Sprintf("Error signing transaction: %v", err)}) + c.JSON(500, gin.H{"error": fmt.Sprintf("Failed to sign transaction: %v", err)}) return } + // Send transaction err = ethereum.Client.SendTransaction(context.Background(), signedTx) if err != nil { - c.JSON(500, gin.H{"error": fmt.Sprintf("Error sending transaction: %v", err)}) + c.JSON(500, gin.H{"error": fmt.Sprintf("Failed to send transaction: %v", err)}) return } + // Get final balances balance0After, err := utils.GetBalance(currency0, userAddress) if err != nil { log.Printf("Error getting balance of currency0 after adding liquidity: %v", err) - c.JSON(500, gin.H{"error": "Internal server error"}) + c.JSON(500, gin.H{"error": "Failed to get final balance"}) return } balance1After, err := utils.GetBalance(currency1, userAddress) if err != nil { log.Printf("Error getting balance of currency1 after adding liquidity: %v", err) - c.JSON(500, gin.H{"error": "Internal server error"}) + c.JSON(500, gin.H{"error": "Failed to get final balance"}) return } + // Calculate balance changes delta0 := new(big.Int).Sub(balance0After, balance0Before) delta1 := new(big.Int).Sub(balance1After, balance1Before) + // Return success response with details c.JSON(200, gin.H{ - "txHash": signedTx.Hash().Hex(), - "message": "Add liquidity with permit initiated successfully", - "balancesBefore": gin.H{"currency0": balance0Before.String(), "currency1": balance1Before.String()}, - "balancesAfter": gin.H{"currency0": balance0After.String(), "currency1": balance1After.String()}, - "deltaBalances": gin.H{"currency0": delta0.String(), "currency1": delta1.String()}, + "txHash": signedTx.Hash().Hex(), + "message": "Liquidity added successfully with permits", + "details": gin.H{ + "balancesBefore": gin.H{ + "currency0": balance0Before.String(), + "currency1": balance1Before.String(), + }, + "balancesAfter": gin.H{ + "currency0": balance0After.String(), + "currency1": balance1After.String(), + }, + "deltaBalances": gin.H{ + "currency0": delta0.String(), + "currency1": delta1.String(), + }, + }, + "poolKey": gin.H{ + "currency0": poolKey.Currency0.Hex(), + "currency1": poolKey.Currency1.Hex(), + "fee": poolKey.Fee, + "hooks": poolKey.Hooks.Hex(), + }, }) } diff --git a/internal/handlers/swapPermit.go b/internal/handlers/swapPermit.go index 6958c52..28bf159 100644 --- a/internal/handlers/swapPermit.go +++ b/internal/handlers/swapPermit.go @@ -5,56 +5,70 @@ import ( "fmt" "log" "math/big" - "time" "uniswap-v4-rpc/internal/ethereum" "uniswap-v4-rpc/pkg/utils" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" "github.com/gin-gonic/gin" ) -func SwapPermit(c *gin.Context) { - var req struct { - Currency0 string `json:"currency0" binding:"required"` - Currency1 string `json:"currency1" binding:"required"` - Amount string `json:"amount" binding:"required"` - ZeroForOne bool `json:"zeroForOne"` - UserAddress string `json:"userAddress" binding:"required"` - PrivateKey string `json:"privateKey" binding:"required"` - } +// SwapRequest defines the structure for a swap request with permit signature +type SwapRequest struct { + Currency0 string `json:"currency0" binding:"required"` // Address of the input token + Currency1 string `json:"currency1" binding:"required"` // Address of the output token + Amount string `json:"amount" binding:"required"` // Amount to swap in base units (wei) + ZeroForOne bool `json:"zeroForOne"` // Direction of swap (true for currency0 to currency1) + UserAddress string `json:"userAddress" binding:"required"` // Address of the user initiating the swap + SignatureV uint8 `json:"v" binding:"required"` // V component of the EIP-712 signature + SignatureR string `json:"r" binding:"required"` // R component of the EIP-712 signature + SignatureS string `json:"s" binding:"required"` // S component of the EIP-712 signature + Deadline *big.Int `json:"deadline" binding:"required"` // Timestamp after which the permit becomes invalid +} +// SwapPermit handles token swaps using signed permits +// This function allows users to swap tokens without pre-approving them, +// by using EIP-712 signatures (gasless approvals) +func SwapPermit(c *gin.Context) { + // Parse and validate the incoming JSON request + var req SwapRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } + // Convert string addresses to Ethereum addresses currency0 := common.HexToAddress(req.Currency0) currency1 := common.HexToAddress(req.Currency1) + userAddress := common.HexToAddress(req.UserAddress) + + // Parse the amount string to big.Int amountSpecified, ok := new(big.Int).SetString(req.Amount, 10) if !ok { c.JSON(400, gin.H{"error": "Invalid amount"}) return } - userAddress := common.HexToAddress(req.UserAddress) - alicePrivKey, err := crypto.HexToECDSA(req.PrivateKey) - if err != nil { - c.JSON(400, gin.H{"error": "Invalid private key"}) - return - } + // Convert hex signature strings to bytes32 format + var r, s [32]byte + rBytes := common.FromHex(req.SignatureR) + sBytes := common.FromHex(req.SignatureS) + copy(r[:], rBytes) + copy(s[:], sBytes) + + // Set swap direction and price limit zeroForOne := req.ZeroForOne + // Default price limit for swaps sqrtPriceLimitX96, _ := new(big.Int).SetString("4295128740", 10) - fmt.Printf("Users's address: %s\n", userAddress.Hex()) - fmt.Printf("Users's private key: 0x%x\n", crypto.FromECDSA(alicePrivKey)) + // Log user address for debugging + fmt.Printf("User's address: %s\n", userAddress.Hex()) - // Create the pool key + // Create pool key for the token pair poolKey := createPoolKey(currency0, currency1, ethereum.HookAddress) - // Prepare swap parameters + // Define swap parameters structure swapParams := struct { ZeroForOne bool AmountSpecified *big.Int @@ -65,6 +79,7 @@ func SwapPermit(c *gin.Context) { SqrtPriceLimitX96: sqrtPriceLimitX96, } + // Define test settings for the swap testSettings := struct { TakeClaims bool SettleUsingBurn bool @@ -73,38 +88,33 @@ func SwapPermit(c *gin.Context) { SettleUsingBurn: false, } + // Log pool and swap details for debugging log.Printf("PoolKey: currency0=%s, currency1=%s, fee=%d, tickSpacing=%d, hooks=%s", poolKey.Currency0.Hex(), poolKey.Currency1.Hex(), poolKey.Fee, poolKey.TickSpacing, poolKey.Hooks.Hex()) log.Printf("SwapParams: zeroForOne=%v, amountSpecified=%s, sqrtPriceLimitX96=%s", swapParams.ZeroForOne, swapParams.AmountSpecified.String(), swapParams.SqrtPriceLimitX96.String()) - // Prepare permit data - deadline := big.NewInt(time.Now().Unix() + 3600) // 1 hour from now + // Calculate value including buffer for fees and slippage (10% buffer) value := new(big.Int).Mul(amountSpecified, big.NewInt(11)) - value = value.Div(value, big.NewInt(10)) // Increase by 10% to account for fees and slippage + value = value.Div(value, big.NewInt(10)) + // Log transaction details log.Printf("Token Address (currency0): %s", currency0.Hex()) log.Printf("Spender Address (SwapRouterAddress): %s", ethereum.SwapRouterAddress.Hex()) log.Printf("User Address: %s", userAddress.Hex()) log.Printf("Value: %s", value.String()) - log.Printf("Deadline: %s", deadline.String()) + log.Printf("Deadline: %s", req.Deadline.String()) + log.Printf("Signature (v,r,s): %d, 0x%x, 0x%x", req.SignatureV, r, s) - // Generate permit signature - v, r, s, err := utils.GeneratePermitSignature(currency0, userAddress, ethereum.SwapRouterAddress, value, deadline, alicePrivKey) - if err != nil { - c.JSON(500, gin.H{"error": fmt.Sprintf("Failed to generate permit signature: %v", err)}) - return - } - - // Pack the data for the swapWithPermit function call + // Pack the transaction data for the swapWithPermit function data, err := ethereum.SwapRouterABI.Pack("swapWithPermit", userAddress, poolKey, swapParams, testSettings, - []byte{}, // hookData - deadline, - v, + []byte{}, // hookData (empty for standard swaps) + req.Deadline, + req.SignatureV, r, s, ) @@ -112,10 +122,12 @@ func SwapPermit(c *gin.Context) { c.JSON(500, gin.H{"error": fmt.Sprintf("Error packing data: %v", err)}) return } - chainID, _ := ethereum.Client.ChainID(context.Background()) + // Get chain ID and create transactor + chainID, _ := ethereum.Client.ChainID(context.Background()) auth, _ := bind.NewKeyedTransactorWithChainID(ethereum.PrivateKey, chainID) + // Get initial balances for comparison balance0Before, err := utils.GetBalance(currency0, userAddress) if err != nil { log.Printf("Error getting balance of currency0 before swap: %v", err) @@ -129,33 +141,36 @@ func SwapPermit(c *gin.Context) { return } - // Create and send the transaction + // Get current nonce for transaction nonce, err := ethereum.Client.PendingNonceAt(context.Background(), auth.From) if err != nil { c.JSON(500, gin.H{"error": fmt.Sprintf("Error fetching nonce: %v", err)}) return } + // Get current gas price gasPrice, err := ethereum.Client.SuggestGasPrice(context.Background()) if err != nil { c.JSON(500, gin.H{"error": fmt.Sprintf("Error fetching gas price: %v", err)}) return } + // Create and sign transaction tx := types.NewTransaction(nonce, ethereum.SwapRouterAddress, big.NewInt(0), 1000000, gasPrice, data) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), ethereum.PrivateKey) if err != nil { c.JSON(500, gin.H{"error": fmt.Sprintf("Error signing transaction: %v", err)}) return } + // Send transaction to the network err = ethereum.Client.SendTransaction(context.Background(), signedTx) if err != nil { c.JSON(500, gin.H{"error": fmt.Sprintf("Error sending transaction: %v", err)}) return } + // Get final balances after swap balance0After, err := utils.GetBalance(currency0, userAddress) if err != nil { log.Printf("Error getting balance of currency0 after swap: %v", err) @@ -169,14 +184,27 @@ func SwapPermit(c *gin.Context) { return } + // Calculate balance changes delta0 := new(big.Int).Sub(balance0After, balance0Before) delta1 := new(big.Int).Sub(balance1After, balance1Before) + // Return success response with transaction details c.JSON(200, gin.H{ - "txHash": signedTx.Hash().Hex(), - "message": "Swap with permit initiated successfully", - "balancesBefore": gin.H{"currency0": balance0Before.String(), "currency1": balance1Before.String()}, - "balancesAfter": gin.H{"currency0": balance0After.String(), "currency1": balance1After.String()}, - "deltaBalances": gin.H{"currency0": delta0.String(), "currency1": delta1.String()}, + "txHash": signedTx.Hash().Hex(), + "message": "Swap with permit initiated successfully", + "balances": gin.H{ + "before": gin.H{ + "currency0": balance0Before.String(), + "currency1": balance1Before.String(), + }, + "after": gin.H{ + "currency0": balance0After.String(), + "currency1": balance1After.String(), + }, + "delta": gin.H{ + "currency0": delta0.String(), + "currency1": delta1.String(), + }, + }, }) }