Skip to content

feat: bridge --asset eth — ETH rails via Wormhole (in + out) - #28

Merged
anil-rome merged 1 commit into
mainfrom
feat-bridge-eth-wormhole
Jul 23, 2026
Merged

feat: bridge --asset eth — ETH rails via Wormhole (in + out)#28
anil-rome merged 1 commit into
mainfrom
feat-bridge-eth-wormhole

Conversation

@anil-rome

Copy link
Copy Markdown
Contributor

Completes the bridge surface for ETH holders — the last missing rail of the from-home on-ramp.

What it adds

One knob on the existing verbs: --asset usdc|eth (default usdc — zero behavior change).

  • Inrome bridge <chain> --from sepolia --amount 0.002 --asset eth: wrap+transfer on the source (one user tx) → Wormhole VAA → Rome's sponsor completes on Solana → lands as wETH. Never gas (gas is USDC), so --intent is rejected for ETH; no settle EIP-712.
  • Outrome bridge <chain> --to sepolia --amount 0.002 --asset eth: approve+burn wETH on Rome (RomeBridgeWithdraw) → you claim on Ethereum only (the Wormhole route has no destination knob; the CLI refuses any other --to). The claim handle is rail-aware and carries the isTransferCompleted gotcha (re-redeeming an already-completed VAA reverts with a misleading gas error).
  • Engines asset-parameterized, not duplicated — quote-builder selection + per-asset decimals + a rail-agnostic claim-step detector are the whole delta.

Bonus fix the funded gate caught: activate three-state

A previously-activated account whose PDA drained below the burn reserve (each burn consumes rent) made activate() revert AlreadyActivated. runActivate now handles all three states: skip (≥ reserve) / first-time activate (0) / topUpUserPda at the on-chain rate (drained). Funded-proven live: refill to exactly USER_PDA_FUNDING (29,969,440 lamports) for ~1.03 USDC (0xe4aaf88d…).

Validation — funded ETH round trip on Hadrian

  • In: wrap 0xb01fe618… (Sepolia) → VAA → wETH 200000 (8dp) landed (balance 0 → 200000)
  • Top-up: 0xe4aaf88d… (drained-state path)
  • Out: burn 0x730db82a… SUCCESS (block 478350002) → claim ready → wETH back to 0 — full round trip
  • 122 unit tests (+7: 18dp parsing, WH-in collects the userSigns-less step-1 tx via the real SDK helper, WH-out no-dest builder + claim detection, 3 pre-network guards, drained-state top-up) · typecheck/build/MCP smoke clean · both directions live-dry-run through the CLI · leak-scan clean

Docs

GUIDES: ETH-in blurb + ETH-out rules (Ethereum-only, claims-are-yours, VAA gotcha) · ARCHITECTURE: both bridge rows asset-aware.

Feature PR, no version bump — next tag pins it.

One knob on the existing engines (asset-parameterized, not duplicated):
--asset usdc (default, CCTP, unchanged) | eth (Wormhole). In: wrap on the
source -> the sponsor completes on Solana -> lands as wETH (never gas; no
settle EIP-712). Out: approve+burn wETH on Rome via RomeBridgeWithdraw ->
the USER claims on Ethereum only (rail-aware claim handle carries the
isTransferCompleted gotcha). Guards fire pre-key/network: unknown asset,
eth-with-intent, eth --to must be the Ethereum rail.

Also fixes a real activate gap the funded gate caught: a previously-
activated PDA drained below the burn reserve made activate() revert
AlreadyActivated — runActivate now has three states (skip / activate /
topUpUserPda at the on-chain rate), funded-proven (refill to exactly
USER_PDA_FUNDING for ~1.03 USDC).

Funded round trip on Hadrian: ETH-in 0.002 (wrap 0xb01fe618 -> VAA ->
wETH 200000@8dp landed) -> activate top-up 0xe4aaf88d -> ETH-out burn
0x730db82a SUCCESS, claim ready, wETH back to 0. 122 unit tests; docs
(GUIDES ETH in/out + ARCHITECTURE rows).
@anil-rome
anil-rome merged commit e8d5635 into main Jul 23, 2026
6 checks passed
@anil-rome
anil-rome deleted the feat-bridge-eth-wormhole branch July 23, 2026 15:37
skansal-rome added a commit that referenced this pull request Jul 24, 2026
Pins the released surface to main. Since v0.9.0: bridge --asset eth (#28)
— ETH via Wormhole, in (Sepolia -> wETH) + out (wETH -> Ethereum only),
funded round-trip validated on Hadrian, plus the activate three-state fix
(drained-PDA top-up); AGENTS.md v3.2 (#29); README refresh (#30). Bumps
package.json/lock and the README install pin 0.9.0 -> 0.10.0.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant