Interacting with Uniswap on a Fork of ZKsync Era Using era_test_node and Foundry

A step-by-step guide to set up a fork of ZKsync Era mainnet and interact with the Uniswap contract to perform token swaps using Foundry.

In this tutorial, you will set up a local ZKsync Era environment using era_test_node, leverage Foundry's cast tool to interact with contracts, and perform token swaps on Uniswap. You will learn how to check balances, deposit ETH for WETH, transfer WETH, and swap tokens using Uniswap's Swap Router.

What you'll learn:

  • How to set up and fork a local ZKsync Era environment.
  • How to use environment variables to streamline your workflow.
  • How to check balances using cast.
  • How to deposit ETH for WETH and transfer WETH.
  • How to approve tokens and perform swaps on Uniswap using the Swap Router.
GitHub

Tools:

  • - foundry
  • - era_test_node
  • - cast
  • - uniswap
  • - zksync-era-explorer
zksync-erafoundrycastuniswaptutorial
Last Updated: Sep 26, 2024

This tutorial shows you how to set up a local ZKsync Era environment using era_test_node, leverage Foundry's cast tool to interact with contracts, and perform token swaps on Uniswap. You'll learn how to:

  1. Set up your development environment.
  2. Streamline your workflow with environment variables.
  3. Check balances using cast call.
  4. Deposit ETH for WETH.
  5. Transfer WETH to another address.
  6. Swap WETH for an ERC20 token on Uniswap.

Let's get started!


Prerequisites

Security Tip: Never commit your private key to version control or share it publicly. Ensure your .env file is excluded from source control. .env should be used for development purposes only, see the foundry keystore section of the docs for a more secure way to manage your private keys.

1. Setting Up the Environment

Fork ZKsync Era Mainnet

Start era_test_node and fork ZKsync Era mainnet:

era_test_node fork mainnet

For debug mode (provides more transaction details and requires version v0.1.0-alpha.27 or higher):

era_test_node -d fork mainnet

2. Streamlining with Environment Variables

Create a new project directory and a .env file in your project directory to store environment variables:

ETH_RPC_URL=http://localhost:8011
PRIVATE_KEY=rich_wallet_private_key_here
ETH_GAS_LIMIT=10000000    # Optional
WETH=0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91
MY_ADDRESS=address_of_the_rich_wallet
SWAP_ROUTER=0x99c56385daBCE3E81d8499d0b8d0257aBC07E8A3

Run the following in your terminal in your project directory to load the environment variables:

source .env
Security Tip: Never commit your private key to version control or share it publicly. Ensure your .env file is excluded from source control. .env should be used for development purposes only, see the foundry keystore section of the docs for a more secure way to manage your private keys.

3. Checking Balances

To check the ETH balance of an address:

cast balance $MY_ADDRESS

4. Depositing ETH for WETH

Deposit ETH and receive WETH by interacting with the WETH contract:

cast send $WETH "deposit()" --value 500000000000000000 --private-key $PRIVATE_KEY
  • Function: deposit()
  • Value: Amount of ETH to deposit (in wei). For example, 0.5 ETH is 500000000000000000 wei.

Verify Your WETH Balance

Check your WETH balance:

cast call $WETH "balanceOf(address)(uint256)" $MY_ADDRESS

5. Transferring WETH

To transfer WETH to another address:

cast send $WETH "transfer(address,uint256)" <RECIPIENT_ADDRESS> <AMOUNT_TO_TRANSFER> --private-key $PRIVATE_KEY
  • <RECIPIENT_ADDRESS>: The address to send WETH to.
  • <AMOUNT_TO_TRANSFER>: Amount of WETH to transfer (in wei).
Check: Use cast call to verify the transfer.

6. Approving WETH for Swapping

Before swapping WETH on Uniswap, approve the Swap Router to spend your WETH:

cast send $WETH "approve(address,uint256)" $SWAP_ROUTER <AMOUNT_TO_APPROVE> --private-key $PRIVATE_KEY
  • <AMOUNT_TO_APPROVE>: The amount of WETH you plan to swap (in wei).

