A web dApp to bridge native ETH between Sepolia (L1) and Abstract Sepolia (L2), track L2→L1 withdrawals until they are claimable, and claim them on L1. Also provides a full transfer history for any address.
Built with Astro 4 + React 18 + viem 2, deployed on Vercel as a static site with URL rewrites.
- Bridge ETH (L1 → L2) — deposit Sepolia ETH into Abstract via the bridge.
- Track & claim withdrawals (L2 → L1) — given an L2 withdrawal tx hash, shows the cross-chain status timeline (submitted → batch committed → batch proven → batch executed → claimable) and lets the user claim it on L1 with
L1Nullifier.finalizeDeposit. - Transfer history — for any address, lists native ETH deposits and withdrawals (newest first) with Etherscan links for each leg.
- WalletConnect support — MetaMask (via injected provider) and WalletConnect v2 (QR on mobile) through Reown's SDK.
| Route | Description |
|---|---|
/ |
Bridge page (deposit) |
/transaction/<hash> |
Track / claim an L2 withdrawal |
/history/<address> |
Transfer history for an address |
The /:hash and /:address segments are handled client-side from window.location.pathname; Vercel rewrites map these to the static pages.
npm install
cp .env.example .env # add VITE_WALLETCONNECT_PROJECT_ID
npm run devEnvironment variables:
VITE_WALLETCONNECT_PROJECT_ID— Reown Cloud project ID (required for WalletConnect; get one at https://cloud.reown.com).SEPOLIA_PK— optional, used only by the CLI scripts.
npm run dev # start dev server
npm run build # static build → dist/
npm run preview # preview the production build
node scripts/withdraw-cli.mjs # CLI for tracking/claiming a withdrawalDeploy to Vercel:
npx vercel --prodSet VITE_WALLETCONNECT_PROJECT_ID in the Vercel project env vars. The vercel.json rewrites map /transaction/<hash> and /history/<address> to the static pages.
- L1→L2 deposits and L1Nullifier claims use
viemcontract calls against the Sepolia bridge contracts (seesrc/lib/). - Withdrawal status is derived from L1 batch commit/prove/execute events and the claim transaction.
- The CLI mirrors the web claim logic for headless operation.
As of the last on-chain check, every contract this app touches holds 0.000000 ETH:
| Contract | Balance |
|---|---|
| BridgeHub (L1) | 0.000000 ETH |
| SharedBridge L1 / L1Nullifier | 0.000000 ETH |
| Diamond proxy / hyperchain | 0.000000 ETH |
| L2 SharedBridge | 0.000000 ETH |
| L2 base token / message sender (0x800a / 0x8008) | 0.000000 ETH |
This is expected for a base-token bridge on a testnet:
- No custody pool. Deposits (L1→L2) forward ETH through BridgeHub → SharedBridge and straight into the L2 system contracts. There is no long-lived escrow reserve sitting on L1.
- Transient claims only. ETH only sits in L1Nullifier between "L2 batch executed on L1" and the user's
finalizeDepositclaim, then it is paid out. - No economic incentive. Abstract testnet ETH has no value, so nobody parks funds; anyone can withdraw at any time, so balances drain to zero.
- In-flight L2→L1 withdrawal — ETH is burned on L2 at withdraw time and does not exist on L1 until the batch is executed (up to ~a day on testnet). This is the "Waiting for L1 execution" state in the app. (Temporary.)
- Executed but unclaimed — after execution, ETH sits in L1Nullifier until someone submits
finalizeDeposit. (Temporary, but indefinite if never claimed.) - Lost or incorrect receiver key —
finalizeDepositonly pays the_l1Receiverbaked into the withdrawal message; a wrong or lost address locks the ETH permanently. (Permanent.) - Failed or expired L1→L2 deposit — if the L2 deposit tx fails (e.g.
l2GasLimittoo low) the minted value needsclaimFailedDeposit; expired priority txs need a manual reclaim. (Temporary.) - Paused bridge / wrong finalizer —
whenNotPausedreverts claims while paused; a withdrawal initiated via the legacy Mailbox path cannot be claimed throughL1Nullifier.finalizeDeposit(InvalidSelector) until the matching finalizer is used. (Temporary.)
Big bridges report large TVL because they run the escrow model: for every token bridged to L2 (USDC, WETH, …), an equal amount must remain locked in the L1 contract to back the circulating L2 supply. Their pools ≈ cumulative deposits − withdrawals, driven by multi-token support and real mainnet usage. This app bridges only the base token (ETH) on a testnet, which neither escrows reserves nor attracts deposits — so its standing balance is near zero. The meaningful metric for a base-token bridge is cumulative deposit/withdrawal volume, not the standing balance.
MIT