340 lines
9.5 KiB
Markdown
340 lines
9.5 KiB
Markdown
|
|
# 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
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 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
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 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:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 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:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 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:**
|
||
|
|
```bash
|
||
|
|
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:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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)
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
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)
|
||
|
|
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```bash
|
||
|
|
# 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:**
|
||
|
|
```bash
|
||
|
|
# 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
|
||
|
|
|
||
|
|
## Additional Resources
|
||
|
|
|
||
|
|
- [WETH9 Contract Documentation](https://docs.openzeppelin.com/contracts/4.x/api/token/erc20#WETH)
|
||
|
|
- [CCIP Documentation](https://docs.chain.link/ccip)
|
||
|
|
- [Foundry Cast Documentation](https://book.getfoundry.sh/reference/cast/)
|
||
|
|
|