Skip to main content

Bridging CELO from Holesky to Alfajores using Viem

In this guide, you'll learn how to programmatically bridge CELO from Holesky to Alfajores using the viem OP Stack.

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 function on the OptimismPortalProxy contract on Holesky. This function moves CELO tokens from your account to the Alfajores testnet.

Code Example

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

import { createWalletClient, createPublicClient, http, parseEther } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { holesky } from "viem/chains";
import { getL2TransactionHashes, publicActionsL2 } from "viem/op-stack";
import { alfajores } from "./alfajores.js";

const CELOL1 = "0xDEd08f6Ec0A57cE6Be62d1876d2CE92AF37eddA0";

// https://docs.celo.org/cel2/contract-addresses
const OptimismPortalProxy = "0xB29597c6866c6C2870348f1035335B75eEf79d07";

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

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

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

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

async function main() {
// Approve OptimismPortal to pull CELO on Holesky
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();