Skip to content

Latest commit

 

History

History
 
 

cross-dom-bridge-eth

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

Bridging ETH with the Conduit AND Optimism SDK

Discord Twitter Follow

This tutorial teaches you how to use the Optimism SDK to transfer ETH between Layer 1 (Ethereum) and Layer 2 (Optimism).

Setup

  1. Ensure your computer has:

  2. Clone this repository and enter it.

    git clone https://github.com/ethereum-optimism/optimism-tutorial.git
    cd optimism-tutorial/cross-dom-bridge-eth
  3. Install the necessary packages.

    yarn
  4. Go to https://app.conduit.xyz/published/view/conduit-opstack-demo-nhl9xsg0wg to view the information for the OP-stack devnet

Run the sample code

The sample code is in index.js, execute it.

PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 node index.js

Expected output

Deposit ETH
On L1:904625697166532776746648320380374280103671755200316906557262200592 Gwei    On L2:904625697166532776746648320380374280103671755200316906559262375061 Gwei
Transaction hash (on L1): 0x52de853aa246f606f15f54b971ed17c4a837981bbda5b64bb516aeeb6d46f6b2
Waiting for status to change to RELAYED
Time so far 8.938 seconds
On L1:904625697166532776746648320380374280103671755200316906556262053171 Gwei    On L2:904625697166532776746648320380374280103671755200316906560262375061 Gwei
depositETH took 9.633 seconds

How does it work?

#! /usr/local/bin/node

// Transfers between L1 and L2 using the Optimism SDK

const ethers = require("ethers")
const optimismSDK = require("@eth-optimism/sdk")
const conduitSDK = require('@conduitxyz/sdk');
require('dotenv').config()

The libraries we need: ethers, dotenv and the Conduit and Optimism SDK themselves.

// Your settlment layer rpc url here
const l1Url = `https://l1-conduit-opstack-demo-nhl9xsg0wg.t.conduit.xyz`
// Your conduit rpc url here
const l2Url = `https://l2-conduit-opstack-demo-nhl9xsg0wg.t.conduit.xyz`
const privateKey = process.env.PRIVATE_KEY

Configuration, read from .env.

// Global variable because we need them almost everywhere
let crossChainMessenger
let addr    // Our address

The configuration parameters required for transfers.

getSigners

This function returns the two signers (one for each layer).

