> ## Documentation Index
> Fetch the complete documentation index at: https://zksync-sdk.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Deposits (L1 → L2)

> Fast, developer-focused deposits with validation, observability, and battle-tested patterns.

# Guide: Depositing funds L1 to L2

This guide provides a complete walkthrough for depositing funds from a L1 (Ethereum) to a L2 (ZKsync). We will use the `@dutterbutter/zksync-sdk` to simplify the process.

The SDK intelligently handles the deposit flow, automatically selecting the correct contract and route based on the token being used.

### Prerequisites

Before you begin, ensure you have the following:

1. **Node.js v20+** or **Bun.sh** installed.

2. An L1 wallet (private key) with some ETH to pay for the deposit and gas fees.

3. RPC endpoints for both the L1 and L2 you are targeting.

4. A project set up with `viem` or `ethers` and `@dutterbutter/zksync-sdk` installed.

   <CodeGroup>
     `bash title="viem" npm install viem @dutterbutter/zksync-sdk dotenv ` `bash
         title="ethers" npm install ethers @dutterbutter/zksync-sdk dotenv `
   </CodeGroup>

5. An `.env` file in your project's root directory with the following variables:

   ```ini theme={"theme":{"light":"vitesse-light","dark":"tokyo-night"}}
   # .env
   L1_RPC_URL="YOUR_L1_RPC_ENDPOINT"
   L2_RPC_URL="YOUR_L2_RPC_ENDPOINT"
   PRIVATE_KEY="YOUR_WALLET_PRIVATE_KEY"
   ```

### The Deposit Process: Step-by-Step

The process can be broken down into 5 key steps after initial setup.

#### Step 0: Setup and SDK Initialization

First, we need to connect to the L1 and L2 networks using your preferred library (`viem` or `ethers`) and then initialize the ZKsync SDK.

<CodeGroup>
  ```typescript title="viem" theme={"theme":{"light":"vitesse-light","dark":"tokyo-night"}}
  import 'dotenv/config';
  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';
  import { ETH_ADDRESS } from '@dutterbutter/zksync-sdk/core';

  // Load configuration from .env file
  const L1_RPC = process.env.L1_RPC_URL!;
  const L2_RPC = process.env.L2_RPC_URL!;
  const PRIVATE_KEY = process.env.PRIVATE_KEY!;

  // --- Clients (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);

  console.log(`Using account: ${account.address}`);

  ```

  ```typescript title="ethers" theme={"theme":{"light":"vitesse-light","dark":"tokyo-night"}}
  import 'dotenv/config';
  import { JsonRpcProvider, Wallet, parseEther } from 'ethers';
  import { createEthersClient, createEthersSdk } from '@dutterbutter/zksync-sdk/ethers';
  import { ETH_ADDRESS } from '@dutterbutter/zksync-sdk/core';

  // Load configuration from .env file
  const L1_RPC = process.env.L1_RPC_URL!;
  const L2_RPC = process.env.L2_RPC_URL!;
  const PRIVATE_KEY = process.env.PRIVATE_KEY!;

  // --- 1. Initialize Providers and Signer ---
  const l1Provider = new JsonRpcProvider(L1_RPC);
  const l2Provider = new JsonRpcProvider(L2_RPC);
  const signer = new Wallet(PRIVATE_KEY, l1Provider);

  // --- 2. Initialize the SDK ---
  const client = await createEthersClient({ l1: l1Provider, l2: l2Provider, signer });
  const sdk = createEthersSdk(client);

  console.log(`Using account: ${signer.address}`);
  ```
</CodeGroup>

#### Step 1: Define Deposit Parameters

Next, define the parameters for your deposit in an object. You’ll specify the deposit amount, the token address — which can represent ETH, a chain’s Base Token (like L1\_SOPH\_TOKEN\_ADDRESS), or any ERC-20 — and the recipient’s address on L2.

