Interacting with Uniswap on a Fork of ZKsync Era Using era_test_node and 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.
Tools:
- - foundry
- - era_test_node
- - cast
- - uniswap
- - zksync-era-explorer
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:
- Set up your development environment.
- Streamline your workflow with environment variables.
- Check balances using
cast call
. - Deposit ETH for WETH.
- Transfer WETH to another address.
- Swap WETH for an ERC20 token on Uniswap.
Let's get started!
Prerequisites
- Foundry: Ethereum development toolkit. Installation Guide
- era_test_node: In-memory node to fork ZKsync Era mainnet. Installation Guide
- Online Converters:
- ETH Converter for converting ETH values.
- Hex to Decimal Converter for number conversions.
- ZKsync Era Explorer:
- ZKsync Era Explorer to view contracts.
- Tokens List to find token addresses.
- Uniswap Pool Explorer:
- Uniswap Pools on ZKsync Era to find pools for swapping.
- Price Conversion Tool:
- Kraken Convert to get current exchange rates.
.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
- ETH_RPC_URL: The URL of your local
era_test_node
instance. - PRIVATE_KEY: One of the private keys from the
era_test_node
accounts. - ETH_GAS_LIMIT: Optional gas limit for transactions.
- WETH: WETH token address on ZKsync Era.
- MY_ADDRESS: The address of the rich wallet private key.
- SWAP_ROUTER: Uniswap Swap Router contract address on ZKsync Era.
Run the following in your terminal in your project directory to load the environment variables:
source .env
.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
is500000000000000000 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).
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
- Use the ZKsync Era Tokens List to choose an ERC20 token.
- Set the ERC20 token address in your
.env
file:
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
- 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
- ZKsync Era Documentation: ZKsync Docs
- Foundry Book: Foundry Documentation
- Uniswap V3 Documentation: Uniswap Docs
- ZKsync Era Explorer: ZKsync Era Explorer
- Uniswap Pools on ZKsync Era: Uniswap Pools
Happy coding and exploring ZKsync Era with Foundry and Cast!