1

Here is my current code. In the past I have used ISwapRouter with v2 liqudity. In this case I am trying to execute a swap on base chain with v3 liqudity but base chain does not have a swap router contract so swaps go through the universal router. Every time I send 10 brett tokens to this contract and then try and call the execute function (in remix) I get my transaction reverted.

// SPDX-License-Identifier: MIT
//Solidity version
pragma solidity =0.8.25;

import"@uniswap/universal-router/contracts/interfaces/IUniversalRouter.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 



contract SwapExecutor 
{
    
    
    address private immutable projectWallet=0x6d9265EC3e99E653Ae77c30AA9753BbcCafB45e9;
    address private immutable brett=0x532f27101965dd16442E59d40670FaF5eBB142E4;
    address private immutable weth= 0x4200000000000000000000000000000000000006;
    uint24 private immutable fee=10000;
    uint256 private immutable tokensIn=10 ether;
    uint256 private immutable tokensOut=0 ether;
    bytes1 private immutable V3_SWAP_EXACT_IN = 0x00;
    bool private immutable fromMsgSender=true;
    
    address private immutable _routerAddress=0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD ;
    IUniversalRouter public router;


    bytes private path;
    
    
    
    constructor() 
    {
        router = IUniversalRouter(_routerAddress);
        path=abi.encodePacked(brett,fee,weth);
    }





    function executeSwap() external payable
    
    {
        IERC20(brett).approve(address(router), tokensIn);

        uint256 deadLine=block.timestamp + 300;
        
        bytes memory commands = new bytes(1);
        commands[0] = V3_SWAP_EXACT_IN;
        
        
        bytes[] memory inputs = new bytes[](1);
        inputs[0] = abi.encode(
            projectWallet,
            tokensIn,
            tokensOut,
            path,
            fromMsgSender
        );

        router.execute{value: msg.value}(commands, inputs, deadLine);
    }
}

1 Answer 1

1

Approval doesn't work like that for the universal router, you need to approve via permit.

Lifting code written in python as easy to port over and swapping the same brett token:

# ...
# we will swap on v3 then unwrap wrap the native token

commands = '0x000c'

from eth_abi import encode
from eth_abi.packed import encode_packed
# some sane inputs (sane doesn't mean safe in this case, always use a good slippage value unless protected some other way)
to = router.address  # router here, eoa address in unwrap
amount = 1 * 10 ** 4
slippage = 0
FEE = 10000
path = encode_packed(['address','uint24','address'], [BRETT_ADDRESS, FEE, WETH_ADDRESS])
from_eoa = True
unwrap_calldata = encode(['address', 'uint256'], [eoa.address, slippage])
v3_calldata = encode(['address', 'uint256', 'uint256', 'bytes', 'bool'], [to, amount, slippage, path, from_eoa])
deadline = 2*10**10

# PERMIT
# permit is a little involved, there are two ways you can do it. In both cases it is necessary to first perform
# a `classical` approval for the permit2 contract to move your tokens, as it is that contracts allowance that will be spent.
# this approval needs to be performed in a preceding transaction (unless as part of an atomic call from as custom contract for example)
# Following that approval we have two options we can explicitly "approve" the router as a spender of the permit2 contracts allowance in another transaction/call. # The benefit of this method is that the process is intuitive and familiar, it can be performed via a block explorer without any special encoding or signing.
# A second approach is to sign permit data (A set of encoded values), and send the signature for that permit along with the swap.
# While this method increases complexity a little it removes the need for the additional transaction to the permit2 contract to grant a approval to the router.

# We will keep it simple for this example:
approve_permit = token.functions.approve(PERMIT_ADDRESS, amount)
approve_router = permit.functions.approve(BRETT_ADDRESS, ROUTER_ADDRESS, amount, deadline)
# ...

You are swapping within a single call from a contract, so just want to add the last two steps in place of the current router approval.

  • First perform a classical approval for the permit2 contract to move your tokens, as it is that contracts allowance that will be spent.
  • Following that, explicitly "approve" the router as a spender of the permit2 contracts allowance:
# Solidity
interface IERC20  { function approve(address spender, uint amount) external returns (bool); }
interface IPERMIT { function approve(address token, address spender, uint160 amount, uint48 expiration); }
// ... 
IERC20(brett).approve(PERMIT_ADDRESS, tokensIn);
IPERMIT(permit).approve(BRETT_ADDRESS, ROUTER_ADDRESS, tokensIn, deadline)
9
  • 1
    For the line in my code router.execute{value: msg.value}(commands, inputs, deadLine); is the msg.value automatiaclly calculated my metamask whenever I hit confirm on my wallet to call the fuinction ? Commented May 24 at 20:45
  • 1
    Not not sure if this is a noob question aswell but uniswap and base have diffrent universal router addresses listed for base chain not sure if it matters which one I use Uniswap:0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD Base:0x198EF79F1F515F02dFE9e3115eD9fC07183f02fC Commented May 24 at 20:47
  • 1
    1. I said what I meant but sure, you can name the interface whatever you like and just lift the approval function. It is a convention to use I followed by the contract name, is just not something I do. 2. Ye it is the value you send along with call/ transaction parameters. Though I'm not sure the contract looks set up to do an eth swap as you need to WRAP_ETH/0x0b first in that case. 3. Shouldn't make a difference, would just use Universal Router V1 2 V2Support / 0x3fC9... found here: docs.uniswap.org/contracts/v3/reference/deployments/…
    – Maka
    Commented May 24 at 21:58
  • 1
    Rather than full import, can try just interface IPERMIT { function approve(address token, address spender, uint160 amount, uint48 expiration); } .
    – Maka
    Commented May 24 at 22:11
  • 1
    No you don't need to and yes you can just add the functions you are interfacing with, though to be honest I will update the answer to adhere to convention and include the explicit import. You can be quite dynamic with how you call a contract and how you define the interface, it just wants to conform enough to accurately encode the calldata.
    – Maka
    Commented May 24 at 22:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.