7. Selecting an ERC20 Token and Swap Pair

Find an ERC20 Token

ERC20=ERC20_TOKEN_ADDRESS_HERE

Find the Uniswap Pool and Fee Tier

  • Visit the Uniswap Pools Explorer to find a pool for your token pair.
  • Note the fee tier of the pool (e.g., 3000 for 0.3% fee).
  • Set the fee in your .env file:
FEE=FEE_TIER_HERE

8. Swapping WETH for an ERC20 Token

We'll use the exactInputSingle function from the Uniswap Swap Router contract. This function accepts a struct as a parameter.

exactInputSingle Function Details

function exactInputSingle(ExactInputSingleParams calldata params)
        external
        payable
        returns (uint256 amountOut);

Struct Definition:

struct ExactInputSingleParams {
    address tokenIn;            // Token you're swapping from
    address tokenOut;           // Token you're swapping to
    uint24 fee;                 // Fee tier of the pool
    address recipient;          // Address to receive output tokens
    uint256 amountIn;           // Amount of input tokens to swap
    uint256 amountOutMinimum;   // Minimum amount of output tokens to receive
    uint160 sqrtPriceLimitX96;  // Price limit for the swap (optional)
}

Update the environment variables

Your .env file should now have the following variables:

ETH_RPC_URL=http://localhost:8011
PRIVATE_KEY=rich_wallet_private_key_here
ETH_GAS_LIMIT=10000000    # Optional
WETH=0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91
MY_ADDRESS=your_wallet_address_here
SWAP_ROUTER=0x99c56385daBCE3E81d8499d0b8d0257aBC07E8A3
ERC20=ERC20_TOKEN_ADDRESS_HERE
FEE=FEE_TIER_HERE
AMOUNT_IN=AMOUNT_OF_WETH_TO_SWAP_HERE
AMOUNT_OUT_MIN=MINIMUM_AMOUNT_OF_ERC20_TO_RECEIVE_HERE
SQRT_PRICE_LIMIT_X96=0
  • $AMOUNT_IN: Amount of WETH to swap (in wei).
  • $AMOUNT_OUT_MIN: Minimum amount of ERC20 tokens to receive (in wei). Use Kraken Convert to estimate this amount.
  • $SQRT_PRICE_LIMIT_X96: Set to 0 to accept current pool prices, or calculate based on desired price limit.

Load the environment variables:

source .env

Execute the Swap Command

cast send $SWAP_ROUTER \
  "exactInputSingle((address,address,uint24,address,uint256,uint256,uint160))" \
  "($WETH,$ERC20,$FEE,$MY_ADDRESS,$AMOUNT_IN,$AMOUNT_OUT_MIN,$SQRT_PRICE_LIMIT_X96)" \
  --value 0 --private-key $PRIVATE_KEY
Note: If something goes wrong:
  • Check your .env file for mistakes
  • Reload the environment variables
  • Check the logs in the terminal where you started era_test_node

Notes and Tips

  • EOA Restrictions: Externally Owned Accounts can't interact directly with Uniswap V3 Pool contracts due to callback requirements. Use the Swap Router instead.
  • Understanding Tuples: When calling functions that accept structs, parameters are passed as tuples enclosed in parentheses and quotes to prevent shell parsing issues.
  • Token Decimals: Ensure you're using the correct number of decimals for your ERC20 token.
  • Gas Limit: If you encounter gas limit issues, set ETH_GAS_LIMIT in your .env file.
  • Approvals: Always approve the Swap Router to spend your tokens before swapping.
  • Cast Send: The cast send command is used to send transactions. --value 0 is used because we are swapping WETH for an ERC20 token, and we are not sending any ETH in this transaction.
  • AI: Utilize tools like ChatGPT for quick calculations andexplanations.

Conclusion

You've successfully set up a local ZKsync Era environment, interacted with contracts using cast, and performed a token swap on Uniswap. This setup allows you to test and develop smart contract interactions in a controlled environment.


Additional Resources


Happy coding and exploring ZKsync Era with Foundry and Cast!


Made with ❤️ by the ZKsync Community