Skip to content

Depositing Funds to Cloak

Introduction

Most Cloak instances are deployed as L3 chains on top of Scroll. That means that there is a canonical smart contract token bridge between Scroll and Cloak.

The general deposit process for Cloak chain operators is:

  1. Bridge assets to Scroll via the canonical bridge (from Ethereum) or via 3rd-party bridges.
  2. Deposit to Cloak via the Cloak bridge.

The remainder of this document will focus on Step 2.

Integration options

For bridging your users' assets to Cloak, you can implement the above process in your frontend. Alternatively, you can also implement your own liquidity bridge by bridging a certain amount of tokens to Cloak, and distributing them to users based on your application logic.

Overview of the Cloak Bridge

The Cloak bridge implements a general message-passing interface between L2 (Scroll) and L3 (Cloak). Most of the bridge code is inherited from the Scroll bridge.

  • The user calls one of the token gateway contracts to initiate the deposit: HostERC20Gateway.depositERC20 or HostETHGateway.depositETH.
  • The token gateway relays the message to HostMessenger, which enqueues a message on HostMessageQueue.
  • The L3 sequencer node picks up the message and relays it to Cloak via ValidiumERC20Gateway.

Most of the above contracts are implementation details; users generally mainly interact with the gateway contracts.

A Note on Stealth Deposits

Unlike the Scroll bridge, the Cloak bridge uses stealth deposits, meaning that the deposit target address is encrypted. This is necessary, so that the L3 confidential identity is not exposed publicly on L2. We also want to avoid easily linking L2 and L3 accounts.

Prerequisites

We recommend using the @scroll-tech/cloak-js package. This package provides the Cloak network configurations, and tools for encrypting and tracking deposits. It supports viem and ethers-js.

To use this package in your JavaScript or TypeScript project, simply install and then import it:

import { abis, cloak } from '@scroll-tech/cloak-js';

const c = cloak('local-devnet');
console.log(c.contracts());

Depositing ETH to Cloak

See the full example at deposit-eth.ts example in the @scroll-tech/cloak-js package.

The deposit process is as follows.

  1. Fetch the current encryption key.

    1
    2
    3
    4
    5
    const [keyId, encryptionKey] = await l2Client.readContract({
      address: c.contracts().HostValidium,
      abi: abis.HostValidium,
      functionName: 'getLatestEncryptionKey',
    });
    
    1
    2
    3
    4
    5
    6
    7
    const validium = new Contract(
      c.contracts().HostValidium,
      abis.HostValidium,
      l2Provider,
    );
    
    const [keyId, encryptionKey] = await validium.getLatestEncryptionKey();
    
  2. Encrypt the target address using the encryption key.

    const recipient = c.encryptAddress(l3Address/* (1)! */, encryptionKey);
    
    1. It is recommended to generate a unique L3 address for the user. One that is not used on another networks.
  3. Send the deposit to the L2 bridge.

    1
    2
    3
    4
    5
    6
    7
    await l2Wallet.writeContract({
      address: c.contracts().HostWethGateway,
      value: amount,
      abi: abis.HostWethGateway,
      functionName: 'deposit',
      args: [recipient, amount, keyId],
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const hostWethGateway = new Contract(
      c.contracts().HostWethGateway,
      abis.HostWethGateway,
      l2Wallet,
    );
    
    const tx = await hostWethGateway.deposit(recipient, amount, keyId, {
      value: amount,
    });
    
  4. Set up deposit tracking.

    const deposit = c.trackDeposit(l2Receipt, l3Account.address);
    
  5. Wait for the deposit to be confirmed on L3.

    const hash = deposit.possibleL3TxHashes.decrypted;
    const l3Receipt = await l3Client.waitForTransactionReceipt({ hash });
    
    const hash = deposit.possibleL3TxHashes.decrypted;
    const l3Receipt = await l3Provider.waitForTransaction(hash);
    

Depositing ERC20 Tokens to Cloak

See the full example at deposit-erc20.ts example in the @scroll-tech/cloak-js package.

ERC20 deposits are similar to ETH deposits, but they happen through the ERC20Gateway and require a token approval transaction on L2.

  1. Fetch the current encryption key.

    1
    2
    3
    4
    5
    const [keyId, encryptionKey] = await l2Client.readContract({
      address: c.contracts().HostValidium,
      abi: abis.HostValidium,
      functionName: 'getLatestEncryptionKey',
    });
    
    1
    2
    3
    4
    5
    6
    7
    const validium = new Contract(
      c.contracts().HostValidium,
      abis.HostValidium,
      l2Provider,
    );
    
    const [keyId, encryptionKey] = await validium.getLatestEncryptionKey();
    
  2. Encrypt the target address using the encryption key.

    const recipient = c.encryptAddress(l3Address, encryptionKey);
    
  3. Approve the Cloak bridge to spend your ERC20 token.

    1
    2
    3
    4
    5
    6
    await l2Wallet.writeContract({
      address: l2Token,
      abi: abis.ERC20,
      functionName: 'approve',
      args: [c.contracts().HostERC20Gateway, amount],
    });
    
    1
    2
    3
    4
    5
    6
    const erc20 = new Contract(l2Token, abis.ERC20, l2Wallet);
    
    const approveTx = await erc20.approve(
      c.contracts().HostERC20Gateway,
      amount,
    );
    
  4. Send the deposit to the L2 bridge.

    1
    2
    3
    4
    5
    6
    await l2Wallet.writeContract({
      address: c.contracts().HostERC20Gateway,
      abi: abis.HostERC20Gateway,
      functionName: 'depositERC20',
      args: [l2Token, recipient, amount, gasLimit, keyId],
    });
    
    const hostERC20Gateway = new Contract(
      c.contracts().HostERC20Gateway,
      abis.HostERC20Gateway,
      l2Wallet,
    );
    
    await hostERC20Gateway.depositERC20(
      l2Token,
      recipient,
      amount,
      gasLimit,
      keyId,
    );
    
  5. Set up deposit tracking.

    const deposit = c.trackDeposit(l2Receipt, l3Account.address);
    
  6. Wait for the deposit to be confirmed on L3.

    const hash = deposit.possibleL3TxHashes.decrypted;
    const l3Receipt = await l3Client.waitForTransactionReceipt({ hash });
    
    const hash = deposit.possibleL3TxHashes.decrypted;
    const l3Receipt = await l3Provider.waitForTransaction(hash);
    

ERC20 Token Mapping

The Scroll-Cloak bridge can permissionlessly bridge any valid ERC20 token. The bridge will deterministically compute the corresponding L3 token address, which will not change after the first deposit. L2 ETH is bridged into L3 Wrapped ETH.

ETH deposits

By default, L2 ETH is deposited into L3 Wrapped ETH (ERC20 token), not into the L3 gas token. L3 gas is free, so users do not need to hold any gas token.

The L2-L3 token mapping can be queried via the HostERC20Gateway contract on L2:

1
2
3
4
5
6
const l3TokenAddress = await l2Client.readContract({
  address: c.contracts().HostERC20Gateway,
  abi: abis.HostERC20Gateway,
  functionName: 'getL2ERC20Address',
  args: [c.contracts().HostWeth],
});
1
2
3
4
5
6
7
const hostERC20Gateway = new Contract(
  c.contracts().HostERC20Gateway,
  abis.HostERC20Gateway,
  l2Wallet,
);

const l3Token = await hostERC20Gateway.getL2ERC20Address(l2Token);