Skip to main content

At a glance

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

Import

import {
  createPublicClient,
  createWalletClient,
  http,
  parseEther,
  type Account,
  type Chain,
  type Transport,
  type WalletClient,
} from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { createViemClient, createViemSdk } from '@dutterbutter/zksync-sdk/viem';

const account = privateKeyToAccount(PRIVATE_KEY as `0x${string}`);
const l1 = createPublicClient({ transport: http(L1_RPC) });
const l2 = createPublicClient({ transport: http(L2_RPC) });
const l1Wallet: WalletClient<Transport, Chain, Account> = createWalletClient({
  account,
  transport: http(L1_RPC),
});

// --- 2. Initialize the SDK ---
const client = createViemClient({ l1, l2, l1Wallet });
const sdk = createViemSdk(client);
// sdk.deposits → DepositsResource

Quick start

Deposit 0.1 ETH from L1 → L2 and wait for L2 execution:
const handle = await sdk.deposits.create({
  token: ETH_ADDRESS, // 0x…00 for ETH
  amount: parseEther('0.1'),
  to: await signer.getAddress(),
});

const l2Receipt = await sdk.deposits.wait(handle, { for: 'l2' }); // null only if no L1 hash
For UX that never throws, use the try* variants and branch on ok.

Route selection (automatic)

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

Method reference

quote(p: DepositParams) → Promise<DepositQuote>

Estimate the operation (route, approvals, gas hints). Does not send txs. Returns: DepositQuote
const q = await sdk.deposits.quote({
  token: ETH_L1,
  amount: parseEther('0.25'),
  to: await signer.getAddress(),
});
/*
{
  route: "eth-base" | "eth-nonbase" | "erc20-base" | "erc20-nonbase",
  approvalsNeeded: [{ token, spender, amount }],
  baseCost?: bigint,
  mintValue?: bigint,
  suggestedL2GasLimit?: bigint,
  gasPerPubdata?: bigint
}
*/
If approvalsNeeded is non-empty (ERC-20), create will include those steps automatically.

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

Result-style quote.

prepare(p: DepositParams) → Promise<DepositPlan<TransactionRequest>>

Builds the plan (ordered steps + unsigned txs) without sending. Returns: DepositPlan
const plan = await sdk.deposits.prepare({ token: ETH_L1, amount: parseEther('0.05'), to });
/*
{
  route,
  summary: DepositQuote,
  steps: [
    { key: "approve:USDC", kind: "approve", tx: TransactionRequest },
    { key: "bridge",       kind: "bridge",  tx: TransactionRequest }
  ]
}
*/

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

Result-style prepare.

create(p: DepositParams) → Promise<DepositHandle<TransactionRequest>>

Prepares and executes all required L1 steps. Returns a handle (with L1 tx hash and per-step hashes). Returns: DepositHandle
const handle = await sdk.deposits.create({ token, amount, to });
/*
{
  kind: "deposit",
  l1TxHash: Hex,
  stepHashes: Record<string, Hex>,
  plan: DepositPlan
}
*/
If any step reverts, create throws a typed error. Prefer tryCreate to avoid exceptions.

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

Result-style create.

status(handleOrHash) → Promise<DepositStatus>

Resolve current phase for a deposit. Accepts the DepositHandle from create or a raw L1 tx hash. Phases
  • UNKNOWN — no L1 hash provided
  • L1_PENDING — L1 receipt not yet found
  • L1_INCLUDED — included on L1; L2 hash not derivable yet
  • L2_PENDING — L2 hash known; waiting for L2 receipt
  • L2_EXECUTED — L2 receipt found with status === 1
  • L2_FAILED — L2 receipt found with status !== 1
const s = await sdk.deposits.status(handle);
// { phase, l1TxHash, l2TxHash? }

wait(handleOrHash, { for: 'l1' | 'l2' }) → Promise<TransactionReceipt | null>

Block until the chosen checkpoint.
  • { for: 'l1' } → L1 receipt (or null if no L1 hash available)
  • { for: 'l2' } → L2 receipt after canonical execution (or null if no L1 hash)
const l1Receipt = await sdk.deposits.wait(handle, { for: 'l1' });
const l2Receipt = await sdk.deposits.wait(handle, { for: 'l2' });

tryWait(handleOrHash, opts) → Result<TransactionReceipt>

Result-style wait.

End-to-end examples

ETH deposit (typical)

const handle = await sdk.deposits.create({
  token: ETH_ADDRESS,
  amount: parseEther('0.1'),
  to: await signer.getAddress(),
});

await sdk.deposits.wait(handle, { for: 'l2' });

ERC-20 deposit (with automatic approvals)

const handle = await sdk.deposits.create({
  token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // example: USDC
  amount: 1_000_000n, // 1.0 USDC (6 dp)
  to: await signer.getAddress(),
});

const l1Receipt = await sdk.deposits.wait(handle, { for: 'l1' });

Types (overview)

type DepositParams = {
  token: Address; // 0x…00 for ETH
  amount: bigint; // wei
  to: Address; // L2 recipient
};

type DepositQuote = {
  route: 'eth-base' | 'eth-nonbase' | 'erc20-base' | 'erc20-nonbase';
  approvalsNeeded: Array<{ token: Address; spender: Address; amount: bigint }>;
  baseCost?: bigint;
  mintValue?: bigint;
  suggestedL2GasLimit?: bigint;
  gasPerPubdata?: bigint;
};

type DepositPlan<TTx = TransactionRequest> = {
  route: DepositQuote['route'];
  summary: DepositQuote;
  steps: Array<{ key: string; kind: string; tx: TTx }>;
};

type DepositHandle<TTx = TransactionRequest> = {
  kind: 'deposit';
  l1TxHash: Hex;
  stepHashes: Record<string, Hex>;
  plan: DepositPlan<TTx>;
};

type DepositStatus =
  | { phase: 'UNKNOWN'; l1TxHash: Hex }
  | { phase: 'L1_PENDING'; l1TxHash: Hex }
  | { phase: 'L1_INCLUDED'; l1TxHash: Hex }
  | { phase: 'L2_PENDING'; l1TxHash: Hex; l2TxHash: Hex }
  | { phase: 'L2_EXECUTED'; l1TxHash: Hex; l2TxHash: Hex }
  | { phase: 'L2_FAILED'; l1TxHash: Hex; l2TxHash: Hex };
Prefer the try* variants if you want to avoid exceptions and work with result objects.

Notes & pitfalls

  • ETH sentinel: use the canonical 0x…00 address when passing ETH as token.
  • Receipts timing: wait({ for: 'l2' }) resolves on canonical L2 execution; it can take longer than L1 inclusion.
  • Gas hints: suggestedL2GasLimit and gasPerPubdata are hints; advanced users may override via low-level calls from the plan.