> ## Documentation Index
> Fetch the complete documentation index at: https://docs.celo.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Bridging CELO from Ethereum

> Programmatically bridge CELO from Ethereum to Celo through the native OptimismPortal bridge using the viem OP Stack

This guide is for developers who want to bridge CELO from Ethereum to Celo programmatically with the [viem OP Stack](https://viem.sh/op-stack). CELO is an ERC-20 token on Ethereum, so unlike a stock OP Stack chain the deposit goes through `depositERC20Transaction` rather than a plain value deposit. If you just want to bridge with a UI, use [Superbridge](/home/bridged-tokens/bridges).

The example below runs against the testnets, bridging CELO from Ethereum Sepolia to Celo Sepolia.

## Steps to bridge CELO

Before transferring tokens, you must authorize the `OptimismPortalProxy` contract to spend CELO on your behalf. Without this approval, the bridging transaction cannot proceed.

Call the [`depositERC20Transaction`](https://viem.sh/op-stack/actions/depositTransaction#deposittransaction) function on the `OptimismPortalProxy` contract on Ethereum Sepolia. This function moves CELO tokens from your account to Celo Sepolia.

## Code example

The following example demonstrates how to configure a file with all the details you need for interacting with Celo Sepolia.

<CodeGroup>
  ```js theme={null}
  import { createWalletClient, createPublicClient, http, parseEther } from "viem";
  import { privateKeyToAccount } from "viem/accounts";
  import { celoSepolia, sepolia } from "viem/chains";
  import { getL2TransactionHashes, publicActionsL2 } from "viem/op-stack";

  // Ethereum Sepolia (11155111), CELO token (CeloTokenProxy), 18 decimals
  const CELOL1 = "0x3c7011fd5e6aed460caa4985cf8d8caba435b092";

  // Ethereum Sepolia (11155111), portal for Celo Sepolia
  // https://docs.celo.org/tooling/contracts/l1-contracts
  const OptimismPortalProxy = "0x44ae3d41a335a7d05eb533029917aad35662dcc2";

  const account = privateKeyToAccount(
    "...",
  );

  export const walletClientL1 = createWalletClient({
    account,
    chain: sepolia,
    transport: http(),
  });

  export const publicClientL1 = createPublicClient({
    account,
    chain: sepolia,
    transport: http(),
  });

  export const publicClientL2 = createPublicClient({
    chain: celoSepolia,
    transport: http(),
  }).extend(publicActionsL2());

  async function main() {
    // Approve OptimismPortal to pull CELO on Sepolia
    const approve = await walletClientL1.writeContract({
      address: CELOL1,
      abi: [
        {
          inputs: [
            { name: "spender", type: "address" },
            { name: "amount", type: "uint256" },
          ],
          name: "approve",
          type: "function",
        },
      ],
      functionName: "approve",
      args: [OptimismPortalProxy, parseEther("0.0001")],
    });

    console.log(`Approval TX Hash: ${approve}`);

    let approveReceipt = await publicClientL1.waitForTransactionReceipt({
      hash: approve,
    });
    console.log(`Approve Transaction Receipt: ${approveReceipt}`);

    // Call depositERC20Transaction on OptimismPortal
    const deposit = await walletClientL1.writeContract({
      address: OptimismPortalProxy,
      abi: [
          {
            inputs: [
              {
                name: "_to",
                type: "address",
              },
              {
                name: "mint",
                type: "uint256",
              },
              {
                name: "_value",
                type: "uint256",
              },
              {
                name: "_gasLimit",
                type: "uint64",
              },
              {
                name: "_isCreation",
                type: "bool",
              },
              {
                name: "_data",
                type: "bytes",
              },
            ],
            name: "depositERC20Transaction",
            type: "function",
          },
        ],
        functionName: "depositERC20Transaction",
        args: [
          account.address, // Account where you want to receive CELO on L2
          parseEther("0.0001"), // Amount you are transferring to the Portal
          parseEther("0.0001"), // Amount you want on L2
          100_000, // Amount of L2 gas to purchase by burning gas on L1.
          false, // Whether the transaction is a contract creation
          "", // Data to trigger the recipient with
        ],
    });

    console.log(`Deposit Transaction: ${deposit}`);


    let depositReceipt = await publicClientL1.waitForTransactionReceipt({
      hash: deposit,
    });
    console.log(`Deposit Transaction Receipt: ${depositReceipt}`);

    // Get the L2 transaction hash from the L1 transaction receipt.
    const [l2Hash] = getL2TransactionHashes(depositReceipt);

    // Wait for the L2 transaction to be processed.
    const l2Receipt = await publicClientL2.waitForTransactionReceipt({
      hash: l2Hash,
    });

    console.log(`L2Receipt: ${l2Receipt}`);
  }

  main();
  ```
</CodeGroup>

## Related

* [Withdrawing CELO to Ethereum](/home/bridged-tokens/withdrawing-celo-to-ethereum) - the reverse direction, through the three-step withdrawal flow
* [Bridges](/home/bridged-tokens/bridges) - bridge UIs, including Superbridge for mainnet
* [L1 contracts](/tooling/contracts/l1-contracts) - the portal and token addresses for mainnet and Celo Sepolia
