Skip to main content

At a glance

  • Resource: sdk.withdrawals
  • Typical flow: quote → create → wait({ for: 'l2' }) → wait({ for: 'ready' }) → finalize
  • Auto-routing: ETH vs ERC-20 and base-token vs non-base handled internally
  • Error style: Throwing methods (quote, prepare, create, status, wait, finalize) + result variants (tryQuote, tryPrepare, tryCreate, tryWait, tryFinalize)

Import

Quick start

Withdraw 0.1 ETH from L2 → L1 and finalize on L1:
Withdrawals are two-phase: inclusion on L2, then finalization on L1. You can call finalize directly; it will throw if not yet ready. Prefer wait(..., { for: 'ready' }) to avoid that.

Route selection (automatic)

  • eth-base — Base token is ETH on L2
  • eth-nonbase — Base token is not ETH on L2
  • erc20-nonbase — Withdrawing an ERC-20 that is not the base token
You do not pass a route; it’s derived from network metadata + token.

Method reference

quote(p: WithdrawParams) → Promise<WithdrawQuote>

Estimate the operation (route, approvals, gas hints). Does not send txs. Returns: WithdrawQuote

tryQuote(p) → Promise<{ ok: true; value: WithdrawQuote } | { ok: false; error }>

Result-style quote.

prepare(p: WithdrawParams) → Promise<WithdrawPlan<TransactionRequest>>

Builds the plan (ordered L2 steps + unsigned txs) without sending. Returns: WithdrawPlan

tryPrepare(p) → Promise<{ ok: true; value: WithdrawPlan } | { ok: false; error }>

Result-style prepare.

create(p: WithdrawParams) → Promise<WithdrawHandle<TransactionRequest>>

Prepares and executes required L2 steps. Returns a handle with the L2 tx hash. Returns: WithdrawHandle
If any L2 step reverts, create throws a typed error. Prefer tryCreate for a result object.

tryCreate(p) → Promise<{ ok: true; value: WithdrawHandle } | { ok: false; error }>

Result-style create.

status(handleOrHash) → Promise<WithdrawalStatus>

Report current phase for a withdrawal. Accepts the WithdrawHandle from create or a raw L2 tx hash. Phases
  • UNKNOWN — no L2 hash provided
  • L2_PENDING — L2 receipt missing
  • PENDING — included on L2 but not yet finalizable
  • READY_TO_FINALIZE — can be finalized on L1 now
  • FINALIZED — already finalized on L1

wait(handleOrHash, { for: 'l2' | 'ready' | 'finalized', pollMs?, timeoutMs? })

Block until a target is reached.
  • { for: 'l2' } → resolves L2 receipt (TransactionReceiptZKsyncOS) or null
  • { for: 'ready' } → resolves null when finalizable
  • { for: 'finalized' } → resolves L1 receipt (if found) or null
Default polling is 5500 ms (min 1000 ms). Use timeoutMs for long windows.

tryWait(handleOrHash, opts) → Result<TransactionReceipt | null>

Result-style wait.

finalize(l2TxHash: Hex) → Promise<{ status: WithdrawalStatus; receipt?: TransactionReceipt }>

Sends the L1 finalize transaction if ready. If already finalized, returns the status without sending.
If not ready, finalize throws a typed STATE error. Use status(…) or wait(..., { for: 'ready' }) first to avoid throws.

tryFinalize(l2TxHash) → Promise<{ ok: true; value: { status: WithdrawalStatus; receipt?: TransactionReceipt } } | { ok: false; error }>

Result-style finalize.

End-to-end examples

Minimal happy path

Types (overview)

Notes & pitfalls

  • Two chains, two receipts: inclusion on L2 and finalization on L1 are independent events.
  • Polling strategy: for production UIs, prefer wait({ for: 'ready' }) then finalize; it avoids premature finalize calls.
  • Approvals: if withdrawing ERC-20 requires approvals, create will include those steps automatically.