Skip to main content

Bridging Celo from Holesky to Dango using Viem

In this tutorial, you will learn how to programmatically bridge CELO from Holskey to Dango using the viem OP Stack.

Bridging CELO from Holesky to Dango is simple, all you need to do is call depositERC20Transaction on the OptimismPortalProxy contract on Holesky.

The depositERC20Transaction pulls CELO tokens from the account which is why you need to approve OptimismPortalProxy to spend CELO tokens first.

As you can see, we’ve created a dango.js file that contains all the necessary data for interacting with the test network. Since Dango will be a short-lived testnet, chain data won’t be available through viem or wagmi, so this file ensures you have everything you need directly at hand.

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 { dango } from "./dango.js";

const CELOL1 = "0xDEd08f6Ec0A57cE6Be62d1876d2CE92AF37eddA0";

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: dango,
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();