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.
- index.js
- alfajores.js
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();
import { defineChain } from "viem";
const sourceId = 17000;
export const alfajores = defineChain({
name: "Alfajores Testnet",
id: 44_787,
nativeCurrency: {
decimals: 18,
name: "CELO",
symbol: "CELO",
},
rpcUrls: {
default: {
http: ["https://alfajores-forno.celo-testnet.org"],
},
},
contracts: {
gasPriceOracle: { address: "0x420000000000000000000000000000000000000F" },
l1Block: { address: "0x4200000000000000000000000000000000000015" },
l2CrossDomainMessenger: {
address: "0x4200000000000000000000000000000000000007",
},
l2Erc721Bridge: { address: "0x4200000000000000000000000000000000000014" },
l2StandardBridge: { address: "0x4200000000000000000000000000000000000010" },
l2ToL1MessagePasser: {
address: "0x4200000000000000000000000000000000000016",
},
disputeGameFactory: {
[sourceId]: {
address: "0x831f39053688f05698ad0fB5f4DE7e56B2949c55",
},
},
l2OutputOracle: {
[sourceId]: {
address: "0x419577592C884868C3ed85B97169b93362581855",
},
},
portal: {
[sourceId]: {
address: "0xB29597c6866c6C2870348f1035335B75eEf79d07",
},
},
l1StandardBridge: {
[sourceId]: {
address: "0x9FEBd0F16b97e0AEF9151AF07106d733E87B1be4",
},
},
},
blockExplorers: {
default: {
name: "Celo Explorer",
url: "https://explorer.celo.org/alfajores",
apiUrl: "https://explorer.celo.org/api",
},
},
testnet: true,
});