<CodeGroup>
  ```typescript title="ETH Token Deposit" theme={"theme":{"light":"vitesse-light","dark":"tokyo-night"}}
  const me = signer.address as Address;

  const depositParams = {
  amount: parseEther('0.01'), // The amount of ETH to deposit (e.g., 0.01 ETH)
  token: ETH_ADDRESS, // The address for native ETH
  to: me, // The recipient address on L2 (in this case, our own address)
  } as const;

  ```

  ```typescript title="ERC20 Token Deposit" theme={"theme":{"light":"vitesse-light","dark":"tokyo-night"}}
  const me = signer.address as Address;

  const depositParams = {
    amount: parseEther('0.01'), // The amount of ERC20 to deposit (e.g., 0.01 ERC20)
    token: ERC20_TOKEN_ADDRESS,  // The address for the ERC20 token
    to: me,                     // The recipient address on L2 (in this case, our own address)
  } as const;
  ```

  ```typescript title="Base Token Deposit" theme={"theme":{"light":"vitesse-light","dark":"tokyo-night"}}
  const me = signer.address as Address;

  const depositParams = {
    amount: parseEther('0.01'), // The amount of SOPH to deposit (e.g., 0.01 SOPH)
    token: L1_SOPH_TOKEN_ADDRESS, // The address for SOPH (or your chain's base token on L1)
    to: me, // The recipient address on L2 (in this case, our own address)
  } as const;
  ```
</CodeGroup>

<Accordion title="Advanced Parameters (Optional)">
  You can also specify advanced options for finer control over the transaction:

  * `l2GasLimit`: The gas limit for the L2 part of the transaction.
  * `gasPerPubdata`: The gas price per byte of public data.
  * `operatorTip`: A tip for the L2 operator.
  * `refundRecipient`: An address to receive any L1 gas refunds.
</Accordion>

#### Step 2: Quote the Deposit

Before sending, it's best practice to get a cost estimate using `sdk.deposits.quote`. This returns information about fees and the expected gas required.

<Note>
  Users can skip this step and go directly to `create`, but quoting helps avoid surprises.
</Note>

```typescript theme={"theme":{"light":"vitesse-light","dark":"tokyo-night"}}
// --- STEP 2: QUOTE ---
const quote = await sdk.deposits.quote(depositParams);
console.log('DEPOSIT QUOTE →', quote);
// Outputs estimated costs and gas limits
```

#### Step 3: Prepare the Transaction

The `sdk.deposits.prepare` method builds the final transaction plan. This step is useful for inspecting the transaction details before execution.

```typescript theme={"theme":{"light":"vitesse-light","dark":"tokyo-night"}}
// --- STEP 3: PREPARE ---
const plan = await sdk.deposits.prepare(depositParams);
console.log('TRANSACTION PLAN →', plan);
// Outputs the prepared transaction objects
```

#### Step 4: Create the Deposit

The `sdk.deposits.create` method executes the plan by sending the transaction to the L1 network. It returns a `handle` which is a unique identifier used to track the deposit's progress.

```typescript theme={"theme":{"light":"vitesse-light","dark":"tokyo-night"}}
// --- STEP 4: CREATE (send tx) ---
const handle = await sdk.deposits.create(depositParams);
console.log('TRANSACTION CREATED →', handle);
// Outputs the handle, e.g., { type: 'deposit', hash: '0x...' }
```

#### Step 5: Track the Transaction Status

A deposit is a two-stage process: inclusion on L1 and then execution on L2. The SDK provides tools to track both stages.

* **`sdk.deposits.status(handle)`:** Check the current status of the deposit at any time.
* **`sdk.deposits.wait(handle, options)`:** Pause execution until the deposit reaches a specific stage.

