Bridging between L2s with small size

Why I treat L2-to-L2 bridging as its own workflow

Bridging between L2s looks like a simple transfer, but the mechanics are different from a normal swap. The path you choose decides how long funds are locked, how many contracts you trust, and how many fees you pay on a small wallet.

This post covers the two main paths (canonical vs liquidity bridges), a quick fee-comparison script I use before sending anything, and the risk checks I run when the amount is under $1k.

How L2 bridges actually move funds

Canonical path: slow but minimal extra trust

Most optimistic rollups use a standard bridge that routes withdrawals back to Ethereum, then you deposit to the destination L2. On OP Stack chains, withdrawals through the Standard Bridge require a 7-day challenge period before finalization on L1. Optimism docs OP Stack withdrawals spec

Arbitrum’s official bridge has the same multi-day wait for withdrawals back to Ethereum, with the docs calling out a 7-8 day window before funds can be claimed. Arbitrum bridge docs

If you follow this path to move between L2s, you have at least two on-chain steps (withdraw to L1, then deposit to the destination). It is slower, but you are not depending on third-party liquidity to get paid out early.

Liquidity bridges: fast but more moving parts

Liquidity bridges front you funds on the destination chain and settle the accounting later. Hop does this with market makers ("Bonders") who front liquidity using hTokens that are swapped in an AMM. Hop docs

Across uses a relayer model: you deposit into a Spoke Pool on the source chain, relayers pay you on the destination chain, and the relayer is reimbursed from a Hub Pool after an optimistic oracle verifies the relay. Across overview

These paths are much faster, but you are adding smart-contract and liquidity-provider trust to skip the standard bridge delay.

flowchart LR
  A["L2 A"] -->|"Withdraw to L1 (challenge period)"| B["Ethereum L1"]
  B -->|"Deposit"| C["L2 B"]
  A -->|"Liquidity bridge payout"| C

My small-size bridging workflow

  1. I choose the destination L2 and confirm I can pay gas on both sides before I bridge anything.
  2. I check the canonical bridge path first, then compare a liquidity bridge if I need speed.
  3. I cap the first transfer to a small test amount (usually $100-$250) and wait for full settlement.
  4. I record the source tx hash, the destination receive tx, and the total time so I can compare future runs.
  5. I only scale to a larger transfer after the small test finishes without surprises.

Code: compare bridge paths with fee math

I do a quick fee check before committing. This is not a bridge quote engine; it is just a small calculator to compare the fees and expected net received once you have numbers from the bridge UIs.

from dataclasses import dataclass


@dataclass
class BridgeQuote:
    name: str
    send_amount_usd: float
    bridge_fee_usd: float
    slippage_usd: float
    source_gas_usd: float
    destination_gas_usd: float

    def net_received(self) -> float:
        """Return expected amount received after fees and slippage."""
        return self.send_amount_usd - self.bridge_fee_usd - self.slippage_usd

    def total_cost(self) -> float:
        """Return all-in cost including gas on both chains."""
        return self.bridge_fee_usd + self.slippage_usd + self.source_gas_usd + self.destination_gas_usd


def compare(quotes: list[BridgeQuote]) -> None:
    """Print a quick comparison of net received and total cost."""
    for quote in quotes:
        print(
            f"{quote.name}: net ${quote.net_received():.2f}, "
            f"total cost ${quote.total_cost():.2f}"
        )


if __name__ == "__main__":
    # Replace these placeholder numbers with quotes from the bridge UIs.
    quotes = [
        BridgeQuote(
            name="Canonical via L1",
            send_amount_usd=500,
            bridge_fee_usd=0.00,
            slippage_usd=0.00,
            source_gas_usd=1.25,
            destination_gas_usd=3.50,  # L1 finalize + L2 deposit gas
        ),
        BridgeQuote(
            name="Liquidity bridge",
            send_amount_usd=500,
            bridge_fee_usd=2.40,
            slippage_usd=0.60,
            source_gas_usd=0.90,
            destination_gas_usd=0.90,
        ),
    ]

    compare(quotes)

If the total cost difference is only a couple of dollars, I default to the path with fewer moving parts. For a sub-$1k wallet, simplicity is a feature.

Risk analysis: L2-to-L2 bridging

  • Technical risk (bridge mechanics). Standard bridges for optimistic rollups include a 7-day challenge period for withdrawals, which can lock funds longer than expected. Optimism docs OP Stack withdrawals spec
  • Technical risk (liquidity bridge design). Liquidity bridges rely on relayers or bonders to front funds and get reimbursed later, which adds smart-contract and counterparty risk. Hop docs Across overview
  • Economic risk (liquidity and slippage). AMM-based bridging can see worse rates when liquidity is thin, which matters more at small sizes where a few dollars is a large percentage. Hop docs
  • Operational risk (gas on both chains). You often need gas on the source and destination chains, and canonical withdrawals require an L1 finalization step that costs gas. OP Stack withdrawals spec

Practical takeaways for a lean wallet

  • Run a tiny test transfer first and record time-to-receive before you scale size.
  • Prefer the simpler path when fee differences are small.
  • Keep a small gas buffer on both chains before initiating any bridge.
  • Treat liquidity bridges as a convenience tradeoff, not a risk-free upgrade.

Sources worth keeping open

  • OP Stack Standard Bridge delay notes
  • OP Stack withdrawals specification
  • Arbitrum bridge user docs
  • Hop protocol overview
  • Across protocol overview