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`); })()