Status vs Wait
Snapshot progress with status(...) or block until a checkpoint with wait(..., { for }) for deposits and withdrawals.
The SDK exposes two complementary ways to track progress:
status(...)— returns a non-blocking snapshot of where an operation is.wait(..., { for })— blocks/polls until a specified checkpoint is reached.
Both apply to deposits and withdrawals.
Use status(...) for UI refreshes; use wait(...) when you need to gate logic on inclusion/finality.
[!NOTE] You can pass either a handle returned from
create(...)or a raw transaction hash.
Withdrawals
withdrawals.status(h | l2TxHash): Promise<WithdrawalStatus>
Input
h: aWithdrawalWaitable(e.g., fromcreate) or the L2 transaction hashHex.
Phases
| Phase | Meaning |
|---|---|
UNKNOWN | Handle doesn’t contain an L2 hash yet. |
L2_PENDING | L2 transaction not yet included. |
PENDING | L2 included, not yet ready to finalize on L1. |
READY_TO_FINALIZE | Finalization on L1 would succeed now. |
FINALIZED | Finalized on L1; funds released. |
Notes
- No L2 receipt ⇒
L2_PENDING - Finalization key derivable but not ready ⇒
PENDING - Already finalized ⇒
FINALIZED
withdrawals-status.ts
const s = await sdk.withdrawals.status(handleOrHash);
// s.phase ∈ 'UNKNOWN' | 'L2_PENDING' | 'PENDING' | 'READY_TO_FINALIZE' | 'FINALIZED'
withdrawals.wait(h | l2TxHash, { for, pollMs?, timeoutMs? })
Targets
| Target | Resolves with | |
|---|---|---|
{ for: 'l2' } | L2 receipt (`TransactionReceipt | null`) |
{ for: 'ready' } | null when finalization becomes possible | |
{ for: 'finalized' } | L1 receipt when finalized, or null if finalized but receipt not found |
Behavior
- If the handle has no L2 hash, returns
nullimmediately. - Default polling interval: 5500 ms (override with
pollMs). timeoutMs→ returnsnullon deadline.
withdrawals-wait.ts
// Wait for L2 inclusion → get L2 receipt (augmented with l2ToL1Logs if available)
const l2Rcpt = await sdk.withdrawals.wait(handle, { for: 'l2', pollMs: 5000 });
// Wait until it becomes finalizable (no side effects)
await sdk.withdrawals.wait(handle, { for: 'ready' });
// Wait for L1 finalization → L1 receipt (or null if not retrievable)
const l1Rcpt = await sdk.withdrawals.wait(handle, { for: 'finalized', timeoutMs: 15 * 60_000 });
[!TIP] Building a UI? Use
status(...)to paint the current phase and enable/disable the Finalize button when the phase isREADY_TO_FINALIZE.
Deposits
deposits.status(h | l1TxHash): Promise<DepositStatus>
Input
h: aDepositWaitable(fromcreate) or L1 transaction hashHex.
Phases
| Phase | Meaning |
|---|---|
UNKNOWN | No L1 hash present on the handle. |
L1_PENDING | L1 receipt missing. |
L1_INCLUDED | L1 included; L2 hash not yet derivable from logs. |
L2_PENDING | L2 hash known but L2 receipt missing. |
L2_EXECUTED | L2 receipt present with status === 1. |
L2_FAILED | L2 receipt present with status !== 1. |
deposits-status.ts
const s = await sdk.deposits.status(handleOrL1Hash);
// s.phase ∈ 'UNKNOWN' | 'L1_PENDING' | 'L1_INCLUDED' | 'L2_PENDING' | 'L2_EXECUTED' | 'L2_FAILED'
deposits.wait(h | l1TxHash, { for: 'l1' | 'l2' })
Targets
| Target | Resolves with |
|---|---|
{ for: 'l1' } | L1 receipt or null |
{ for: 'l2' } | L2 receipt or null (waits L1 inclusion then L2 execution) |
deposits-wait.ts
const l1Rcpt = await sdk.deposits.wait(handle, { for: 'l1' });
const l2Rcpt = await sdk.deposits.wait(handle, { for: 'l2' });
[!NOTE]
wait(..., { for: 'l2' })waits for both L1 inclusion and canonical L2 execution.
Practical Patterns
Pick the Right Tool
- Use
status(...)for poll-less UI refreshes (e.g., on page focus or controlled intervals). - Use
wait(...)for workflow gating (scripts, jobs, or “continue when X happens”).
Timeouts & Polling
polling.ts
const ready = await sdk.withdrawals.wait(handle, {
for: 'ready',
pollMs: 5500, // minimum enforced internally
timeoutMs: 30 * 60_000, // 30 minutes → returns null on deadline
});
if (ready === null) {
// timeout or not yet finalizable — decide whether to retry or show a hint
}
Error Handling
- Network hiccup while fetching receipts ⇒ throws
ZKsyncErrorof kindRPC. - Internal decode issue ⇒ throws
ZKsyncErrorof kindINTERNAL.
Prefer no-throw variants if you want explicit flow control:
no-throw.ts
const r = await sdk.withdrawals.tryWait(handle, { for: 'finalized' });
if (!r.ok) {
console.error('Finalize wait failed:', r.error);
} else {
console.log('Finalized L1 receipt:', r.value);
}
Tips & Edge Cases
- Handles vs hashes: Passing a handle without the relevant hash yields
UNKNOWN/null. If you already have a hash, pass it directly. - Finalization windows: For withdrawals,
READY_TO_FINALIZEmay take a while. Usestatus(...)for responsive UI and reservewait(..., { for: 'finalized' })for blocking logic. - Retries: If a
waitreturnsnullbecause oftimeoutMs, safely callstatus(...)to decide whether to retry or surface user guidance.