Bridge ETH на Base через OP Stack
Как перевести ETH из Ethereum Sepolia в Base Sepolia через L1StandardBridge OP Stack и проверить поступление на L2.
Что делает bridge ETH в OP Stack
Base построена на OP Stack, поэтому ETH переводится через стандартный L1→L2 bridge. На L1 вы отправляете ETH в L1StandardBridge, а на L2 тот же объём появляется на адресе получателя.
В примере используется Ethereum Sepolia → Base Sepolia. Это тестовые сети, но механика такая же, как при bridge в Base mainnet.
Создаём проект
mkdir base-bridge-eth
cd base-bridge-eth
npm init -y
npm pkg set type="module"
npm install ethers dotenv
npm install --save-dev typescript tsx @types/node
mkdir srcСоздайте файл .env:
PRIVATE_KEY="0x..."
PARENT_RPC_URL="https://ethereum-sepolia-rpc.publicnode.com"
CHILD_RPC_URL="https://sepolia.base.org"
BRIDGE_AMOUNT_ETH="0.001"PRIVATE_KEY должен принадлежать аккаунту с ETH в Ethereum Sepolia. Этот ETH оплатит L1 gas и будет переведён в Base Sepolia.
Отправляем ETH через L1StandardBridge
Создайте файл src/bridge-eth.ts:
import "dotenv/config";
import { setTimeout } from "node:timers/promises";
import { ethers } from "ethers";
const PARENT_CHAIN_ID = 11155111n;
const CHILD_CHAIN_ID = 84532n;
const PARENT_RPC_URL =
process.env.PARENT_RPC_URL ?? "https://ethereum-sepolia-rpc.publicnode.com";
const CHILD_RPC_URL = process.env.CHILD_RPC_URL ?? "https://sepolia.base.org";
const privateKey = process.env.PRIVATE_KEY;
const amountEth = process.env.BRIDGE_AMOUNT_ETH ?? "0.001";
if (!privateKey) {
throw new Error("Добавьте PRIVATE_KEY в .env");
}
const BASE_SEPOLIA_L1_STANDARD_BRIDGE =
"0xfd0Bf71F60660E2f608ed56e1659C450eB113120";
const L1_STANDARD_BRIDGE_ABI = [
"function bridgeETHTo(address _to,uint32 _minGasLimit,bytes _extraData) external payable",
];
const parentProvider = new ethers.JsonRpcProvider(PARENT_RPC_URL);
const childProvider = new ethers.JsonRpcProvider(CHILD_RPC_URL);
const wallet = new ethers.Wallet(privateKey, parentProvider);
async function assertNetwork(
provider: ethers.JsonRpcProvider,
expectedChainId: bigint,
name: string,
) {
const network = await provider.getNetwork();
if (network.chainId !== expectedChainId) {
throw new Error(
`${name}: ожидался chainId ${expectedChainId}, получен ${network.chainId}`,
);
}
}
await assertNetwork(parentProvider, PARENT_CHAIN_ID, "Ethereum Sepolia");
await assertNetwork(childProvider, CHILD_CHAIN_ID, "Base Sepolia");
const destinationAddress = await wallet.getAddress();
const amount = ethers.parseEther(amountEth);
const parentBalance = await parentProvider.getBalance(destinationAddress);
const childBalanceBefore = await childProvider.getBalance(destinationAddress);
if (parentBalance < amount) {
throw new Error(
`Недостаточно ETH в Ethereum Sepolia: ${ethers.formatEther(parentBalance)}`,
);
}
console.log("Destination:", destinationAddress);
console.log("Bridge amount:", amountEth, "ETH");
console.log(
"Base balance before:",
ethers.formatEther(childBalanceBefore),
"ETH",
);
const bridge = new ethers.Contract(
BASE_SEPOLIA_L1_STANDARD_BRIDGE,
L1_STANDARD_BRIDGE_ABI,
wallet,
);
const minGasLimit = 200_000;
const extraData = "0x";
const tx = await bridge.bridgeETHTo(destinationAddress, minGasLimit, extraData, {
value: amount,
});
console.log("L1 bridge tx:", tx.hash);
const receipt = await tx.wait();
if (!receipt) {
throw new Error("Транзакция не вернула receipt");
}
console.log("L1 confirmed in block:", receipt.blockNumber);
console.log(`L1 explorer: https://sepolia.etherscan.io/tx/${tx.hash}`);
const targetBalance = childBalanceBefore + amount;
for (let attempt = 1; attempt <= 40; attempt++) {
const childBalance = await childProvider.getBalance(destinationAddress);
if (childBalance >= targetBalance) {
console.log("Base balance after:", ethers.formatEther(childBalance), "ETH");
console.log(
`L2 explorer: https://sepolia-explorer.base.org/address/${destinationAddress}`,
);
process.exit(0);
}
console.log(`Ждём поступление на Base... попытка ${attempt}/40`);
await setTimeout(30_000);
}
throw new Error("ETH не поступил в Base Sepolia за 20 минут");Запустите:
npx tsx src/bridge-eth.tsСкрипт отправит L1-транзакцию в Ethereum Sepolia, дождётся подтверждения и будет проверять баланс адреса в Base Sepolia.
Как это работает
BASE_SEPOLIA_L1_STANDARD_BRIDGE — адрес L1 Standard Bridge для Base Sepolia в Ethereum Sepolia.
bridgeETHTo(destinationAddress, minGasLimit, extraData, { value }) — создаёт OP Stack deposit: ETH блокируется на L1, а затем зачисляется получателю на L2.
destinationAddress — адрес, который получит ETH в Base Sepolia. В примере используется тот же EOA, который отправляет L1-транзакцию.
minGasLimit — минимальный gas limit для L2-части сообщения. Для обычного ETH bridge достаточно небольшого значения, в примере используется запас 200_000.
extraData — произвольные байты для bridge-сообщения. Для простого перевода ETH передаём 0x.
targetBalance = childBalanceBefore + amount — проверка, что депозит дошёл до Base Sepolia.
Почему нужен L1 bridge-контракт
Обычный sendTransaction() переводит ETH только внутри одной сети. Если отправить ETH на адрес в Ethereum Sepolia, он не появится в Base Sepolia.
OP Stack bridge делает две операции:
| Шаг | Сеть | Что происходит |
|---|---|---|
| L1 deposit | Ethereum Sepolia | ETH отправляется в L1StandardBridge |
| L2 mint | Base Sepolia | Base зачисляет ETH на адрес получателя |
Поэтому bridge-транзакция начинается на L1, а результат проверяется на L2.
Base Sepolia vs Ethereum Sepolia
| Сеть | Chain ID | RPC в примере | Где нужен ETH |
|---|---|---|---|
| Ethereum Sepolia | 11155111 | https://ethereum-sepolia-rpc.publicnode.com | Для bridge-транзакции и L1 gas |
| Base Sepolia | 84532 | https://sepolia.base.org | Для будущих транзакций в Base |
После bridge ETH окажется в Base Sepolia. Его можно использовать для деплоя контрактов и вызовов функций в Base.
Частые ошибки
ETH есть в Base Sepolia, но нет в Ethereum Sepolia → bridge начинается на L1. Для запуска нужен ETH именно в Ethereum Sepolia.
RPC URL ведёт не в ту сеть → проверьте chainId. Скрипт падает, если получил не 11155111 или не 84532.
Баланс на Base не изменился сразу → OP Stack deposit обычно занимает минуты. Дождитесь L2-зачисления и проверьте explorer.
Использовали адрес bridge от mainnet → для Base Sepolia нужен L1StandardBridge 0xfd0Bf71F60660E2f608ed56e1659C450eB113120.
Что дальше
- Настройка и деплой на Base — использовать полученный ETH для деплоя контракта
- Bridge ETH на Arbitrum — сравнить OP Stack bridge с Arbitrum Inbox
- Оценка газа (estimateGas) — заранее проверять стоимость транзакций