Skip to main content

Withdrawing CELO from Alfajores to Holesky using Viem

In this tutorial, you will learn how to programmatically withdraw CELO from Alfajores to Holesky using the viem OP Stack.

Steps to Withdraw CELO

Withdrawals require the user to submit three transactions:

  1. Withdrawal initiating a transaction, which the user submits on L2.
  2. Withdrawal proving transaction, which the user submits on L1 to prove that the withdrawal is legitimate.
  3. Withdrawal finalizing transaction, which the user submits on L1 after the fault challenge period has passed, to actually run the transaction on L1.

Code Example

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

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

const account = privateKeyToAccount(
"[PRIVATE_KEY]",
);

const value = parseEther("0.0001"); // Amount to Withdraw

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

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

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

export const walletClientL2 = createWalletClient({
chain: alfajores,
transport: http(),
account,
}).extend(walletActionsL2());

export default async function main() {
console.log("Building Initiate Withdrawal...");
const args = await publicClientL1.buildInitiateWithdrawal({
account,
to: account.address, // Receive on the same address on L1.
value,
});

console.log("Initiaiting Withdrawal...");
const hash = await walletClientL2.initiateWithdrawal(args);

const initiateWithdrawalReceipt = await publicClientL2.waitForTransactionReceipt({
hash
});
console.log(`Withdrawal Initiated: ${initiateWithdrawalReceipt}`);

/**
* The below step can take upto 2 hours!
*
* Hence, you may want to use viem's `getTimeToProve`.
*
* https://viem.sh/op-stack/actions/getTimeToProve
*
* Store the wait time in a database
* and let the user know to come back later.
*
* */
console.log("Waiting to prove...");
const { output, withdrawal } = await publicClientL1.waitToProve({
receipt: initiateWithdrawalReceipt,
targetChain: walletClientL2.chain,
});

console.log("Building Prove Withdrawal...");
const proveArgs = await publicClientL2.buildProveWithdrawal({
output,
withdrawal,
});

console.log("Proving Withdrawal...");
const proveHash = await walletClientL1.proveWithdrawal(proveArgs);

const proveReceipt = await publicClientL1.waitForTransactionReceipt({
hash: proveHash,
});
console.log(`Withdrawal Proved: ${proveReceipt}`);

/**
* The below step can take a few minutes, ideally 2 minutes.
*
* Hence, you may want to use viem's `getTimeToFinalize`.
*
* https://viem.sh/op-stack/actions/getTimeToFinalize
*
* Store the wait time in a database
* and let the user know to come back later.
*
*
*/
console.log("Waiting To Finalize...");
await publicClientL1.waitToFinalize({
targetChain: walletClientL2.chain,
withdrawalHash: withdrawal.withdrawalHash,
});

console.log("Finalizing Withdrawal...");
const finalizeWithdrawalHash = await walletClientL1.finalizeWithdrawal({
targetChain: walletClientL2.chain,
withdrawal,
});

const finalizeWithdrawalReceipt = await publicClientL1.waitForTransactionReceipt({
hash: finalizeWithdrawalHash,
});
console.log(`Withdrawal Finalized: ${finalizeWithdrawalReceipt}`)
}