Skip to content
This repository was archived by the owner on Apr 6, 2026. It is now read-only.
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 89 additions & 79 deletions pages/builders/app-developers/tutorials/send-tx-from-eth.mdx
Original file line number Diff line number Diff line change
@@ -1,37 +1,27 @@
---
title: Triggering OP Mainnet Transactions from Ethereum
title: Triggering OP Mainnet Transactions from Ethereum using Viem
lang: en-US
description: Learn how to force transaction inclusion without the OP Mainnet Sequencer.
description: Learn how to force transaction inclusion without the OP Mainnet Sequencer using Viem.
---

import { Callout, Steps } from 'nextra/components'
import { WipCallout } from '@/components/WipCallout'

<WipCallout />
# Triggering OP Mainnet Transactions from Ethereum
# Triggering OP Mainnet Transactions from Ethereum using Viem

OP Mainnet currently uses a single-Sequencer block production model.
This means that there is only one Sequencer active on the network at any given time. Single-Sequencer models are simpler than their highly decentralized counterparts but they are also more vulnerable to potential downtime.


OP Mainnet currently uses a single-Sequencer block production model.
This means that there is only one Sequencer active on the network at any given time.
Single-Sequencer models are simpler than their highly decentralized counterparts but they are also more vulnerable to potential downtime.

Sequencer downtime must not be able to prevent users from transacting on the network.
As a result, OP Mainnet includes a mechanism for "forcing" transactions to be included in the blockchain.
This mechanism involves triggering a transaction on OP Mainnet by sending a transaction on Ethereum.

In this tutorial you'll learn how to trigger a transaction on OP Mainnet from Ethereum.
You'll use the OP Sepolia testnet, but the same logic will apply to OP Mainnet.
Sequencer downtime must not be able to prevent users from transacting on the network. As a result, OP Mainnet includes a mechanism for "forcing" transactions to be included in the blockchain. This mechanism involves triggering a transaction on OP Mainnet by sending a transaction on Ethereum.
In this tutorial you'll learn how to trigger a transaction on OP Mainnet from Ethereum using Viem. You'll use the OP Sepolia testnet, but the same logic will apply to OP Mainnet.

## Dependencies

