Skip to content

Latest commit

 

History

History
 
 

sdk-trace-tx

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

Trace cross domain transactions

Discord Twitter Follow

This tutorial teaches you how to trace individual cross-domain transactions between L1 Ethereum and Optimism using the Optimism SDK. To see how to send these messages, see the cross domain tutorial or the tutorials on how to transfer ETH and ERC-20.

Getting started

Prerequisites

  • Have Node.js running on your computer, as well as yarn.
  • Access to L1 (Ethereum mainnet) and L2 (Optimism) providers.
  1. Use yarn to download the packages the script needs.

    yarn
  2. Copy .env.example to .env and specify the URLs for L1 and L2. For the transactions in this tutorial we will use Goerli. However, you can use the same code to trace mainnet transactions.

  3. Start the hardhat console

    yarn hardhat console --network l1
  4. Create a CrossDomainMessenger:

    optimismSDK = require("@eth-optimism/sdk")
    l1Provider = new ethers.providers.JsonRpcProvider(process.env.L1URL)
    l2Provider = new ethers.providers.JsonRpcProvider(process.env.L2URL)
    l1ChainId = (await l1Provider._networkPromise).chainId
    l2ChainId = (await l2Provider._networkPromise).chainId  
    crossChainMessenger = new optimismSDK.CrossChainMessenger({
      l1ChainId: l1ChainId,    
      l2ChainId: l2ChainId,          
      l1SignerOrProvider: l1Provider,
      l2SignerOrProvider: l2Provider,
      bedrock: true
    })

    Note: Until mainnet is updated to bedrock, don't put the bedrock: true when tracing bedrock transactions

Tracing a deposit

We are going to trace this deposit.

  1. Get the message status.

    l1TxHash = "0x80da95d06cfe8504b11295c8b3926709ccd6614b23863cdad721acd5f53c9052"
    await crossChainMessenger.getMessageStatus(l1TxHash)

    The list of message statuses and their meaning is in the SDK documentation. 6 means the message was relayed successfully.

  2. Get the message receipt.

    l2Rcpt = await crossChainMessenger.getMessageReceipt(l1TxHash)

    In addition to l2Rcpt.transactionReceipt, which contains the standard transaction receipt, you get l2Rcpt.receiptStatus with the transaction status. 1 means successful relay.

  3. Get the hash of the L2 transaction (l2Rcpt.transactionReceipt.transactionHash)

    l2TxHash = l2Rcpt.transactionReceipt.transactionHash

    You can view this transaction on Etherscan.

  4. In Optimism terminology deposit refers to any transaction going from L1 Ethereum to Optimism, and withdrawal refers to any transaction going from Optimism to L1 Ethereum, whether or not there are assets attached. To see if actual assets were transferred, you can parse the event log.

    The event names and their parameters are usually available on Etherscan, but you can't just copy and paste, you need to make a few changes:

    • Add event before each event.
    • Change the index_topic_<n> strings to indexed, and put them after the type rather than before.
    abi = [
      "event Transfer (address indexed from, address indexed to, uint256 value)",
      "event Mint (address indexed _account, uint256 _amount)",
      "event DepositFinalized (address indexed _l1Token, address indexed _l2Token, address indexed  _from, address _to, uint256 _amount, bytes _data)",
      "event RelayedMessage (bytes32 indexed msgHash)"
    ]
    iface = new ethers.utils.Interface(abi)
    logEvents = l2Rcpt.transactionReceipt.logs.map(x => {
       try {
       res = iface.parseLog(x)
       res.address = x.address
       return res
       } catch (e) {}
    }).filter(e => e != undefined)

    The try .. catch syntax is necessary because not all the log entries can be parsed by iface.

  5. When an asset is deposited, it is actually locked in the bridge on L1, and an equivalent asset is minted on L2. To see transferred assets, look for Mint events.

    mints = logEvents.filter(x => x.name == 'Mint')
    for(i = 0; i<mints.length; i++)
      console.log(`Asset: ${mints[i].address}, amount ${mints[0].args._amount / 1e18}`)

Tracing a withdrawal

We are going to trace this withdrawal.

  1. Get the message status.

    l2TxHash = "0x548f9eed01498e1b015aaf2f4b8c538f59a2ad9f450aa389bb0bde9b39f31053"
    await crossChainMessenger.getMessageStatus(l2TxHash)

    The list of message statuses and their meaning is in the SDK documentation. 6 means the message was relayed successfully.

  2. Get the message receipt.

    l1Rcpt = await crossChainMessenger.getMessageReceipt(l2TxHash)

    In addition to l1Rcpt.transactionReceipt, which contains the standard transaction receipt, you get l1Rcpt.receiptStatus with the transaction status. 1 means successful relay.

  3. Get the hash of the L1 transaction (l1Rcpt.transactionReceipt.transactionHash)

    l1TxHash = l1Rcpt.transactionReceipt.transactionHash

    You can view this transaction on Etherscan.

  4. In Optimism terminology deposit refers to any transaction going from L1 Ethereum to Optimism, and withdrawal refers to any transaction going from Optimism to L1 Ethereum, whether or not there are assets attached. To see if actual assets were transferred, you can parse the event log. This is how you parse the event log of the L2 transaction.

    The event names and their parameters are usually available on Etherscan, but you can't just copy and paste, you need to make a few changes:

    • Add event before each event.
    • Change the index_topic_<n> strings to indexed, and put them after the type rather than before.
    abi = [
      "event Transfer (address indexed from, address indexed to, uint256 value)",
      "event Burn (address indexed _account, uint256 _amount)",
      "event SentMessage (address indexed target, address sender, bytes message, uint256 messageNonce, uint256 gasLimit)",
      "event WithdrawalInitiated (address indexed _l1Token, address indexed _l2Token, address indexed _from, address _to, uint256 _amount, bytes _data)"
    ]
    iface = new ethers.utils.Interface(abi)
    l2Rcpt = await l2Provider.getTransactionReceipt(l2TxHash)
    events = l2Rcpt.logs.map(x => {
      res = iface.parseLog(x)
      res.address = x.address
      return res
    })
    logEvents = l2Rcpt.logs.map(x => {
       try {
       res = iface.parseLog(x)
       res.address = x.address
       return res
       } catch (e) {}
    }).filter(e => e != undefined)

    The try .. catch syntax is necessary because not all the log entries can be parsed by iface.

  5. When an asset is withdrawn, it is burned on L2, and then the bridge on L1 releases the equivalent asset. To see transferred assets, look for Burn events.

    burns = logEvents.filter(x => x.name == 'Burn')
    for(i = 0; i<burns.length; i++)
      console.log(`Asset: ${burns[i].address}, amount ${burns[0].args._amount / 1e18}`)