9.5 KiB
9.5 KiB
Wrap ETH to WETH9 and Bridge to Ethereum Mainnet
This guide explains the complete process of wrapping ETH to WETH9 and bridging it to Ethereum Mainnet from ChainID 138.
Overview
The process involves three main steps:
- Wrap ETH to WETH9 - Convert native ETH to WETH9 tokens
- Approve Bridge - Grant the bridge contract permission to spend your WETH9
- Bridge to Ethereum Mainnet - Send WETH9 cross-chain via CCIP
Prerequisites
- Private key with sufficient ETH balance (amount + gas fees)
casttool from Foundry (for command-line execution)- Access to ChainID 138 RPC endpoint
- Basic understanding of blockchain transactions
Contract Addresses
ChainID 138 (Source Chain)
- WETH9 Contract:
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 - WETH9 Bridge:
0x89dd12025bfCD38A168455A44B400e913ED33BE2 - RPC URL:
http://192.168.11.250:8545orhttps://rpc-core.d-bis.org
Ethereum Mainnet (Destination)
- Chain Selector:
5009297550715157269 - Chain ID:
1
Quick Start
Using the Automated Script
# Basic usage (with PRIVATE_KEY in .env)
./scripts/wrap-and-bridge-to-ethereum.sh 1.0
# With private key as argument
./scripts/wrap-and-bridge-to-ethereum.sh 1.0 0xYourPrivateKeyHere
The script will:
- Check your ETH balance
- Wrap ETH to WETH9 if needed
- Approve the bridge contract
- Calculate CCIP fees
- Send the cross-chain transfer
Manual Process (Step-by-Step)
Step 1: Check Your Balance
# Get your address from private key
DEPLOYER=$(cast wallet address --private-key "0xYourPrivateKey")
# Check ETH balance
cast balance "$DEPLOYER" --rpc-url "http://192.168.11.250:8545"
# Check WETH9 balance
cast call "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" \
"balanceOf(address)" "$DEPLOYER" \
--rpc-url "http://192.168.11.250:8545"
Step 2: Wrap ETH to WETH9
WETH9 uses the standard deposit() function to wrap ETH:
# Convert amount to wei (e.g., 1.0 ETH)
AMOUNT_WEI=$(cast --to-wei 1.0 ether)
# Wrap ETH by calling deposit() with value
cast send "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" "deposit()" \
--value "$AMOUNT_WEI" \
--rpc-url "http://192.168.11.250:8545" \
--private-key "0xYourPrivateKey" \
--gas-price 5000000000
What happens:
- ETH is sent to the WETH9 contract
- Equivalent WETH9 tokens are minted to your address
- A
Depositevent is emitted - A
Transferevent is emitted (from address(0) to you)
Step 3: Approve Bridge Contract
Before bridging, you must approve the bridge to spend your WETH9:
# Approve bridge (using max uint256 for unlimited approval)
MAX_UINT256="115792089237316195423570985008687907853269984665640564039457584007913129639935"
cast send "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" \
"approve(address,uint256)" \
"0x89dd12025bfCD38A168455A44B400e913ED33BE2" \
"$MAX_UINT256" \
--rpc-url "http://192.168.11.250:8545" \
--private-key "0xYourPrivateKey" \
--gas-price 5000000000
Check allowance:
cast call "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" \
"allowance(address,address)" \
"$DEPLOYER" \
"0x89dd12025bfCD38A168455A44B400e913ED33BE2" \
--rpc-url "http://192.168.11.250:8545"
Step 4: Calculate CCIP Fee
Before bridging, check the required fee:
ETHEREUM_SELECTOR="5009297550715157269"
AMOUNT_WEI=$(cast --to-wei 1.0 ether)
cast call "0x89dd12025bfCD38A168455A44B400e913ED33BE2" \
"calculateFee(uint64,uint256)" \
"$ETHEREUM_SELECTOR" \
"$AMOUNT_WEI" \
--rpc-url "http://192.168.11.250:8545"
Step 5: Bridge to Ethereum Mainnet
Send the cross-chain transfer:
ETHEREUM_SELECTOR="5009297550715157269"
AMOUNT_WEI=$(cast --to-wei 1.0 ether)
DEPLOYER=$(cast wallet address --private-key "0xYourPrivateKey")
cast send "0x89dd12025bfCD38A168455A44B400e913ED33BE2" \
"sendCrossChain(uint64,address,uint256)" \
"$ETHEREUM_SELECTOR" \
"$DEPLOYER" \
"$AMOUNT_WEI" \
--rpc-url "http://192.168.11.250:8545" \
--private-key "0xYourPrivateKey" \
--gas-price 5000000000
What happens:
- Bridge contract transfers WETH9 from your address
- CCIP message is sent to Ethereum Mainnet
- On Ethereum Mainnet, WETH9 tokens are minted to your address
- The process typically takes a few minutes
Using Web3 Libraries
JavaScript/TypeScript (ethers.js)
const { ethers } = require('ethers');
// Setup
const provider = new ethers.providers.JsonRpcProvider('http://192.168.11.250:8545');
const wallet = new ethers.Wallet('0xYourPrivateKey', provider);
const WETH9_ADDRESS = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
const BRIDGE_ADDRESS = '0x89dd12025bfCD38A168455A44B400e913ED33BE2';
const ETHEREUM_SELECTOR = '5009297550715157269';
// Step 1: Wrap ETH
const weth9 = new ethers.Contract(WETH9_ADDRESS, [
'function deposit() payable',
'function balanceOf(address) view returns (uint256)',
'function approve(address,uint256) returns (bool)',
'function allowance(address,address) view returns (uint256)'
], wallet);
const amount = ethers.utils.parseEther('1.0');
const wrapTx = await weth9.deposit({ value: amount });
await wrapTx.wait();
console.log('Wrapped ETH to WETH9:', wrapTx.hash);
// Step 2: Approve Bridge
const approveTx = await weth9.approve(BRIDGE_ADDRESS, ethers.constants.MaxUint256);
await approveTx.wait();
console.log('Approved bridge:', approveTx.hash);
// Step 3: Bridge
const bridge = new ethers.Contract(BRIDGE_ADDRESS, [
'function sendCrossChain(uint64,address,uint256)',
'function calculateFee(uint64,uint256) view returns (uint256)'
], wallet);
const fee = await bridge.calculateFee(ETHEREUM_SELECTOR, amount);
console.log('CCIP Fee:', ethers.utils.formatEther(fee));
const bridgeTx = await bridge.sendCrossChain(ETHEREUM_SELECTOR, wallet.address, amount);
await bridgeTx.wait();
console.log('Bridged to Ethereum Mainnet:', bridgeTx.hash);
Python (web3.py)
from web3 import Web3
# Setup
w3 = Web3(Web3.HTTPProvider('http://192.168.11.250:8545'))
account = w3.eth.account.from_key('0xYourPrivateKey')
WETH9_ADDRESS = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'
BRIDGE_ADDRESS = '0x89dd12025bfCD38A168455A44B400e913ED33BE2'
ETHEREUM_SELECTOR = 5009297550715157269
# WETH9 ABI (simplified)
weth9_abi = [
{
"constant": False,
"inputs": [],
"name": "deposit",
"outputs": [],
"payable": True,
"stateMutability": "payable",
"type": "function"
},
{
"constant": False,
"inputs": [{"name": "spender", "type": "address"}, {"name": "amount", "type": "uint256"}],
"name": "approve",
"outputs": [{"name": "", "type": "bool"}],
"type": "function"
}
]
# Bridge ABI (simplified)
bridge_abi = [
{
"constant": False,
"inputs": [
{"name": "destinationChainSelector", "type": "uint64"},
{"name": "recipient", "type": "address"},
{"name": "amount", "type": "uint256"}
],
"name": "sendCrossChain",
"outputs": [],
"type": "function"
}
]
weth9 = w3.eth.contract(address=WETH9_ADDRESS, abi=weth9_abi)
bridge = w3.eth.contract(address=BRIDGE_ADDRESS, abi=bridge_abi)
amount = w3.toWei(1.0, 'ether')
# Step 1: Wrap ETH
tx_hash = weth9.functions.deposit().transact({
'from': account.address,
'value': amount,
'gas': 100000,
'gasPrice': w3.toWei(5, 'gwei')
})
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f'Wrapped ETH: {tx_hash.hex()}')
# Step 2: Approve Bridge
max_uint256 = 2**256 - 1
tx_hash = weth9.functions.approve(BRIDGE_ADDRESS, max_uint256).transact({
'from': account.address,
'gas': 100000,
'gasPrice': w3.toWei(5, 'gwei')
})
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f'Approved bridge: {tx_hash.hex()}')
# Step 3: Bridge
tx_hash = bridge.functions.sendCrossChain(
ETHEREUM_SELECTOR,
account.address,
amount
).transact({
'from': account.address,
'gas': 500000,
'gasPrice': w3.toWei(5, 'gwei')
})
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f'Bridged to Ethereum Mainnet: {tx_hash.hex()}')
Important Notes
Gas Fees
- Wrapping ETH: ~50,000 gas
- Approving bridge: ~50,000 gas
- Bridging: ~200,000-500,000 gas (depends on CCIP complexity)
- Keep extra ETH for gas fees (recommend 0.01+ ETH)
Transaction Confirmation
- ChainID 138 transactions typically confirm in seconds
- CCIP bridge transfers may take 5-15 minutes to complete on Ethereum Mainnet
- Monitor both chains for completion
Security
- Never share your private key
- Store private keys securely (use environment variables or secure key management)
- Verify contract addresses before interacting
- Double-check amounts before sending
Troubleshooting
Insufficient Balance:
# Check ETH balance
cast balance "$DEPLOYER" --rpc-url "$RPC_URL"
# Check WETH9 balance
cast call "$WETH9_ADDRESS" "balanceOf(address)" "$DEPLOYER" --rpc-url "$RPC_URL"
Transaction Failed:
- Check gas price (may need to increase)
- Verify nonce (may need to wait for previous transactions)
- Ensure sufficient balance for amount + gas
Bridge Not Approved:
# Check allowance
cast call "$WETH9_ADDRESS" "allowance(address,address)" "$DEPLOYER" "$WETH9_BRIDGE" --rpc-url "$RPC_URL"
Monitoring Transactions
- ChainID 138 Explorer: https://explorer.d-bis.org
- Ethereum Mainnet Explorer: https://etherscan.io