diff --git a/.github/workflows/tutorials.yml b/.github/workflows/tutorials.yml
index 9e5336541..7018045f1 100644
--- a/.github/workflows/tutorials.yml
+++ b/.github/workflows/tutorials.yml
@@ -47,10 +47,48 @@ jobs:
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
- sdk-estimate-costs:
+ send-tx-from-eth:
needs: cross-dom-bridge-eth
runs-on: ubuntu-latest
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v2
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v2
+ with:
+ node-version: '20.x'
+
+ - name: Install pnpm
+ run: npm install -g pnpm
+
+ - name: Install dependencies
+ run: pnpm install
+
+ - name: Run tutorial
+ env:
+ TUTORIAL_PRIVATE_KEY: ${{ secrets.TUTORIAL_PRIVATE_KEY }}
+ run:
+ node ./public/tutorials/send-tx-from-eth.js
+
+ - name: Notify Slack on failure
+ uses: ravsamhq/notify-slack-action@v2
+ if: always()
+ with:
+ status: ${{ job.status }}
+ notify_when: "failure"
+ notification_title: "{workflow} has {status_message} (<{run_url}|view errors>)"
+ message_format: "{emoji} *{workflow}* {status_message} in <{repo_url}|{repo}>"
+ footer: "<{run_url}|View Run>"
+ mention_users_when: "failure,warnings"
+ env:
+ SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
+
+ sdk-estimate-costs:
+ needs: send-tx-from-eth
+ runs-on: ubuntu-latest
+
steps:
- name: Check out code
uses: actions/checkout@v2
diff --git a/pages/builders/app-developers/tutorials/send-tx-from-eth.mdx b/pages/builders/app-developers/tutorials/send-tx-from-eth.mdx
index 5b95054cf..b971a8417 100644
--- a/pages/builders/app-developers/tutorials/send-tx-from-eth.mdx
+++ b/pages/builders/app-developers/tutorials/send-tx-from-eth.mdx
@@ -1,25 +1,20 @@
---
-title: Triggering OP Mainnet transactions from Ethereum
+title: Triggering OP Stack transactions from Ethereum
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 Stack Sequencer using Viem.
---
import { Callout, Steps } from 'nextra/components'
import { WipCallout } from '@/components/WipCallout'
-# Triggering OP Mainnet transactions from Ethereum
+# Triggering OP Stack transactions from Ethereum
-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 Stack 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 Stack includes a mechanism for "forcing" transactions to be included in the blockchain. This mechanism involves triggering a transaction on OP Stack by sending a transaction on Ethereum.
+In this tutorial you'll learn how to trigger a transaction on OP Stack from Ethereum using Viem. You'll use the OP Sepolia testnet, but the same logic will apply to OP Stack.
## Dependencies
@@ -28,57 +23,41 @@ You'll use the OP Sepolia testnet, but the same logic will apply to OP Mainnet.
## 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.
+ {Make a Project Folder
}
-{Make a Project Folder
}
-
-```bash
-mkdir op-sample-project
-cd op-sample-project
-```
-
-{Initialize the Project
}
+ ```bash
+ mkdir trigger-transaction
+ cd trigger-transaction
+ ```
-```bash
-pnpm init
-```
-
-{Install the Contracts Package
}
+ {Initialize the Project
}
-```bash
-pnpm add @eth-optimism/contracts-ts
-```
+ ```bash
+ pnpm init
+ ```
-{Install the Utils Package
}
-
-```bash
-pnpm add @eth-optimism/core-utils
-```
-
-{Install ethers.js
}
-
-```bash
-pnpm add ethers@^5
-```
+ {Install Viem
}
+ ```bash
+ pnpm add viem
+ ```
-Want to create a new wallet for this tutorial?
-If you have [`cast`](https://book.getfoundry.sh/getting-started/installation) installed you can run `cast wallet new` in your terminal to create a new wallet and get the private key.
+ Want to create a new wallet for this tutorial?
+ If you have [`cast`](https://book.getfoundry.sh/getting-started/installation) installed you can run `cast wallet new` in your terminal to create a new wallet and get the private key.
## 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.
-You can use [this faucet](https://sepoliafaucet.com) to get ETH on Sepolia.
-You can use the [Superchain Faucet](https://console.optimism.io/faucet?utm_source=docs) to get ETH on OP Sepolia.
+ You can use [this faucet](https://sepoliafaucet.com) to get ETH on Sepolia.
+ You can use the [Superchain Faucet](https://console.optimism.io/faucet?utm_source=docs) to get ETH on OP Sepolia.
## Add a private key to your environment
@@ -93,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
@@ -107,112 +86,86 @@ This will bring up a Node REPL prompt that allows you to run javascript code.
You need to import some dependencies into your Node REPL session.
+ {Import Viem
}
-{Import the Contracts Package
}
-
-```js file=/public/tutorials/send-tx-from-eth.js#L3 hash=02e8ca4ffb8e411c4b43d969c5533e24
-```
-
-{Import the Utils Package
}
-
-```js file=/public/tutorials/send-tx-from-eth.js#L4 hash=6e350dd75d29dff73d09b1f0cdd1fe78
-```
-
-{Import ethers.js
}
-
-```js file=/public/tutorials/send-tx-from-eth.js#L5 hash=69a65ef97862612e4978b8563e6dbe3a
-```
-
+ ```js file=/public/tutorials/send-tx-from-eth.js#L3-L6 hash=1e06dede41cb7ba0bd9414a8962521c6
+ ```
## 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.
+ {Load your private key
}
-{Load your private key
}
-
-```js file=/public/tutorials/send-tx-from-eth.js#L7 hash=755b77a7ffc7dfdc186f36c37d3d847a
-```
-
-{Create the RPC providers and wallets
}
+ ```js file=/public/tutorials/send-tx-from-eth.js#L8-L9 hash=46ba01375a5d8844b2315f0e579dfac3
+ ```
-```js file=/public/tutorials/send-tx-from-eth.js#L9-L12 hash=9afdce50665ae93bce602068071ffaa1
-```
+ {Create the RPC providers and wallets
}
+ ```js file=/public/tutorials/send-tx-from-eth.js#L11-L13 hash=d5a5a1252f4b6ff026cd58de8e6ae7f1
+ ```
## 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=/public/tutorials/send-tx-from-eth.js#L15-L16 hash=062c80bbd70e12144fe45532611a1846
+```js file=/public/tutorials/send-tx-from-eth.js#L17-L18 hash=2bbd74b9de0c0fa0daca043ab9030ff0
```
## 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.
+ {Create the OptimismPortal object
}
-{Create the OptimismPortal object
}
+ ```js file=/public/tutorials/send-tx-from-eth.js#L20-L31 hash=b062257111aacc2f3a985542e451269c
+ ```
-```js file=/public/tutorials/send-tx-from-eth.js#L18-L22 hash=75e09aebd1fe33724587ce7464f91940
-```
+ {Estimate the required gas
}
-{Estimate the required gas
}
+ 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=/public/tutorials/send-tx-from-eth.js#L33-L45 hash=f5d0d92f161514a3359997143804af0b
+ ```
-```js file=/public/tutorials/send-tx-from-eth.js#L25-L31 hash=a5ed372cf7ae78a6fea1e7a65c582cee
-```
+ {Send the transaction
}
-{Send the transaction
}
+ 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 file=/public/tutorials/send-tx-from-eth.js#L50-L61 hash=59e3ee527809087e9e615f28caa49083
+ ```
-```js file=/public/tutorials/send-tx-from-eth.js#L34-L43 hash=57a69ed74c2fb3bf2242b0452ed00f88
-```
+ {Wait for the L1 transaction
}
-{Wait for the L1 transaction
}
+ First you'll need to wait for the L1 transaction to be mined.
-First you'll need to wait for the L1 transaction to be mined.
+ ```js file=/public/tutorials/send-tx-from-eth.js#L60 hash=0efd9bd3369de7f5f36ea5540a5d8078
+ ```
-```js file=/public/tutorials/send-tx-from-eth.js#L46 hash=efcac85d794ab4711595112fbe2c7a8e
-```
-
-{Wait for the L2 transaction
}
+ {Wait for the L2 transaction
}
-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.
-
-```js file=/public/tutorials/send-tx-from-eth.js#L49-L50 hash=28b3fcba963fc4b960e89cc96b20aea2
-```
+ 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=/public/tutorials/send-tx-from-eth.js#L67-L73 hash=bf903509fb370c2b4e85dbfbf05650ee
+ ```
## 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=/public/tutorials/send-tx-from-eth.js#L53-L54 hash=b907d1590d7b39e8cfba4fb84886a8f9
+```js file=/public/tutorials/send-tx-from-eth.js#L75-L76 hash=dd456528a8bae103b73c5bcd19216916
```
Make sure that the difference is equal to the amount you were expecting to send.
-```js file=/public/tutorials/send-tx-from-eth.js#L57-L58 hash=e44227b3ca6f46e6a16a10689b11ad39
+```js file=/public/tutorials/send-tx-from-eth.js#L78-L79 hash=3885097e127ff18b3c2c2fc1d3d5a7c0
```
## 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.
diff --git a/public/tutorials/send-tx-from-eth.js b/public/tutorials/send-tx-from-eth.js
index 9666aeb07..12ea74460 100644
--- a/public/tutorials/send-tx-from-eth.js
+++ b/public/tutorials/send-tx-from-eth.js
@@ -1,60 +1,81 @@
(async () => {
-const contracts = require("@eth-optimism/contracts-ts")
-const utils = require("@eth-optimism/core-utils")
-const ethers = require("ethers")
-
-const privateKey = process.env.TUTORIAL_PRIVATE_KEY
-
-const l1Provider = new ethers.providers.StaticJsonRpcProvider("https://rpc.ankr.com/eth_sepolia")
-const l2Provider = new ethers.providers.StaticJsonRpcProvider("https://sepolia.optimism.io")
-const l1Wallet = new ethers.Wallet(privateKey, l1Provider)
-const l2Wallet = new ethers.Wallet(privateKey, l2Provider)
-
-console.log('Initial balance:')
-const initialBalance = await l2Wallet.getBalance()
-console.log(ethers.utils.formatEther(initialBalance))
-
-const OptimismPortal = new ethers.Contract(
- '0x16Fc5058F25648194471939df75CF27A2fdC48BC',
- contracts.optimismPortalABI,
- l1Wallet,
-)
-
-console.log('Estimating L1 transaction gas...')
-const gas = await OptimismPortal.estimateGas.depositTransaction(
- '0x1000000000000000000000000000000000000000', // _to
- ethers.utils.parseEther('0.000069420'), // _value
- 1e6, // _gasLimit
- false, // _isCreation
- '0x', // _data
-)
-
-console.log('Sending L1 transaction...')
-const tx = await OptimismPortal.depositTransaction(
- '0x1000000000000000000000000000000000000000', // _to
- ethers.utils.parseEther('0.000069420'), // _value
- 1e6, // _gasLimit
- false, // _isCreation
- '0x', // _data
- {
- gasLimit: gas.mul(120).div(100) // Add 20% buffer
- }
-)
-
-console.log('Waiting for L1 transaction...')
-const receipt = await tx.wait()
-
-console.log('Waiting for L2 transaction to be relayed...')
-const deposit = utils.DepositTx.fromL1Receipt(receipt, 0)
-await l2Provider.waitForTransaction(deposit.hash())
-
-console.log('Final balance:')
-const finalBalance = await l2Wallet.getBalance()
-console.log(ethers.utils.formatEther(finalBalance))
-
-console.log('Difference:')
-const difference = initialBalance.sub(finalBalance)
-console.log(ethers.utils.formatEther(difference))
+ const { createPublicClient, createWalletClient, http, parseEther, formatEther } = require('viem');
+ const { optimismSepolia, sepolia } = require('viem/chains');
+ const { privateKeyToAccount } = require('viem/accounts');
+ const { publicActionsL2, publicActionsL1, walletActionsL2, walletActionsL1, getL2TransactionHashes } = require ('viem/op-stack')
+
+ const privateKey = process.env.TUTORIAL_PRIVATE_KEY;
+ const account = privateKeyToAccount(privateKey);
+
+ const l1PublicClient = createPublicClient({ chain: sepolia, transport: http("https://rpc.ankr.com/eth_sepolia") }).extend(publicActionsL1())
+ const l2PublicClient = createPublicClient({ chain: optimismSepolia, transport: http("https://sepolia.optimism.io") }).extend(publicActionsL2());
+ const l1WalletClient = createWalletClient({ chain: sepolia, transport: http("https://rpc.ankr.com/eth_sepolia") }).extend(walletActionsL1());
+ const l2WalletClient = createWalletClient({ chain: optimismSepolia, transport: http("https://sepolia.optimism.io") }).extend(walletActionsL2())
+
+ const address = account.address;
+ const initialBalance = await l2PublicClient.getBalance({ address });
+ console.log(`Initial balance: ${formatEther(initialBalance)} ETH`);
+
+ const optimismPortalAbi = [
+ {
+ inputs: [
+ { internalType: 'uint256', name: '_gasLimit', type: 'uint256' },
+ { internalType: 'bytes', name: '_data', type: 'bytes' },
+ ],
+ name: 'depositTransaction',
+ outputs: [],
+ stateMutability: 'payable',
+ type: 'function',
+ },
+ ];
+
+ const optimismPortalAddress = '0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383';
+ const gasLimit = 100000n;
+ const data = '0x';
+ const value = parseEther('0.000069420');
+
+ const gasEstimate = await l1PublicClient.estimateContractGas({
+ address: optimismPortalAddress,
+ abi: optimismPortalAbi,
+ functionName: 'depositTransaction',
+ args: [gasLimit, data],
+ value,
+ account: account.address,
+ });
+
+ console.log(`Gas estimate: ${gasEstimate}`);
+
+ // Step 3: Send the transaction
+ const { request } = await l1PublicClient.simulateContract({
+ account,
+ address: optimismPortalAddress,
+ abi: optimismPortalAbi,
+ functionName: 'depositTransaction',
+ args: [gasLimit, data],
+ value,
+ gas: gasEstimate * 120n / 100n, // 20% buffer
+ })
+
+ const l1TxHash = await l1WalletClient.writeContract(request)
+ console.log(`L1 transaction hash: ${l1TxHash}`)
+
+ // Step 4: Wait for the L1 transaction
+ const l1Receipt = await l1PublicClient.waitForTransactionReceipt({hash: l1TxHash})
+ console.log('L1 transaction confirmed:', l1Receipt)
+
+ const [l2Hash] = getL2TransactionHashes(l1TxHash)
+ console.log(`Corresponding L2 transaction hash: ${l2Hash}`);
+
+ const l2Receipt = await l2PublicClient.waitForTransactionReceipt({
+ hash: l2Hash,
+ });
+ console.log('L2 transaction confirmed:', l2Receipt);
+
+ const finalBalance = await l2Wallet.getBalance()
+ console.log(`Final balance: ${formatEther(finalBalance)} ETH`);
+
+ const difference = initialBalance - finalBalance
+ console.log(`Difference in balance: ${formatEther(difference)} ETH`);
})()