Files
explorer-monorepo/docs/WRAP_AND_BRIDGE_TO_ETHEREUM.md

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:

  1. Wrap ETH to WETH9 - Convert native ETH to WETH9 tokens
  2. Approve Bridge - Grant the bridge contract permission to spend your WETH9
  3. Bridge to Ethereum Mainnet - Send WETH9 cross-chain via CCIP

Prerequisites

  • Private key with sufficient ETH balance (amount + gas fees)
  • cast tool 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:8545 or https://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:

  1. Check your ETH balance
  2. Wrap ETH to WETH9 if needed
  3. Approve the bridge contract
  4. Calculate CCIP fees
  5. 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 Deposit event is emitted
  • A Transfer event 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

Additional Resources