Skip to main content

Withdrawing Celo from Dango to Holesky using Viem

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

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.

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 {
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 { dango } from "./dango.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: dango,
transport: http(),
}).extend(publicActionsL2());

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

export const walletClientL2 = createWalletClient({
chain: dango,
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}`)
}