diff --git a/.changeset/real-chicken-sleep.md b/.changeset/real-chicken-sleep.md
new file mode 100644
index 0000000000..1fa363cf29
--- /dev/null
+++ b/.changeset/real-chicken-sleep.md
@@ -0,0 +1,5 @@
+---
+"viem": minor
+---
+
+Added `deposit` action, apply and undo alias util functions in ZKsync extension
diff --git a/site/pages/zksync/actions/deposit.md b/site/pages/zksync/actions/deposit.md
new file mode 100644
index 0000000000..76f9607c53
--- /dev/null
+++ b/site/pages/zksync/actions/deposit.md
@@ -0,0 +1,426 @@
+---
+description: Transfers the specified token from the associated account on the L1 network to the target account on the L2 network.
+---
+
+# deposit
+
+Transfers the specified token from the associated account on the L1 network to the target account on the L2 network.
+The token can be either ETH or any ERC20 token. For ERC20 tokens, enough approved tokens must be associated with
+the specified L1 bridge (default one or the one defined in `bridgeAddress`).
+In this case, depending on is the chain ETH-based or not `approveToken` or `approveBaseToken`
+can be enabled to perform token approval. If there are already enough approved tokens for the L1 bridge,
+token approval will be skipped.
+
+## Usage
+
+:::code-group
+
+```ts [example.ts]
+import { account, walletClient, zksyncClient } from './config'
+import { legacyEthAddress } from 'viem/zksync'
+
+// deposit ETH
+const hash = await walletClient.deposit({
+  account,
+  client: zksyncClient,
+  token: legacyEthAddress,
+  amount: 7_000_000_000n,
+  to: account.address,
+  refundRecipient: account.address,
+})
+
+// deposit ERC20
+const txHash = await walletClient.deposit({
+    account,
+    client: zksyncClient,
+    token: '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55',
+    amount: 20n,
+    to: account.address,
+    approveToken: true,
+    refundRecipient: account.address,
+})
+```
+
+```ts [config.ts]
+import { createWalletClient, createPublicClient, custom } from 'viem'
+import { privateKeyToAccount } from 'viem/accounts'
+import { zksync, mainnet } from 'viem/chains'
+import { publicActionsL2, walletActionsL1 } from 'viem/zksync'
+
+export const zksyncClient = createPublicClient({
+  chain: zksync,
+  transport: custom(window.ethereum)
+}).extend(publicActionsL2())
+
+export const walletClient = createWalletClient({
+  chain: mainnet,
+  transport: custom(window.ethereum)
+}).extend(walletActionsL1())
+
+// JSON-RPC Account
+export const [account] = await walletClient.getAddresses()
+// Local Account
+export const account = privateKeyToAccount(...)
+```
+
+:::
+
+### Account Hoisting
+
+If you do not wish to pass an `account` to every `deposit`, you can also hoist the Account on the Wallet Client (see `config.ts`).
+
+[Learn more](/docs/clients/wallet#account).
+
+:::code-group
+
+```ts [example.ts]
+import { walletClient, zksyncClient } from './config'
+import { legacyEthAddress } from 'viem/zksync'
+
+// deposit ETH
+const hash = await walletClient.deposit({
+  client: zksyncClient,
+  token: legacyEthAddress,
+  amount: 7_000_000_000n,
+  to: walletClient.account.address,
+  refundRecipient: walletClient.account.address,
+})
+
+// deposit ERC20
+const txHash = await walletClient.deposit({
+  client: zksyncClient,
+  token: '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55',
+  amount: 20n,
+  to: walletClient.account.address,
+  approveToken: true,
+  refundRecipient: walletClient.account.address,
+})
+```
+
+```ts [config.ts (JSON-RPC Account)]
+import { createWalletClient, custom } from 'viem'
+import { zksync } from 'viem/chains'
+import { publicActionsL2, walletActionsL1 } from 'viem/zksync'
+
+export const zksyncClient = createPublicClient({
+  chain: zksync,
+  transport: custom(window.ethereum)
+}).extend(publicActionsL2())
+
+// Retrieve Account from an EIP-1193  Provider. // [!code focus]
+const [account] = await window.ethereum.request({  // [!code focus]
+  method: 'eth_requestAccounts' // [!code focus]
+}) // [!code focus]
+
+export const walletClient = createWalletClient({
+  account,
+  transport: custom(window.ethereum) // [!code focus]
+}).extend(walletActionsL1())
+```
+
+```ts [config.ts (Local Account)]
+import { createWalletClient, custom } from 'viem'
+import { zksync } from 'viem/chains'
+import { privateKeyToAccount } from 'viem/accounts'
+import { publicActionsL2, walletActionsL1 } from 'viem/zksync'
+
+export const zksyncClient = createPublicClient({
+  chain: zksync,
+  transport: custom(window.ethereum)
+}).extend(publicActionsL2())
+
+export const walletClient = createWalletClient({
+  account: privateKeyToAccount('0x...'), // [!code focus]
+  transport: custom(window.ethereum)
+}).extend(walletActionsL1())
+```
+
+:::
+
+## Returns
+
+[`Hash`](/docs/glossary/types#hash)
+
+The [Transaction](/docs/glossary/terms#transaction) hash.
+
+## Parameters
+
+### client
+
+- **Type:** `Client`
+
+The L2 client for fetching data from L2 chain.
+
+```ts
+const hash = await walletClient.deposit({
+  client: zksyncClient, // [!code focus]
+  token: '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55',
+  amount: 20n,
+  to: walletClient.account.address,
+  approveToken: true,
+  refundRecipient: walletClient.account.address,
+})
+```
+
+### token
+
+- **Type:** `Address`
+
+The address of the token to deposit.
+
+```ts
+const hash = await walletClient.deposit({
+  client: zksyncClient,
+  token: '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55', // [!code focus]
+  amount: 20n,
+  to: walletClient.account.address,  
+  approveToken: true,
+  refundRecipient: walletClient.account.address,
+})
+```
+
+### amount
+
+- **Type:** `bigint`
+
+The amount of the token to deposit.
+
+```ts
+const hash = await walletClient.deposit({
+  client: zksyncClient,
+  token: '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55',
+  amount: 20n, // [!code focus]
+  to: walletClient.account.address,
+  approveToken: true,
+  refundRecipient: walletClient.account.address,
+})
+```
+
+### to (optional)
+
+- **Type:** `Address`
+- **Default:** `walletClient.account`
+
+The address that will receive the deposited tokens on L2.
+
+```ts
+const hash = await walletClient.deposit({
+  client: zksyncClient,
+  token: '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55',
+  amount: 20n,
+  to: walletClient.account.address, // [!code focus]
+  approveToken: true,
+  refundRecipient: walletClient.account.address,
+})
+```
+
+### operatorTip (optional)
+
+- **Type:** `bigint`
+
+The tip the operator will receive on top of the base cost of the transaction.
+Currently, ZKsync node do not consider this tip.
+
+```ts
+const hash = await walletClient.deposit({
+  client: zksyncClient,
+  token: '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55',
+  amount: 20n,
+  to: walletClient.account.address,
+  operatorTip: 100_000n, // [!code focus]
+  approveToken: true,
+  refundRecipient: walletClient.account.address,
+})
+```
+
+### l2GasLimit (optional)
+
+- **Type:** `bigint`
+
+Maximum amount of L2 gas that transaction can consume during execution on L2.
+
+```ts
+const hash = await walletClient.requestExecute({
+  client: zksyncClient,
+  token: '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55',
+  amount: 20n,
+  to: walletClient.account.address,
+  l2GasLimit: 900_000n, // [!code focus]
+  approveToken: true,
+  refundRecipient: walletClient.account.address,
+})
+```
+
+### gasPerPubdataByte (optional)
+
+- **Type:** `bigint`
+
+The L2 gas price for each published L1 calldata byte.
+
+```ts
+const hash = await walletClient.deposit({
+  client: zksyncClient,
+  token: '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55',
+  amount: 20n,
+  to: walletClient.account.address,
+  approveToken: true,
+  refundRecipient: walletClient.account.address,
+  gasPerPubdataByte: 250_000_000_000n // [!code focus]
+})
+```
+
+### refundRecipient (optional)
+
+- **Type:** `Address`
+- **Default:** `walletClient.account`
+
+The address on L2 that will receive the refund for the transaction.
+If the transaction fails, it will also be the address to receive `amount`.
+
+```ts
+const hash = await walletClient.deposit({
+  client: zksyncClient,
+  token: '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55',
+  amount: 20n,
+  to: walletClient.account.address,
+  approveToken: true,
+  refundRecipient: walletClient.account.address, // [!code focus]
+})
+```
+
+### bridgeAddress (optional)
+
+- **Type:** `Address`
+- **Default:** ZKsync L1 shared bridge
+
+The address of the bridge contract to be used.
+
+```ts
+const hash = await walletClient.deposit({
+  client: zksyncClient,
+  token: '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55',
+  amount: 20n,
+  to: walletClient.account.address,
+  approveToken: true,
+  refundRecipient: walletClient.account.address, 
+  bridgeAddress: '0xFC073319977e314F251EAE6ae6bE76B0B3BAeeCF' // [!code focus]
+})
+```
+
+### customBridgeData (optional)
+
+- **Type:** `Hex`
+
+Additional data that can be sent to a bridge.
+
+```ts
+const hash = await walletClient.deposit({
+  client: zksyncClient,
+  token: '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55',
+  amount: 20n,
+  to: walletClient.account.address,
+  approveToken: true,
+  refundRecipient: walletClient.account.address,
+  bridgeAddress: '0xFC073319977e314F251EAE6ae6bE76B0B3BAeeCF', 
+  customBridgeData: '0x...' // [!code focus],
+})
+```
+
+### approveToken (optional)
+
+- **Type:** `boolean | TransactionRequest`
+
+Whether token approval should be performed under the hood.
+Set this flag to true (or provide transaction overrides) if the bridge does
+not have sufficient allowance. The approval transaction is executed only if
+the bridge lacks sufficient allowance; otherwise, it is skipped.
+
+::: code-group
+
+```ts [boolean.ts]
+const hash = await walletClient.deposit({
+  client: zksyncClient,
+  token: '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55',
+  amount: 20n,
+  to: walletClient.account.address,
+  approveToken: true, // [!code focus],
+  refundRecipient: walletClient.account.address,
+  bridgeAddress: '0xFC073319977e314F251EAE6ae6bE76B0B3BAeeCF',
+})
+```
+
+```ts [overrides.ts]
+const hash = await walletClient.deposit({
+  client: zksyncClient,
+  token: '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55',
+  amount: 20n,
+  to: walletClient.account.address,
+  approveToken: { 
+    maxFeePerGas: 200_000_000_000n // [!code focus],  
+  },
+  refundRecipient: walletClient.account.address,
+  bridgeAddress: '0xFC073319977e314F251EAE6ae6bE76B0B3BAeeCF',
+})
+```
+
+:::
+
+### approveBaseToken (optional)
+
+- **Type:** `boolean | TransactionRequest`
+
+Whether base token approval should be performed under the hood.
+Set this flag to true (or provide transaction overrides) if the bridge does
+not have sufficient allowance. The approval transaction is executed only if
+the bridge lacks sufficient allowance; otherwise, it is skipped.
+
+::: code-group
+
+```ts [boolean.ts]
+const hash = await walletClient.deposit({
+  client: zksyncClient,
+  token: '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55',
+  amount: 20n,
+  to: walletClient.account.address,
+  approveBaseToken: true, // [!code focus],
+  refundRecipient: walletClient.account.address,
+  bridgeAddress: '0xFC073319977e314F251EAE6ae6bE76B0B3BAeeCF',
+})
+```
+
+```ts [overrides.ts]
+const hash = await walletClient.deposit({
+  client: zksyncClient,
+  token: '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55',
+  amount: 20n,
+  to: walletClient.account.address,
+  approveBaseToken: { 
+    maxFeePerGas: 200_000_000_000n // [!code focus],  
+  },
+  refundRecipient: walletClient.account.address,
+  bridgeAddress: '0xFC073319977e314F251EAE6ae6bE76B0B3BAeeCF',
+})
+```
+
+:::
+
+### chain (optional)
+
+- **Type:** [`Chain`](/docs/glossary/types#chain)
+- **Default:** `walletClient.chain`
+
+The target chain. If there is a mismatch between the wallet's current chain & the target chain, an error will be thrown.
+
+```ts
+import { zksync } from 'viem/chains' // [!code focus]
+
+const hash = await walletClient.deposit({
+  chain: zksync, // [!code focus]
+  client: zksyncClient,
+  token: '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55',
+  amount: 20n,
+  to: walletClient.account.address,
+  approveToken: true,
+  refundRecipient: walletClient.account.address,
+})
+```
\ No newline at end of file
diff --git a/site/sidebar.ts b/site/sidebar.ts
index 5fe1616ba7..1cfeea5653 100644
--- a/site/sidebar.ts
+++ b/site/sidebar.ts
@@ -1826,6 +1826,10 @@ export const sidebar = {
             text: 'finalizeWithdrawal',
             link: '/zksync/actions/finalizeWithdrawal',
           },
+          {
+            text: 'deposit',
+            link: '/zksync/actions/deposit',
+          },
         ],
       },
       {
diff --git a/src/zksync/actions/deposit.test.ts b/src/zksync/actions/deposit.test.ts
new file mode 100644
index 0000000000..47f17ee10f
--- /dev/null
+++ b/src/zksync/actions/deposit.test.ts
@@ -0,0 +1,95 @@
+import { expect, test } from 'vitest'
+import { anvilMainnet, anvilZksync } from '~test/src/anvil.js'
+import { accounts } from '~test/src/constants.js'
+import { mockRequestReturnData } from '~test/src/zksync.js'
+import { privateKeyToAccount } from '~viem/accounts/privateKeyToAccount.js'
+import { publicActions } from '~viem/clients/decorators/public.js'
+import type { EIP1193RequestFn } from '~viem/types/eip1193.js'
+import { legacyEthAddress } from '~viem/zksync/constants/address.js'
+import { publicActionsL2 } from '~viem/zksync/decorators/publicL2.js'
+import { deposit } from './deposit.js'
+
+const request = (async ({ method, params }) => {
+  if (method === 'eth_sendRawTransaction')
+    return '0x9afe47f3d95eccfc9210851ba5f877f76d372514a26b48bad848a07f77c33b87'
+  if (method === 'eth_sendTransaction')
+    return '0x9afe47f3d95eccfc9210851ba5f877f76d372514a26b48bad848a07f77c33b87'
+  if (method === 'eth_estimateGas') return 158774n
+  if (method === 'eth_gasPrice') return 150_000_000n
+  if (method === 'eth_maxPriorityFeePerGas') return 100_000_000n
+  if (method === 'eth_call')
+    return '0x00000000000000000000000070a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55'
+  if (method === 'eth_getTransactionCount') return 1n
+  if (method === 'eth_getBlockByNumber') return anvilMainnet.forkBlockNumber
+  if (method === 'eth_chainId') return anvilMainnet.chain.id
+  return anvilMainnet.getClient().request({ method, params } as any)
+}) as EIP1193RequestFn
+
+const baseClient = anvilMainnet.getClient({ batch: { multicall: false } })
+baseClient.request = request
+const client = baseClient.extend(publicActions)
+
+const baseClientWithAccount = anvilMainnet.getClient({
+  batch: { multicall: false },
+  account: true,
+})
+baseClientWithAccount.request = request
+const clientWithAccount = baseClientWithAccount.extend(publicActions)
+
+const baseClientL2 = anvilZksync.getClient()
+baseClientL2.request = (async ({ method, params }) => {
+  if (method === 'eth_call')
+    return '0x00000000000000000000000070a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55'
+  if (method === 'eth_estimateGas') return 158774n
+  return (
+    (await mockRequestReturnData(method)) ??
+    (await anvilZksync.getClient().request({ method, params } as any))
+  )
+}) as EIP1193RequestFn
+const clientL2 = baseClientL2.extend(publicActionsL2())
+
+test.skip('default', async () => {
+  const account = privateKeyToAccount(accounts[0].privateKey)
+  expect(
+    deposit(client, {
+      client: clientL2,
+      account,
+      token: legacyEthAddress,
+      to: account.address,
+      amount: 7_000_000_000n,
+      refundRecipient: account.address,
+    }),
+  ).toBeDefined()
+})
+
+test('default: account hoisting', async () => {
+  const account = privateKeyToAccount(accounts[0].privateKey)
+  expect(
+    deposit(clientWithAccount, {
+      client: clientL2,
+      token: legacyEthAddress,
+      to: account.address,
+      amount: 7_000_000_000n,
+      refundRecipient: account.address,
+    }),
+  ).toBeDefined()
+})
+
+test('errors: no account provided', async () => {
+  const account = privateKeyToAccount(accounts[0].privateKey)
+  await expect(() =>
+    deposit(client, {
+      client: clientL2,
+      token: legacyEthAddress,
+      to: account.address,
+      amount: 7_000_000_000n,
+      refundRecipient: account.address,
+    }),
+  ).rejects.toThrowErrorMatchingInlineSnapshot(`
+      [AccountNotFoundError: Could not find an Account to execute with this Action.
+      Please provide an Account with the \`account\` argument on the Action, or by supplying an \`account\` to the Client.
+
+      Docs: https://viem.sh/docs/actions/wallet/sendTransaction
+      Version: viem@x.y.z]
+  `)
+})
diff --git a/src/zksync/actions/deposit.ts b/src/zksync/actions/deposit.ts
new file mode 100644
index 0000000000..159eefcb4a
--- /dev/null
+++ b/src/zksync/actions/deposit.ts
@@ -0,0 +1,856 @@
+import { type Address, parseAbi, parseAbiParameters } from 'abitype'
+import { ZeroAddress } from 'ethers'
+import type { Hex } from 'viem'
+import type { Account } from '../../accounts/types.js'
+import {
+  type EstimateGasParameters,
+  estimateGas,
+} from '../../actions/public/estimateGas.js'
+import { readContract } from '../../actions/public/readContract.js'
+import { waitForTransactionReceipt } from '../../actions/public/waitForTransactionReceipt.js'
+import {
+  type SendTransactionErrorType,
+  type SendTransactionParameters,
+  type SendTransactionReturnType,
+  sendTransaction,
+} from '../../actions/wallet/sendTransaction.js'
+import {
+  type WriteContractParameters,
+  writeContract,
+} from '../../actions/wallet/writeContract.js'
+import type { Client } from '../../clients/createClient.js'
+import { publicActions } from '../../clients/decorators/public.js'
+import type { Transport } from '../../clients/transports/createTransport.js'
+import { erc20Abi } from '../../constants/abis.js'
+import { AccountNotFoundError } from '../../errors/account.js'
+import { ClientChainNotConfiguredError } from '../../errors/chain.js'
+import type { GetAccountParameter } from '../../types/account.js'
+import type {
+  Chain,
+  DeriveChain,
+  GetChainParameter,
+} from '../../types/chain.js'
+import type { UnionEvaluate, UnionOmit } from '../../types/utils.js'
+import {
+  type FormattedTransactionRequest,
+  encodeAbiParameters,
+  encodeFunctionData,
+  isAddressEqual,
+  parseAccount,
+} from '../../utils/index.js'
+import { bridgehubAbi } from '../constants/abis.js'
+import {
+  ethAddressInContracts,
+  legacyEthAddress,
+} from '../constants/address.js'
+import { requiredL1ToL2GasPerPubdataLimit } from '../constants/number.js'
+import {
+  BaseFeeHigherThanValueError,
+  type BaseFeeHigherThanValueErrorType,
+} from '../errors/bridge.js'
+import type { ChainEIP712 } from '../types/chain.js'
+import type { BridgeContractAddresses } from '../types/contract.js'
+import { applyL1ToL2Alias } from '../utils/bridge/applyL1ToL2Alias.js'
+import { estimateGasL1ToL2 } from './estimateGasL1ToL2.js'
+import { getBridgehubContractAddress } from './getBridgehubContractAddress.js'
+import { getDefaultBridgeAddresses } from './getDefaultBridgeAddresses.js'
+import { getL1Allowance } from './getL1Allowance.js'
+
+export type DepositParameters<
+  chain extends Chain | undefined = Chain | undefined,
+  account extends Account | undefined = Account | undefined,
+  chainOverride extends Chain | undefined = Chain | undefined,
+  chainL2 extends ChainEIP712 | undefined = ChainEIP712 | undefined,
+  accountL2 extends Account | undefined = Account | undefined,
+  _derivedChain extends Chain | undefined = DeriveChain<chain, chainOverride>,
+> = UnionEvaluate<
+  UnionOmit<FormattedTransactionRequest<_derivedChain>, 'data' | 'to' | 'from'>
+> &
+  Partial<GetChainParameter<chain, chainOverride>> &
+  Partial<GetAccountParameter<account>> & {
+    /** L2 client. */
+    client: Client<Transport, chainL2, accountL2>
+    /** The address of the token to deposit. */
+    token: Address
+    /** The amount of the token to deposit. */
+    amount: bigint
+    /** The address that will receive the deposited tokens on L2.
+    Defaults to the sender address.*/
+    to?: Address
+    /** (currently not used) The tip the operator will receive on top of
+    the base cost of the transaction. */
+    operatorTip?: bigint
+    /** Maximum amount of L2 gas that transaction can consume during execution on L2. */
+    l2GasLimit?: bigint
+    /** The L2 gas price for each published L1 calldata byte. */
+    gasPerPubdataByte?: bigint
+    /** The address on L2 that will receive the refund for the transaction.
+    If the transaction fails, it will also be the address to receive `amount`. */
+    refundRecipient?: Address
+    /** The address of the bridge contract to be used.
+    Defaults to the default ZKsync L1 shared bridge. */
+    bridgeAddress?: Address
+    /** Additional data that can be sent to a bridge. */
+    customBridgeData?: Hex
+    /** Whether token approval should be performed under the hood.
+    Set this flag to true (or provide transaction overrides) if the bridge does
+    not have sufficient allowance. The approval transaction is executed only if
+    the bridge lacks sufficient allowance; otherwise, it is skipped. */
+    approveToken?:
+      | boolean
+      | UnionEvaluate<
+          UnionOmit<
+            FormattedTransactionRequest<_derivedChain>,
+            'data' | 'to' | 'from'
+          >
+        >
+    /** Whether base token approval should be performed under the hood.
+    Set this flag to true (or provide transaction overrides) if the bridge does
+    not have sufficient allowance. The approval transaction is executed only if
+    the bridge lacks sufficient allowance; otherwise, it is skipped. */
+    approveBaseToken?:
+      | boolean
+      | UnionEvaluate<
+          UnionOmit<
+            FormattedTransactionRequest<_derivedChain>,
+            'data' | 'to' | 'from'
+          >
+        >
+  }
+
+export type DepositReturnType = SendTransactionReturnType
+
+export type DepositErrorType =
+  | SendTransactionErrorType
+  | BaseFeeHigherThanValueErrorType
+
+/**
+ * Transfers the specified token from the associated account on the L1 network to the target account on the L2 network.
+ * The token can be either ETH or any ERC20 token. For ERC20 tokens, enough approved tokens must be associated with
+ * the specified L1 bridge (default one or the one defined in `bridgeAddress`).
+ * In this case, depending on is the chain ETH-based or not `approveToken` or `approveBaseToken`
+ * can be enabled to perform token approval. If there are already enough approved tokens for the L1 bridge,
+ * token approval will be skipped.
+ *
+ * @param client - Client to use
+ * @param parameters - {@link DepositParameters}
+ * @returns hash - The [Transaction](https://viem.sh/docs/glossary/terms#transaction) hash. {@link DepositReturnType}
+ *
+ * @example
+ * import { createPublicClient, http } from 'viem'
+ * import { privateKeyToAccount } from 'viem/accounts'
+ * import { zksync, mainnet } from 'viem/chains'
+ * import { deposit, legacyEthAddress, publicActionsL2 } from 'viem/zksync'
+ *
+ * const client = createPublicClient({
+ *     chain: mainnet,
+ *     transport: http(),
+ * })
+ *
+ * const clientL2 = createPublicClient({
+ *   chain: zksync,
+ *   transport: http(),
+ * }).extend(publicActionsL2())
+ *
+ * const account = privateKeyToAccount('0x…')
+ *
+ * const hash = await deposit(client, {
+ *     client: clientL2,
+ *     account,
+ *     token: legacyEthAddress,
+ *     to: account.address,
+ *     amount: 1_000_000_000_000_000_000n,
+ *     refundRecipient: account.address,
+ * })
+ *
+ * @example Account Hoisting
+ * import { createPublicClient, createWalletClient, http } from 'viem'
+ * import { privateKeyToAccount } from 'viem/accounts'
+ * import { zksync, mainnet } from 'viem/chains'
+ * import { legacyEthAddress, publicActionsL2 } from 'viem/zksync'
+ *
+ * const walletClient = createWalletClient({
+ *   chain: mainnet,
+ *   transport: http(),
+ *   account: privateKeyToAccount('0x…'),
+ * })
+ *
+ * const clientL2 = createPublicClient({
+ *   chain: zksync,
+ *   transport: http(),
+ * }).extend(publicActionsL2())
+ *
+ * const hash = await deposit(walletClient, {
+ *     client: clientL2,
+ *     account,
+ *     token: legacyEthAddress,
+ *     to: walletClient.account.address,
+ *     amount: 1_000_000_000_000_000_000n,
+ *     refundRecipient: walletClient.account.address,
+ * })
+ */
+export async function deposit<
+  chain extends Chain | undefined,
+  account extends Account | undefined,
+  chainOverride extends Chain | undefined = Chain | undefined,
+  chainL2 extends ChainEIP712 | undefined = ChainEIP712 | undefined,
+  accountL2 extends Account | undefined = Account | undefined,
+  _derivedChain extends Chain | undefined = DeriveChain<chain, chainOverride>,
+>(
+  client: Client<Transport, chain, account>,
+  parameters: DepositParameters<
+    chain,
+    account,
+    chainOverride,
+    chainL2,
+    accountL2
+  >,
+): Promise<DepositReturnType> {
+  let {
+    account: account_ = client.account,
+    chain: chain_ = client.chain,
+    client: l2Client,
+    token,
+    amount,
+    approveToken,
+    approveBaseToken,
+    gas,
+  } = parameters
+
+  const account = account_ ? parseAccount(account_) : client.account
+  if (!account)
+    throw new AccountNotFoundError({
+      docsPath: '/docs/actions/wallet/sendTransaction',
+    })
+  if (!l2Client.chain) throw new ClientChainNotConfiguredError()
+
+  if (isAddressEqual(token, legacyEthAddress)) token = ethAddressInContracts
+
+  const bridgeAddresses = await getDefaultBridgeAddresses(l2Client)
+  const bridgehub = await getBridgehubContractAddress(l2Client)
+  const baseToken = await readContract(client, {
+    address: bridgehub,
+    abi: bridgehubAbi,
+    functionName: 'baseToken',
+    args: [BigInt(l2Client.chain.id)],
+  })
+
+  const { mintValue, tx } = await getL1DepositTx(
+    client,
+    account,
+    { ...parameters, token },
+    bridgeAddresses,
+    bridgehub,
+    baseToken,
+  )
+
+  await approveTokens(
+    client,
+    chain_,
+    tx.bridgeAddress,
+    baseToken,
+    mintValue,
+    account,
+    token,
+    amount,
+    approveToken,
+    approveBaseToken,
+  )
+
+  if (!gas) {
+    const baseGasLimit = await estimateGas(client, {
+      account: account.address,
+      to: bridgehub,
+      value: tx.value,
+      data: tx.data,
+    } as EstimateGasParameters)
+    gas = scaleGasLimit(baseGasLimit)
+  }
+
+  return await sendTransaction(client, {
+    chain: chain_,
+    account,
+    gas,
+    ...tx,
+  } as SendTransactionParameters)
+}
+
+async function getL1DepositTx<
+  chain extends Chain | undefined,
+  account extends Account | undefined,
+  chainOverride extends Chain | undefined = Chain | undefined,
+  chainL2 extends ChainEIP712 | undefined = ChainEIP712 | undefined,
+  accountL2 extends Account | undefined = Account | undefined,
+  _derivedChain extends Chain | undefined = DeriveChain<chain, chainOverride>,
+>(
+  client: Client<Transport, chain, account>,
+  account: Account,
+  parameters: DepositParameters<
+    chain,
+    account,
+    chainOverride,
+    chainL2,
+    accountL2,
+    _derivedChain
+  >,
+  bridgeAddresses: BridgeContractAddresses,
+  bridgehub: Address,
+  baseToken: Address,
+) {
+  let {
+    account: _account,
+    chain: _chain,
+    client: l2Client,
+    token,
+    amount,
+    to,
+    operatorTip = 0n,
+    l2GasLimit,
+    gasPerPubdataByte = requiredL1ToL2GasPerPubdataLimit,
+    refundRecipient = ZeroAddress as Address,
+    bridgeAddress,
+    customBridgeData,
+    value,
+    gasPrice,
+    maxFeePerGas,
+    maxPriorityFeePerGas,
+    approveToken: _approveToken,
+    approveBaseToken: _approveBaseToken,
+    ...rest
+  } = parameters
+
+  if (!l2Client.chain) throw new ClientChainNotConfiguredError()
+
+  to ??= account.address
+  let gasPriceForEstimation = maxFeePerGas || gasPrice
+  if (!gasPriceForEstimation) {
+    const estimatedFee = await getFeePrice(client)
+    gasPriceForEstimation = estimatedFee.maxFeePerGas
+    maxFeePerGas = estimatedFee.maxFeePerGas
+    maxPriorityFeePerGas ??= estimatedFee.maxPriorityFeePerGas
+  }
+
+  const { l2GasLimit_, baseCost } = await getL2BridgeTxFeeParams(
+    client,
+    l2Client,
+    bridgehub,
+    gasPriceForEstimation,
+    account.address,
+    token,
+    amount,
+    to,
+    gasPerPubdataByte,
+    baseToken,
+    l2GasLimit,
+    bridgeAddress,
+    customBridgeData,
+  )
+  l2GasLimit = l2GasLimit_
+  let mintValue: bigint
+  let data: Hex
+
+  const isETHBasedChain = isAddressEqual(baseToken, ethAddressInContracts)
+  if (
+    (isETHBasedChain && isAddressEqual(token, ethAddressInContracts)) || // ETH on ETH-based chain
+    isAddressEqual(token, baseToken) // base token on custom chain
+  ) {
+    // Deposit base token
+    mintValue = baseCost + operatorTip + amount
+    let providedValue = isETHBasedChain ? value : mintValue
+    if (!providedValue || providedValue === 0n) providedValue = mintValue
+    if (baseCost > providedValue)
+      throw new BaseFeeHigherThanValueError(baseCost, providedValue)
+
+    value = isETHBasedChain ? providedValue : 0n
+    bridgeAddress = bridgeAddresses.sharedL1 // required for approval of base token on custom chain
+
+    data = encodeFunctionData({
+      abi: bridgehubAbi,
+      functionName: 'requestL2TransactionDirect',
+      args: [
+        {
+          chainId: BigInt(l2Client.chain.id),
+          mintValue: providedValue,
+          l2Contract: to,
+          l2Value: amount,
+          l2Calldata: '0x',
+          l2GasLimit,
+          l2GasPerPubdataByteLimit: gasPerPubdataByte,
+          factoryDeps: [],
+          refundRecipient,
+        },
+      ],
+    })
+  } else if (isAddressEqual(baseToken, ethAddressInContracts)) {
+    // Deposit token on ETH-based chain
+    mintValue = baseCost + BigInt(operatorTip)
+    value = mintValue
+    if (baseCost > mintValue)
+      throw new BaseFeeHigherThanValueError(baseCost, mintValue)
+
+    bridgeAddress ??= bridgeAddresses.sharedL1
+
+    data = encodeFunctionData({
+      abi: bridgehubAbi,
+      functionName: 'requestL2TransactionTwoBridges',
+      args: [
+        {
+          chainId: BigInt(l2Client.chain.id),
+          mintValue,
+          l2Value: 0n,
+          l2GasLimit,
+          l2GasPerPubdataByteLimit: gasPerPubdataByte,
+          refundRecipient,
+          secondBridgeAddress: bridgeAddress,
+          secondBridgeValue: 0n,
+          secondBridgeCalldata: encodeAbiParameters(
+            parseAbiParameters('address x, uint256 y, address z'),
+            [token, amount, to],
+          ),
+        },
+      ],
+    })
+  } else if (isAddressEqual(token, ethAddressInContracts)) {
+    // Deposit ETH on custom chain
+    mintValue = baseCost + operatorTip
+    value = amount
+    if (baseCost > mintValue)
+      throw new BaseFeeHigherThanValueError(baseCost, mintValue)
+
+    bridgeAddress = bridgeAddresses.sharedL1
+
+    data = encodeFunctionData({
+      abi: bridgehubAbi,
+      functionName: 'requestL2TransactionTwoBridges',
+      args: [
+        {
+          chainId: BigInt(l2Client.chain.id),
+          mintValue,
+          l2Value: 0n,
+          l2GasLimit,
+          l2GasPerPubdataByteLimit: gasPerPubdataByte,
+          refundRecipient,
+          secondBridgeAddress: bridgeAddress,
+          secondBridgeValue: amount,
+          secondBridgeCalldata: encodeAbiParameters(
+            parseAbiParameters('address x, uint256 y, address z'),
+            [ethAddressInContracts, 0n, to],
+          ),
+        },
+      ],
+    })
+  } else {
+    // Deposit token on custom chain
+    mintValue = baseCost + operatorTip
+    value ??= 0n
+    if (baseCost > mintValue)
+      throw new BaseFeeHigherThanValueError(baseCost, mintValue)
+
+    bridgeAddress ??= bridgeAddresses.sharedL1
+
+    data = encodeFunctionData({
+      abi: bridgehubAbi,
+      functionName: 'requestL2TransactionTwoBridges',
+      args: [
+        {
+          chainId: BigInt(l2Client.chain.id),
+          mintValue,
+          l2Value: 0n,
+          l2GasLimit,
+          l2GasPerPubdataByteLimit: gasPerPubdataByte,
+          refundRecipient,
+          secondBridgeAddress: bridgeAddress,
+          secondBridgeValue: 0n,
+          secondBridgeCalldata: encodeAbiParameters(
+            parseAbiParameters('address x, uint256 y, address z'),
+            [token, amount, to],
+          ),
+        },
+      ],
+    })
+  }
+
+  return {
+    mintValue,
+    tx: {
+      bridgeAddress,
+      to: bridgehub,
+      data,
+      value,
+      gasPrice,
+      maxFeePerGas,
+      maxPriorityFeePerGas,
+      ...rest,
+    },
+  }
+}
+
+async function approveTokens<
+  chain extends Chain | undefined,
+  chainOverride extends Chain | undefined = Chain | undefined,
+  _derivedChain extends Chain | undefined = DeriveChain<chain, chainOverride>,
+>(
+  client: Client<Transport, chain>,
+  chain: Chain | null | undefined,
+  bridgeAddress: Address,
+  baseToken: Address,
+  mintValue: bigint,
+  account: Account,
+  token: Address,
+  amount: bigint,
+  approveToken?:
+    | boolean
+    | UnionEvaluate<
+        UnionOmit<
+          FormattedTransactionRequest<_derivedChain>,
+          'data' | 'to' | 'from'
+        >
+      >,
+  approveBaseToken?:
+    | boolean
+    | UnionEvaluate<
+        UnionOmit<
+          FormattedTransactionRequest<_derivedChain>,
+          'data' | 'to' | 'from'
+        >
+      >,
+) {
+  if (isAddressEqual(baseToken, ethAddressInContracts)) {
+    // Deposit token on ETH-based chain
+    if (approveToken) {
+      const overrides = typeof approveToken === 'boolean' ? {} : approveToken
+      const allowance = await getL1Allowance(client, {
+        token,
+        bridgeAddress,
+        account,
+      })
+      if (allowance < amount) {
+        const hash = await writeContract(client, {
+          chain,
+          account,
+          address: token,
+          abi: erc20Abi,
+          functionName: 'approve',
+          args: [bridgeAddress, amount],
+          ...overrides,
+        } satisfies WriteContractParameters as any)
+        await waitForTransactionReceipt(client, { hash })
+      }
+    }
+    return
+  }
+
+  if (isAddressEqual(token, ethAddressInContracts)) {
+    // Deposit ETH on custom chain
+    if (approveBaseToken) {
+      const overrides = typeof approveToken === 'boolean' ? {} : approveToken
+      const allowance = await getL1Allowance(client, {
+        token: baseToken,
+        bridgeAddress,
+        account,
+      })
+      if (allowance < mintValue) {
+        const hash = await writeContract(client, {
+          chain,
+          account,
+          address: baseToken,
+          abi: erc20Abi,
+          functionName: 'approve',
+          args: [bridgeAddress, mintValue],
+          ...overrides,
+        } satisfies WriteContractParameters as any)
+        await waitForTransactionReceipt(client, { hash })
+      }
+      return
+    }
+  }
+
+  if (isAddressEqual(token, baseToken)) {
+    // Deposit base token on custom chain
+    if (approveToken || approveBaseToken) {
+      const overrides =
+        typeof approveToken === 'boolean'
+          ? {}
+          : (approveToken ?? typeof approveBaseToken === 'boolean')
+            ? {}
+            : approveBaseToken
+      const allowance = await getL1Allowance(client, {
+        token: baseToken,
+        bridgeAddress,
+        account,
+      })
+      if (allowance < mintValue) {
+        const hash = await writeContract(client, {
+          chain,
+          account,
+          address: baseToken,
+          abi: erc20Abi,
+          functionName: 'approve',
+          args: [bridgeAddress, mintValue],
+          ...overrides,
+        } satisfies WriteContractParameters as any)
+        await waitForTransactionReceipt(client, { hash })
+      }
+    }
+    return
+  }
+
+  // Deposit token on custom chain
+  if (approveBaseToken) {
+    const overrides = typeof approveToken === 'boolean' ? {} : approveToken
+    const allowance = await getL1Allowance(client, {
+      token: baseToken,
+      bridgeAddress,
+      account,
+    })
+    if (allowance < mintValue) {
+      const hash = await writeContract(client, {
+        chain,
+        account,
+        address: baseToken,
+        abi: erc20Abi,
+        functionName: 'approve',
+        args: [bridgeAddress, mintValue],
+        ...overrides,
+      } satisfies WriteContractParameters as any)
+      await waitForTransactionReceipt(client, { hash })
+    }
+  }
+
+  if (approveToken) {
+    const overrides = typeof approveToken === 'boolean' ? {} : approveToken
+    const allowance = await getL1Allowance(client, {
+      token,
+      bridgeAddress,
+      account,
+    })
+    if (allowance < amount) {
+      const hash = await writeContract(client, {
+        chain,
+        account,
+        address: token,
+        abi: erc20Abi,
+        functionName: 'approve',
+        args: [bridgeAddress, amount],
+        ...overrides,
+      } satisfies WriteContractParameters as any)
+      await waitForTransactionReceipt(client, { hash })
+    }
+  }
+}
+
+async function getL2BridgeTxFeeParams<
+  chain extends Chain | undefined,
+  chainL2 extends ChainEIP712 | undefined,
+>(
+  client: Client<Transport, chain>,
+  l2Client: Client<Transport, chainL2>,
+  bridgehub: Address,
+  gasPrice: bigint,
+  from: Address,
+  token: Address,
+  amount: bigint,
+  to: Address,
+  gasPerPubdataByte: bigint,
+  baseToken: Address,
+  l2GasLimit?: bigint,
+  bridgeAddress?: Address,
+  customBridgeData?: Hex,
+) {
+  if (!l2Client.chain) throw new ClientChainNotConfiguredError()
+
+  let l2GasLimit_ = l2GasLimit
+  if (!l2GasLimit_)
+    l2GasLimit_ = bridgeAddress
+      ? await getL2GasLimitFromCustomBridge(
+          client,
+          l2Client,
+          from,
+          token,
+          amount,
+          to,
+          gasPerPubdataByte,
+          bridgeAddress,
+          customBridgeData,
+        )
+      : await getL2GasLimitFromDefaultBridge(
+          client,
+          l2Client,
+          from,
+          token,
+          amount,
+          to,
+          gasPerPubdataByte,
+          baseToken,
+        )
+
+  const baseCost = await readContract(client, {
+    address: bridgehub,
+    abi: bridgehubAbi,
+    functionName: 'l2TransactionBaseCost',
+    args: [BigInt(l2Client.chain.id), gasPrice, l2GasLimit_, gasPerPubdataByte],
+  })
+  return { l2GasLimit_, baseCost }
+}
+
+async function getL2GasLimitFromDefaultBridge<
+  chain extends Chain | undefined,
+  chainL2 extends ChainEIP712 | undefined,
+>(
+  client: Client<Transport, chain>,
+  l2Client: Client<Transport, chainL2>,
+  from: Address,
+  token: Address,
+  amount: bigint,
+  to: Address,
+  gasPerPubdataByte: bigint,
+  baseToken: Address,
+) {
+  if (isAddressEqual(token, baseToken)) {
+    return await estimateGasL1ToL2(l2Client, {
+      chain: l2Client.chain,
+      account: from,
+      from,
+      to,
+      value: amount,
+      data: '0x',
+      gasPerPubdata: gasPerPubdataByte,
+    })
+  }
+  const value = 0n
+  const bridgeAddresses = await getDefaultBridgeAddresses(l2Client)
+  const l1BridgeAddress = bridgeAddresses.sharedL1
+  const l2BridgeAddress = bridgeAddresses.sharedL2
+  const bridgeData = await encodeDefaultBridgeData(client, token)
+
+  const calldata = encodeFunctionData({
+    abi: parseAbi([
+      'function finalizeDeposit(address _l1Sender, address _l2Receiver, address _l1Token, uint256 _amount, bytes _data)',
+    ]),
+    functionName: 'finalizeDeposit',
+    args: [
+      from,
+      to,
+      isAddressEqual(token, legacyEthAddress) ? ethAddressInContracts : token,
+      amount,
+      bridgeData,
+    ],
+  })
+
+  return await estimateGasL1ToL2(l2Client, {
+    chain: l2Client.chain,
+    account: applyL1ToL2Alias(l1BridgeAddress),
+    to: l2BridgeAddress,
+    data: calldata,
+    gasPerPubdata: gasPerPubdataByte,
+    value,
+  })
+}
+
+async function getL2GasLimitFromCustomBridge<
+  chain extends Chain | undefined,
+  chainL2 extends ChainEIP712 | undefined,
+>(
+  client: Client<Transport, chain>,
+  l2Client: Client<Transport, chainL2>,
+  from: Address,
+  token: Address,
+  amount: bigint,
+  to: Address,
+  gasPerPubdataByte: bigint,
+  bridgeAddress: Address,
+  customBridgeData?: Hex,
+) {
+  let customBridgeData_ = customBridgeData
+  if (!customBridgeData_ || customBridgeData_ === '0x')
+    customBridgeData_ = await encodeDefaultBridgeData(client, token)
+
+  const l2BridgeAddress = await readContract(client, {
+    address: token,
+    abi: parseAbi([
+      'function l2BridgeAddress(uint256 _chainId) view returns (address)',
+    ]),
+    functionName: 'l2BridgeAddress',
+    args: [BigInt(l2Client.chain!.id)],
+  })
+
+  const calldata = encodeFunctionData({
+    abi: parseAbi([
+      'function finalizeDeposit(address _l1Sender, address _l2Receiver, address _l1Token, uint256 _amount, bytes _data)',
+    ]),
+    functionName: 'finalizeDeposit',
+    args: [from, to, token, amount, customBridgeData_],
+  })
+
+  return await estimateGasL1ToL2(l2Client, {
+    chain: l2Client.chain,
+    account: from,
+    from: applyL1ToL2Alias(bridgeAddress),
+    to: l2BridgeAddress,
+    data: calldata,
+    gasPerPubdata: gasPerPubdataByte,
+  })
+}
+
+async function encodeDefaultBridgeData<chain extends Chain | undefined>(
+  client: Client<Transport, chain>,
+  token: Address,
+) {
+  let token_ = token
+  if (isAddressEqual(token, legacyEthAddress)) token_ = ethAddressInContracts
+  let name = 'Ether'
+  let symbol = 'ETH'
+  let decimals = 18n
+  if (!isAddressEqual(token_, ethAddressInContracts)) {
+    name = await readContract(client, {
+      address: token_,
+      abi: erc20Abi,
+      functionName: 'name',
+      args: [],
+    })
+    symbol = await readContract(client, {
+      address: token_,
+      abi: erc20Abi,
+      functionName: 'symbol',
+      args: [],
+    })
+    decimals = BigInt(
+      await readContract(client, {
+        address: token_,
+        abi: erc20Abi,
+        functionName: 'decimals',
+        args: [],
+      }),
+    )
+  }
+
+  const nameBytes = encodeAbiParameters([{ type: 'string' }], [name])
+  const symbolBytes = encodeAbiParameters([{ type: 'string' }], [symbol])
+  const decimalsBytes = encodeAbiParameters([{ type: 'uint256' }], [decimals])
+
+  return encodeAbiParameters(
+    [{ type: 'bytes' }, { type: 'bytes' }, { type: 'bytes' }],
+    [nameBytes, symbolBytes, decimalsBytes],
+  )
+}
+
+function scaleGasLimit(gasLimit: bigint): bigint {
+  return (gasLimit * BigInt(12)) / BigInt(10)
+}
+
+async function getFeePrice<chain extends Chain | undefined>(
+  client: Client<Transport, chain>,
+) {
+  const client_ = client.extend(publicActions)
+  const block = await client_.getBlock()
+  const baseFee =
+    typeof block.baseFeePerGas !== 'bigint'
+      ? await client_.getGasPrice()
+      : block.baseFeePerGas
+  const maxPriorityFeePerGas = await client_.estimateMaxPriorityFeePerGas()
+
+  return {
+    maxFeePerGas: (baseFee * 3n) / 2n + maxPriorityFeePerGas,
+    maxPriorityFeePerGas: maxPriorityFeePerGas,
+  }
+}
diff --git a/src/zksync/constants/address.ts b/src/zksync/constants/address.ts
index 91b9cc45b4..5851a557ab 100644
--- a/src/zksync/constants/address.ts
+++ b/src/zksync/constants/address.ts
@@ -17,3 +17,11 @@ export const l2BaseTokenAddress =
 
 export const l1MessengerAddress =
   '0x0000000000000000000000000000000000008008' as const
+
+export const bootloaderFormalAddress =
+  '0x0000000000000000000000000000000000008001' as const
+
+export const l1ToL2AliasOffset =
+  '0x1111000000000000000000000000000000001111' as const
+
+export const addressModulo = 2n ** 160n
diff --git a/src/zksync/decorators/walletL1.test.ts b/src/zksync/decorators/walletL1.test.ts
index 5966c67f7a..f967aeac4d 100644
--- a/src/zksync/decorators/walletL1.test.ts
+++ b/src/zksync/decorators/walletL1.test.ts
@@ -1,8 +1,13 @@
 import { expect, test } from 'vitest'
 import { anvilMainnet, anvilZksync } from '~test/src/anvil.js'
-import { mockRequestReturnData } from '~test/src/zksync.js'
-import type { EIP1193RequestFn } from '~viem/types/eip1193.js'
-import { publicActionsL2, walletActionsL1 } from '~viem/zksync/index.js'
+import { accounts, mockRequestReturnData } from '~test/src/zksync.js'
+import { privateKeyToAccount } from '~viem/accounts/privateKeyToAccount.js'
+import type { EIP1193RequestFn } from '~viem/index.js'
+import {
+  legacyEthAddress,
+  publicActionsL2,
+  walletActionsL1,
+} from '~viem/zksync/index.js'
 
 const baseClient = anvilMainnet.getClient({
   batch: { multicall: false },
@@ -16,6 +21,7 @@ baseClient.request = (async ({ method, params }) => {
     return '0x00000000000000000000000070a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55'
   if (method === 'eth_getTransactionCount') return 1n
   if (method === 'eth_gasPrice') return 150_000_000n
+  if (method === 'eth_maxPriorityFeePerGas') return 100_000_000n
   if (method === 'eth_getBlockByNumber') return anvilMainnet.forkBlockNumber
   if (method === 'eth_chainId') return anvilMainnet.chain.id
   return anvilMainnet.getClient().request({ method, params } as any)
@@ -56,3 +62,16 @@ test('finalizeWithdrawal', async () => {
     }),
   ).toBeDefined()
 })
+
+test('deposit', async () => {
+  const account = privateKeyToAccount(accounts[0].privateKey)
+  expect(
+    await client.deposit({
+      client: zksyncClient,
+      token: legacyEthAddress,
+      to: account.address,
+      refundRecipient: account.address,
+      amount: 7_000_000_000n,
+    }),
+  ).toBeDefined()
+})
diff --git a/src/zksync/decorators/walletL1.ts b/src/zksync/decorators/walletL1.ts
index f02674b21d..3125685c42 100644
--- a/src/zksync/decorators/walletL1.ts
+++ b/src/zksync/decorators/walletL1.ts
@@ -2,6 +2,11 @@ import type { Client } from '../../clients/createClient.js'
 import type { Transport } from '../../clients/transports/createTransport.js'
 import type { Account } from '../../types/account.js'
 import type { Chain } from '../../types/chain.js'
+import {
+  type DepositParameters,
+  type DepositReturnType,
+  deposit,
+} from '../actions/deposit.js'
 import {
   type FinalizeWithdrawalParameters,
   type FinalizeWithdrawalReturnType,
@@ -18,6 +23,56 @@ export type WalletActionsL1<
   chain extends Chain | undefined = Chain | undefined,
   account extends Account | undefined = Account | undefined,
 > = {
+  /**
+   * Transfers the specified token from the associated account on the L1 network to the target account on the L2 network.
+   * The token can be either ETH or any ERC20 token. For ERC20 tokens, enough approved tokens must be associated with
+   * the specified L1 bridge (default one or the one defined in `bridgeAddress`).
+   * In this case, depending on is the chain ETH-based or not `approveToken` or `approveBaseToken`
+   * can be enabled to perform token approval. If there are already enough approved tokens for the L1 bridge,
+   * token approval will be skipped.
+   *
+   * @param parameters - {@link DepositParameters}
+   * @returns hash - The [Transaction](https://viem.sh/docs/glossary/terms#transaction) hash. {@link DepositReturnType}
+   *
+   * @example
+   * import { createPublicClient, createWalletClient, http } from 'viem'
+   * import { privateKeyToAccount } from 'viem/accounts'
+   * import { zksync, mainnet } from 'viem/chains'
+   * import { walletActionsL1, legacyEthAddress, publicActionsL2 } from 'viem/zksync'
+   *
+   * const walletClient = createWalletClient({
+   *   chain: mainnet,
+   *   transport: http(),
+   *   account: privateKeyToAccount('0x…'),
+   * }).extend(walletActionsL1())
+   *
+   * const clientL2 = createPublicClient({
+   *   chain: zksync,
+   *   transport: http(),
+   * }).extend(publicActionsL2())
+   *
+   * const hash = await walletClient.deposit({
+   *     client: clientL2,
+   *     account,
+   *     token: legacyEthAddress,
+   *     to: walletClient.account.address,
+   *     amount: 1_000_000_000_000_000_000n,
+   *     refundRecipient: walletClient.account.address,
+   * })
+   */
+  deposit: <
+    chainOverride extends Chain | undefined = undefined,
+    chainL2 extends ChainEIP712 | undefined = ChainEIP712 | undefined,
+    accountL2 extends Account | undefined = Account | undefined,
+  >(
+    parameters: DepositParameters<
+      chain,
+      account,
+      chainOverride,
+      chainL2,
+      accountL2
+    >,
+  ) => Promise<DepositReturnType>
   /**
    * Initiates the withdrawal process which withdraws ETH or any ERC20 token
    * from the associated account on L2 network to the target account on L1 network.
@@ -113,6 +168,7 @@ export function walletActionsL1() {
   >(
     client: Client<transport, chain, account>,
   ): WalletActionsL1<chain, account> => ({
+    deposit: (args) => deposit(client, args),
     finalizeWithdrawal: (args) => finalizeWithdrawal(client, args),
     requestExecute: (args) => requestExecute(client, args),
   })
diff --git a/src/zksync/index.ts b/src/zksync/index.ts
index f5de26a3e7..b5fe026787 100644
--- a/src/zksync/index.ts
+++ b/src/zksync/index.ts
@@ -28,6 +28,12 @@ export {
   type HashBytecodeErrorType,
   hashBytecode,
 } from './utils/hashBytecode.js'
+export {
+  type DepositErrorType,
+  type DepositReturnType,
+  type DepositParameters,
+  deposit,
+} from './actions/deposit.js'
 export {
   type EstimateFeeParameters,
   type EstimateFeeReturnType,
diff --git a/src/zksync/utils/bridge/applyL1ToL2Alias.test.ts b/src/zksync/utils/bridge/applyL1ToL2Alias.test.ts
new file mode 100644
index 0000000000..81a6c1a26e
--- /dev/null
+++ b/src/zksync/utils/bridge/applyL1ToL2Alias.test.ts
@@ -0,0 +1,18 @@
+import { expect, test } from 'vitest'
+import { applyL1ToL2Alias } from './applyL1ToL2Alias.js'
+
+test('default', async () => {
+  const l1ContractAddress = '0x702942B8205E5dEdCD3374E5f4419843adA76Eeb'
+  const l2ContractAddress = applyL1ToL2Alias(l1ContractAddress)
+  expect(l2ContractAddress.toLowerCase()).equals(
+    '0x813A42B8205E5DedCd3374e5f4419843ADa77FFC'.toLowerCase(),
+  )
+})
+
+test('pad zero to left', async () => {
+  const l1ContractAddress = '0xeeeeffffffffffffffffffffffffffffffffeeef'
+  const l2ContractAddress = applyL1ToL2Alias(l1ContractAddress)
+  expect(l2ContractAddress.toLowerCase()).equals(
+    '0x0000000000000000000000000000000000000000'.toLowerCase(),
+  )
+})
diff --git a/src/zksync/utils/bridge/applyL1ToL2Alias.ts b/src/zksync/utils/bridge/applyL1ToL2Alias.ts
new file mode 100644
index 0000000000..d760c78b54
--- /dev/null
+++ b/src/zksync/utils/bridge/applyL1ToL2Alias.ts
@@ -0,0 +1,29 @@
+import type { Address } from '../../../accounts/index.js'
+import { pad, toHex } from '../../../utils/index.js'
+import { addressModulo, l1ToL2AliasOffset } from '../../constants/address.js'
+
+/**
+ * Converts the address that submitted a transaction to the inbox on L1 to the `msg.sender` viewed on L2.
+ * Returns the `msg.sender` of the `L1->L2` transaction as the address of the contract that initiated the transaction.
+ *
+ * All available cases:
+ * - During a normal transaction, if contract `A` calls contract `B`, the `msg.sender` is `A`.
+ * - During `L1->L2` communication, if an EOA `X` calls contract `B`, the `msg.sender` is `X`.
+ * - During `L1->L2` communication, if a contract `A` calls contract `B`, the `msg.sender` is `applyL1ToL2Alias(A)`.
+ *
+ * @param address - The address of the contract.
+ * @returns address - The transformed address representing the `msg.sender` on L2.
+ *
+ * @example
+ * import { applyL1ToL2Alias } from 'viem/zksync'
+ *
+ * const l1ContractAddress = "0x702942B8205E5dEdCD3374E5f4419843adA76Eeb";
+ * const l2ContractAddress = utils.applyL1ToL2Alias(l1ContractAddress);
+ * // l2ContractAddress = "0x813A42B8205E5DedCd3374e5f4419843ADa77FFC"
+ */
+export function applyL1ToL2Alias(address: Address): Address {
+  return pad(
+    toHex((BigInt(address) + BigInt(l1ToL2AliasOffset)) % addressModulo),
+    { size: 20 },
+  )
+}
diff --git a/src/zksync/utils/bridge/undoL1ToL2Alias.test.ts b/src/zksync/utils/bridge/undoL1ToL2Alias.test.ts
new file mode 100644
index 0000000000..2064e3f23f
--- /dev/null
+++ b/src/zksync/utils/bridge/undoL1ToL2Alias.test.ts
@@ -0,0 +1,27 @@
+import { expect, test } from 'vitest'
+import { undoL1ToL2Alias } from './undoL1ToL2Alias.js'
+
+test('default', async () => {
+  const l2ContractAddress = '0x813A42B8205E5DedCd3374e5f4419843ADa77FFC'
+  const l1ContractAddress = undoL1ToL2Alias(l2ContractAddress)
+  expect(l1ContractAddress.toLowerCase()).equals(
+    '0x702942B8205E5dEdCD3374E5f4419843adA76Eeb'.toLowerCase(),
+  )
+})
+
+test('pad zero to left', async () => {
+  const l2ContractAddress = '0x1111000000000000000000000000000000001111'
+  const l1ContractAddress = undoL1ToL2Alias(l2ContractAddress)
+  expect(l1ContractAddress.toLowerCase()).equals(
+    '0x0000000000000000000000000000000000000000'.toLowerCase(),
+  )
+})
+
+test('offset is greater than the address', async () => {
+  const l2ContractAddress = '0x100'
+  const l1ContractAddress = undoL1ToL2Alias(l2ContractAddress)
+
+  expect(l1ContractAddress.toLowerCase()).equals(
+    '0xeeeeffffffffffffffffffffffffffffffffefef'.toLowerCase(),
+  )
+})
diff --git a/src/zksync/utils/bridge/undoL1ToL2Alias.ts b/src/zksync/utils/bridge/undoL1ToL2Alias.ts
new file mode 100644
index 0000000000..6b9ff03cd3
--- /dev/null
+++ b/src/zksync/utils/bridge/undoL1ToL2Alias.ts
@@ -0,0 +1,22 @@
+import type { Address } from '../../../accounts/index.js'
+import { pad, toHex } from '../../../utils/index.js'
+import { addressModulo, l1ToL2AliasOffset } from '../../constants/address.js'
+
+/**
+ * Converts and returns the `msg.sender` viewed on L2 to the address that submitted a transaction to the inbox on L1.
+ *
+ * @param address - The sender address viewed on L2.
+ * @returns address - The hash of the L2 priority operation.
+ *
+ * @example
+ * import { undoL1ToL2Alias } from 'viem/zksync'
+ *
+ * const l2ContractAddress = "0x813A42B8205E5DedCd3374e5f4419843ADa77FFC";
+ * const l1ContractAddress = utils.undoL1ToL2Alias(l2ContractAddress);
+ * // const l1ContractAddress = "0x702942B8205E5dEdCD3374E5f4419843adA76Eeb"
+ */
+export function undoL1ToL2Alias(address: Address): Address {
+  let result = BigInt(address) - BigInt(l1ToL2AliasOffset)
+  if (result < 0n) result += addressModulo
+  return pad(toHex(result), { size: 20 })
+}