```typescript theme={"theme":{"light":"vitesse-light","dark":"tokyo-night"}}
// --- STEP 5: TRACK ---
console.log('⏳ Waiting for L1 inclusion...');
const l1Receipt = await sdk.deposits.wait(handle, { for: 'l1' });
console.log('✅ L1 included at block:', l1Receipt?.blockNumber);

// Check the status again after L1 inclusion
const statusAfterL1 = await sdk.deposits.status(handle);
console.log('STATUS (after L1) →', statusAfterL1);

console.log('⏳ Waiting for L2 execution...');
const l2Receipt = await sdk.deposits.wait(handle, { for: 'l2' });
console.log('✅ L2 executed at block:', l2Receipt?.blockNumber);
```

Once the `wait` for L2 execution completes, your ETH has successfully been deposited to your L2 address.

### Full Code Example

Here is the complete, runnable script for your reference.

<Accordion title="Click to view the full script.">
  <CodeGroup>
    ```typescript title="viem" theme={"theme":{"light":"vitesse-light","dark":"tokyo-night"}}
    /**
     * Example: Deposit ETH into an L2
     */
    import 'dotenv/config';
    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';
    import type { Address } from '@dutterbutter/zksync-sdk/core';
    import { ETH_ADDRESS } from '@dutterbutter/zksync-sdk/core';

    // --- CONFIG ---
    // Use env if available, otherwise fall back to local dev defaults.
    const L1_RPC = process.env.L1_RPC_URL ?? 'http://localhost:8545';
    const L2_RPC = process.env.L2_RPC_URL ?? 'http://localhost:3050';
    const PRIVATE_KEY = process.env.PRIVATE_KEY ?? '';

    async function main() {
      if (!PRIVATE_KEY || PRIVATE_KEY.length !== 66) {
        throw new Error('⚠️ Set a 0x-prefixed 32-byte PRIVATE_KEY in your .env');
      }

      // --- Clients (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),
      });

      // Show balances for sanity
      const [balL1, balL2] = await Promise.all([
        l1.getBalance({ address: account.address }),
        l2.getBalance({ address: account.address }),
      ]);
      console.log('Using account:', account.address);
      console.log('L1 balance:', balL1.toString());
      console.log('L2 balance:', balL2.toString());

      // --- Init SDK ---
      const client = createViemClient({ l1, l2, l1Wallet });
      const sdk = createViemSdk(client);

      // --- Deposit params ---
      const me = account.address as Address;
      const params = {
        amount: parseEther('0.01'),
        token: ETH_ADDRESS, // ETH sentinel
        to: me,
        // optional:
        // l2GasLimit: 300_000n,
        // gasPerPubdata: 800n,
        // operatorTip: 0n,
        // refundRecipient: me,
      } as const;

      // 1) QUOTE
      const quote = await sdk.deposits.quote(params);
      console.log('QUOTE →', quote);

      // 2) PREPARE (no txs sent)
      const plan = await sdk.deposits.prepare(params);
      console.log('PREPARE →', plan);

      // 3) CREATE (send deposit)
      const handle = await sdk.deposits.create(params);
      console.log('CREATE →', handle);

      // 4) STATUS
      const status = await sdk.deposits.status(handle);
      console.log('STATUS →', status);

      // 5) WAIT: L1 inclusion
      console.log('⏳ Waiting for L1 inclusion...');
      const l1Receipt = await sdk.deposits.wait(handle, { for: 'l1' });
      console.log('✅ L1 included at block:', l1Receipt?.blockNumber);

      const statusAfterL1 = await sdk.deposits.status(handle);
      console.log('STATUS (after L1) →', statusAfterL1);

      // 6) WAIT: L2 execution
      console.log('⏳ Waiting for L2 execution...');
      const l2Receipt = await sdk.deposits.wait(handle, { for: 'l2' });
      console.log('✅ L2 executed at block:', l2Receipt?.blockNumber);
    }

    main().catch((err) => {
      console.error(err);
      process.exit(1);
    });
    ```

    ```typescript title="ethers" theme={"theme":{"light":"vitesse-light","dark":"tokyo-night"}}
    /**
     * Example: Deposit ETH from L1 into a ZKsync L2
     */
    import 'dotenv/config';
    import { JsonRpcProvider, Wallet, parseEther } from 'ethers';
    import { createEthersClient, createEthersSdk } from '@dutterbutter/zksync-sdk/ethers';
    import type { Address } from '@dutterbutter/zksync-sdk/core';
    import { ETH_ADDRESS } from '@dutterbutter/zksync-sdk/core';

    // --- CONFIG ---
    const L1_RPC = process.env.L1_RPC_URL ?? 'http://localhost:8545';
    const L2_RPC = process.env.L2_RPC_URL ?? 'http://localhost:3050';
    const PRIVATE_KEY = process.env.PRIVATE_KEY ?? '';

    async function main() {
      if (!PRIVATE_KEY) throw new Error('⚠️ Set your PRIVATE_KEY in the .env file');

      const l1 = new JsonRpcProvider(L1_RPC);
      const l2 = new JsonRpcProvider(L2_RPC);
      const signer = new Wallet(PRIVATE_KEY, l1);

      const me = signer.address as Address;
      console.log('Using account:', me);

      // --- INIT SDK ---
      const client = await createEthersClient({ l1, l2, signer });
      const sdk = createEthersSdk(client);

      // --- DEPOSIT PARAMS ---
      const params = {
        amount: parseEther('0.01'),
        token: ETH_ADDRESS,
        to: me,
      } as const;

      // --- STEP 1: QUOTE ---
      const quote = await sdk.deposits.quote(params);
      console.log('QUOTE →', quote);

      // --- STEP 2: PREPARE ---
      const plan = await sdk.deposits.prepare(params);
      console.log('PREPARE →', plan);

      // --- STEP 3: CREATE (send tx) ---
      const handle = await sdk.deposits.create(params);
      console.log('CREATE →', handle);

      // --- STEP 4 & 5: TRACK ---
      console.log('⏳ Waiting for L1 inclusion...');
      const l1Receipt = await sdk.deposits.wait(handle, { for: 'l1' });
      console.log('✅ L1 included at block:', l1Receipt?.blockNumber);

      console.log('⏳ Waiting for L2 execution...');
      const l2Receipt = await sdk.deposits.wait(handle, { for: 'l2' });
      console.log('✅ L2 executed at block:', l2Receipt?.blockNumber);

      const finalStatus = await sdk.deposits.status(handle);
      console.log('FINAL STATUS →', finalStatus);
    }

    main().catch((err) => {
      console.error(err);
      process.exit(1);
    });
    ```
  </CodeGroup>
</Accordion>

### Related API Reference

For more detailed information on the methods used in this guide, see the official API reference:

* [`sdk.deposits.quote()`](https://zksync-sdk.mintlify.app/api-reference/ethers/deposits#quote-p%3A-depositparams-%E2%86%92-promise%3Cdepositquote%3E)
* [`sdk.deposits.prepare()`](https://zksync-sdk.mintlify.app/api-reference/ethers/deposits#prepare-p%3A-depositparams-%E2%86%92-promise%3Cdepositplan%3Ctransactionrequest%3E%3E)
* [`sdk.deposits.create()`](https://zksync-sdk.mintlify.app/api-reference/ethers/deposits#create-p%3A-depositparams-%E2%86%92-promise%3Cdeposithandle%3Ctransactionrequest%3E%3E)
* [`sdk.deposits.status()`](https://zksync-sdk.mintlify.app/api-reference/ethers/deposits#status-handleorhash-%E2%86%92-promise%3Cdepositstatus%3E)
* [`sdk.deposits.wait()`](https://zksync-sdk.mintlify.app/api-reference/ethers/deposits#wait-handleorhash%2C-%7B-for%3A-l1-%7C-l2-%7D-%E2%86%92-promise%3Ctransactionreceipt-%7C-null%3E)