const getSigners = async () => {
    const l1RpcProvider = new ethers.providers.JsonRpcProvider(l1Url)    
    const l2RpcProvider = new ethers.providers.JsonRpcProvider(l2Url)

The first step is to create the two providers, each connected to an endpoint in the appropriate layer.

    const l1RpcProvider = new ethers.providers.JsonRpcProvider(l1Url)
    const l2RpcProvider = new ethers.providers.JsonRpcProvider(l2Url)
    const l1Wallet = new ethers.Wallet(privateKey, l1RpcProvider)
    const l2Wallet = new ethers.Wallet(privateKey, l2RpcProvider)

Finally, create and return the wallets. We need to use wallets, rather than providers, because we need to sign transactions.

setup

This function sets up the parameters we need for transfers.

const setup = async() => {
  const [l1Signer, l2Signer] = await getSigners()
  addr = l1Signer.address

Get the signers we need, and our address.

  // The network slug is available in the Network Information tab here: https://app.conduit.xyz/published/view/conduit-opstack-demo-nhl9xsg0wg
  let config = await conduitSDK.getOptimismConfiguration('conduit:conduit-opstack-demo-nhl9xsg0wg');
  config.l1SignerOrProvider = l1Signer
  config.l2SignerOrProvider = l2Signer
    
  crossChainMessenger = new optimismSDK.CrossChainMessenger(config)

Create the CrossChainMessenger object that we use to transfer assets. Here we generate a config at runtime given the slug of the Conduit chain. This queries Conduit's servers for the addresses of the rollups contracts and other metadata necessary for the CrossChainMessenger.

Variables that make it easier to convert between WEI and ETH

Both ETH and DAI are denominated in units that are 10^18 of their basic unit. These variables simplify the conversion.

const gwei = 1000000000n
const eth = gwei * gwei   // 10^18
const centieth = eth/100n

reportBalances

This function reports the ETH balances of the address on both layers.

const reportBalances = async () => {
  const l1Balance = (await crossChainMessenger.l1Signer.getBalance()).toString().slice(0,-9)
  const l2Balance = (await crossChainMessenger.l2Signer.getBalance()).toString().slice(0,-9)

  console.log(`On L1:${l1Balance} Gwei    On L2:${l2Balance} Gwei`)
}    // reportBalances

depositETH

This function shows how to deposit ETH from Ethereum to Optimism.

const depositETH = async () => {

  console.log("Deposit ETH")
  await reportBalances()

To show that the deposit actually happened we show before and after balances.

  const start = new Date()

  const response = await crossChainMessenger.depositETH(gwei)  

crossChainMessenger.depositETH() creates and sends the deposit trasaction on L1.

  console.log(`Transaction hash (on L1): ${response.hash}`)
  await response.wait()

Of course, it takes time for the transaction to actually be processed on L1.

  console.log("Waiting for status to change to RELAYED")
  console.log(`Time so far ${(new Date()-start)/1000} seconds`)
  await crossChainMessenger.waitForMessageStatus(response.hash, 
                                                  optimismSDK.MessageStatus.RELAYED) 

After the transaction is processed on L1 it needs to be picked up by an off-chain service and relayed to L2. To show that the deposit actually happened we need to wait until the message is relayed. The waitForMessageStatus function does this for us. Here are the statuses we can specify.

The third parameter (which is optional) is a hashed array of options:

  • pollIntervalMs: The poll interval
  • timeoutMs: Maximum time to wait
  await reportBalances()    
  console.log(`depositETH took ${(new Date()-start)/1000} seconds\n\n`)
}     // depositETH()

Once the message is relayed the balance change on Optimism is practically instantaneous. We can just report the balances and see that the L2 balance rose by 1 gwei.

withdrawETH

This function shows how to withdraw ETH from Optimism to Ethereum.

const withdrawETH = async () => { 
  
  console.log("Withdraw ETH")
  const start = new Date()  
  await reportBalances()

  const response = await crossChainMessenger.withdrawETH(centieth)

For deposits it was enough to transfer 1 gwei to show that the L2 balance increases. However, in the case of withdrawals the withdrawing account needs to be pay for finalizing the message, which costs more than that.

By sending 0.01 ETH it is guaranteed that the withdrawal will actually increase the L1 ETH balance instead of decreasing it.

  console.log(`Transaction hash (on L2): ${response.hash}`)
  await response.wait()

  console.log("Waiting for status to change to IN_CHALLENGE_PERIOD")

There are two wait periods for a withdrawal:

  1. Until the status root is written to L1.
  2. The challenge period.

You can read more about this here.

  console.log(`Time so far ${(new Date()-start)/1000} seconds`)  
  await crossChainMessenger.waitForMessageStatus(response.hash, 
    optimismSDK.MessageStatus.IN_CHALLENGE_PERIOD)
  console.log("In the challenge period, waiting for status READY_FOR_RELAY") 
  console.log(`Time so far ${(new Date()-start)/1000} seconds`)  
  await crossChainMessenger.waitForMessageStatus(response.hash, 
                                                optimismSDK.MessageStatus.READY_FOR_RELAY)

Wait until the state that includes the transaction gets past the challenge period, at which time we can finalize (also known as claim) the transaction.

                                                
  console.log("Ready for relay, finalizing message now")
  console.log(`Time so far ${(new Date()-start)/1000} seconds`)  
  await crossChainMessenger.finalizeMessage(response)

Finalizing the message also takes a bit of time.

  console.log("Waiting for status to change to RELAYED")
  console.log(`Time so far ${(new Date()-start)/1000} seconds`)  
  await crossChainMessenger.waitForMessageStatus(response, 
    optimismSDK.MessageStatus.RELAYED) 
  await reportBalances()   
  console.log(`withdrawETH took ${(new Date()-start)/1000} seconds\n\n\n`)  
}     // withdrawETH()

main

A main to run the setup followed by both operations.

const main = async () => {    
    await setup()
    await depositETH()
    await withdrawETH() 
}  // main



main().then(() => process.exit(0))
  .catch((error) => {
    console.error(error)
    process.exit(1)
  })

Conclusion

You should now be able to write applications that use Conduit's and Optimism's SDK and bridge to transfer ETH between layer 1 and layer 2.