* [node](https://nodejs.org/en/)
* [pnpm](https://pnpm.io/installation)
* [node](https://nodejs.org/en/)
* [pnpm](https://pnpm.io/installation)
Comment thread
krofax marked this conversation as resolved.
Outdated

## Create a Demo Project

You're going to use the `@eth-optimism/contracts-ts` package for this tutorial.
Since the `@eth-optimism/contracts-ts` package is a [Node.js](https://nodejs.org/en/) library, you'll need to create a Node.js project to use it.
You're going to use the `viem` package for this tutorial. Since Viem is a [Node.js](https://nodejs.org/en/) library, you'll need to create a Node.js project to use it.

<Steps>

Expand All @@ -48,22 +38,10 @@ cd op-sample-project
pnpm init
```

{<h3>Install the Contracts Package</h3>}

```bash
pnpm add @eth-optimism/contracts-ts
```

{<h3>Install the Utils Package</h3>}

```bash
pnpm add @eth-optimism/core-utils
```

{<h3>Install ethers.js</h3>}
{<h3>Install Viem</h3>}

```bash
pnpm add ethers@^5
pnpm add viem
```

</Steps>
Expand All @@ -75,8 +53,7 @@ If you have [`cast`](https://book.getfoundry.sh/getting-started/installation) in

## Get ETH on Sepolia and OP Sepolia

This tutorial explains how to bridge tokens from Sepolia to OP Sepolia.
You will need to get some ETH on both of these testnets.
This tutorial explains how to bridge tokens from Sepolia to OP Sepolia. You will need to get some ETH on both of these testnets.

<Callout type="info">
You can use [this faucet](https://sepoliafaucet.com) to get ETH on Sepolia.
Expand All @@ -85,8 +62,8 @@ You can use the [Superchain Faucet](https://console.optimism.io/faucet?utm_sourc

## Add a Private Key to Your Environment

You need a private key in order to sign transactions.
Set your private key as an environment variable with the `export` command.
You need a private key in order to sign transactions.
Set your private key as an environment variable with the `export` command.
Make sure this private key corresponds to an address that has ETH on both Sepolia and OP Sepolia.

```bash
Expand All @@ -95,7 +72,7 @@ export TUTORIAL_PRIVATE_KEY=0x...

## Start the Node REPL

You're going to use the Node REPL to interact with the Optimism SDK.
You're going to use the Node REPL to interact with Viem.
To start the Node REPL run the following command in your terminal:

```bash
Expand All @@ -110,111 +87,144 @@ You need to import some dependencies into your Node REPL session.

<Steps>

{<h3>Import the Contracts Package</h3>}

```js file=<rootDir>/public/tutorials/send-tx-from-eth.js#L3 hash=02e8ca4ffb8e411c4b43d969c5533e24
```

{<h3>Import the Utils Package</h3>}

```js file=<rootDir>/public/tutorials/send-tx-from-eth.js#L4 hash=6e350dd75d29dff73d09b1f0cdd1fe78
```

{<h3>Import ethers.js</h3>}
{<h3>Import Viem</h3>}

```js file=<rootDir>/public/tutorials/send-tx-from-eth.js#L5 hash=69a65ef97862612e4978b8563e6dbe3a
```js
const { createPublicClient, createWalletClient, http, parseEther, formatEther } = require('viem');
const { optimismSepolia, sepolia } = require('viem/chains');
```

</Steps>

## Set Session Variables

You'll need a few variables throughout this tutorial.
Let's set those up now.
You'll need a few variables throughout this tutorial. Let's set those up now.

<Steps>

{<h3>Load your private key</h3>}

```js file=<rootDir>/public/tutorials/send-tx-from-eth.js#L7 hash=755b77a7ffc7dfdc186f36c37d3d847a
```js
const privateKey = process.env.TUTORIAL_PRIVATE_KEY;
```

{<h3>Create the RPC providers and wallets</h3>}

```js file=<rootDir>/public/tutorials/send-tx-from-eth.js#L9-L12 hash=9afdce50665ae93bce602068071ffaa1
```js
const l1PublicClient = createPublicClient({ chain: sepolia, transport: http("https://rpc.ankr.com/eth_sepolia") });
const l2PublicClient = createPublicClient({ chain: optimismSepolia, transport: http("https://sepolia.optimism.io") });
const l1WalletClient = createWalletClient({ chain: sepolia, transport: http("https://rpc.ankr.com/eth_sepolia") });
const l2WalletClient = createWalletClient({ chain: optimismSepolia, transport: http("https://sepolia.optimism.io") });
```

</Steps>

## Check Your Initial Balance

You'll be sending a small amount of ETH as part of this tutorial.
Quickly check your balance on OP Sepolia so that you know how much you had at the start of the tutorial.
You'll be sending a small amount of ETH as part of this tutorial. Quickly check your balance on OP Sepolia so that you know how much you had at the start of the tutorial.

```js file=<rootDir>/public/tutorials/send-tx-from-eth.js#L15-L16 hash=062c80bbd70e12144fe45532611a1846
```js
const address = await l2WalletClient.account.address;
const initialBalance = await l2PublicClient.getBalance({ address });
console.log(`Initial balance: ${formatEther(initialBalance)} ETH`);
```

## Trigger the Transaction

Now you'll use the [`OptimismPortal`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol) contract to trigger a transaction on OP Sepolia by sending a transaction on Sepolia.
Now you'll use the `OptimismPortal` contract to trigger a transaction on OP Sepolia by sending a transaction on Sepolia.

<Steps>

{<h3>Create the OptimismPortal object</h3>}

```js file=<rootDir>/public/tutorials/send-tx-from-eth.js#L18-L22 hash=75e09aebd1fe33724587ce7464f91940
```js
const optimismPortalAbi = [
{
inputs: [
{ internalType: 'uint256', name: '_gasLimit', type: 'uint256' },
{ internalType: 'bytes', name: '_data', type: 'bytes' },
],
name: 'depositTransaction',
outputs: [],
stateMutability: 'payable',
type: 'function',
},
];

const optimismPortalAddress = '0x16fc5058f25648194471939df75cf27a2fdc48bc';
```

{<h3>Estimate the required gas</h3>}

When sending transactions via the `OptimismPortal` contract it's important to always include a gas buffer.
This is because the `OptimismPortal` charges a variable amount of gas depending on the current demand for L2 transactions triggered via L1.
If you do not include a gas buffer, your transactions may fail.
When sending transactions via the `OptimismPortal` contract it's important to always include a gas buffer. This is because the `OptimismPortal` charges a variable amount of gas depending on the current demand for L2 transactions triggered via L1. If you do not include a gas buffer, your transactions may fail.

```js file=<rootDir>/public/tutorials/send-tx-from-eth.js#L25-L31 hash=a5ed372cf7ae78a6fea1e7a65c582cee
```js
const gasLimit = 100000n;
const data = '0x';
const value = parseEther('0.000001');

const gasEstimate = await l1PublicClient.estimateContractGas({
address: optimismPortalAddress,
abi: optimismPortalAbi,
functionName: 'depositTransaction',
args: [gasLimit, data],
value,
});
```

{<h3>Send the transaction</h3>}

Now you'll send the transaction.
Note that you are including a buffer of 20% on top of the gas estimate.
Now you'll send the transaction. Note that you are including a buffer of 20% on top of the gas estimate.

```js
const { hash: l1TxHash } = await l1WalletClient.writeContract({
address: optimismPortalAddress,
abi: optimismPortalAbi,
functionName: 'depositTransaction',
args: [gasLimit, data],
value,
gas: gasEstimate * 120n / 100n, // 20% buffer
});

```js file=<rootDir>/public/tutorials/send-tx-from-eth.js#L34-L43 hash=57a69ed74c2fb3bf2242b0452ed00f88
console.log(`L1 transaction hash: ${l1TxHash}`);
```

{<h3>Wait for the L1 transaction</h3>}

First you'll need to wait for the L1 transaction to be mined.

```js file=<rootDir>/public/tutorials/send-tx-from-eth.js#L46 hash=efcac85d794ab4711595112fbe2c7a8e
```js
const l1Receipt = await l1PublicClient.waitForTransactionReceipt({ hash: l1TxHash });
```

{<h3>Wait for the L2 transaction</h3>}

Now you'll need to wait for the corresponding L2 transaction to be included in a block.
This transaction is automatically created as a result of your L1 transaction.
Here you'll determine the hash of the L2 transaction using the `@eth-optimism/core-utils` library and then wait for that transaction to be included in the L2 blockchain.
Now you'll need to wait for the corresponding L2 transaction to be included in a block. This transaction is automatically created as a result of your L1 transaction. Here you'll determine the hash of the L2 transaction and then wait for that transaction to be included in the L2 blockchain.

```js file=<rootDir>/public/tutorials/send-tx-from-eth.js#L49-L50 hash=28b3fcba963fc4b960e89cc96b20aea2
```js
const l2TxHash = l1Receipt.logs[0].topics[1];
const l2Receipt = await l2PublicClient.waitForTransactionReceipt({ hash: l2TxHash });
console.log(`L2 transaction hash: ${l2TxHash}`);
```

</Steps>

## Check Your Updated Balance

You should have a little less ETH on OP Sepolia now.
Check your balance to confirm.
You should have a little less ETH on OP Sepolia now. Check your balance to confirm.

```js file=<rootDir>/public/tutorials/send-tx-from-eth.js#L53-L54 hash=b907d1590d7b39e8cfba4fb84886a8f9
```js
const finalBalance = await l2PublicClient.getBalance({ address });
console.log(`Final balance: ${formatEther(finalBalance)} ETH`);
```

Make sure that the difference is equal to the amount you were expecting to send.

```js file=<rootDir>/public/tutorials/send-tx-from-eth.js#L57-L58 hash=e44227b3ca6f46e6a16a10689b11ad39
```js
const difference = initialBalance - finalBalance;
console.log(`Difference: ${formatEther(difference)} ETH`);
```

## Next Steps

You've successfully triggered a transaction on OP Sepolia by sending a transaction on Sepolia.
Although this tutorial demonstrated the simple example of sending a basic ETH transfer from your L2 address via the OptimismPortal contract, you can use this same technique to trigger any transaction you want.
You can trigger smart contracts, send ERC-20 tokens, and more.
You've successfully triggered a transaction on OP Sepolia by sending a transaction on Sepolia using Viem. Although this tutorial demonstrated the simple example of sending a basic ETH transfer from your L2 address via the OptimismPortal contract, you can use this same technique to trigger any transaction you want. You can trigger smart contracts, send ERC-20 tokens, and more.