Builders
Developer Tools
SDK Javascript
View transactions between layers

View Transactions Between Layers

This tutorial teaches you how to use the Optimism SDK (opens in a new tab) to view the transactions passed between L1 (Ethereum) and L2 (OP Mainnet) by an address.

The SDK supports multiple OP Chains: OP, Base, etc. To see whether a specific OP Chain is supported directly, see the documentation (opens in a new tab). Chains that aren't officially supported just take a few extra steps. Get the L1 contract addresses, and provide them to the SDK. Once you do that, you can use the SDK normally.

Before You Begin

The node script, index.js, makes these assumptions:

  1. You have Node.js (opens in a new tab), pnpm (opens in a new tab), and yarn (opens in a new tab) installed on your system.
  2. Access to L1 (Ethereum mainnet) and L2 (OP Mainnet) providers.

Running the script

Make a Project Folder and Initialize It

mkdir project-folder-name
cd folder-name
pnpm init

Create Two Files for the Folder

  • Create a .env file
.env file
L1URL=<<< L1 URL goes here >>>
L2URL=<<< L2 URL goes here >>>
  • Specify the URLs for L1 and L2 in .env file
  • Create an view-tx.js file
view-tx.js
#! /usr/local/bin/node
 
// View transfers between L1 and L2 using the Optimism SDK
 
const ethers = require("ethers")
const optimismSDK = require("@eth-optimism/sdk")
require('dotenv').config()
 
// Global variable because we need them almost everywhere
let crossChainMessenger
 
 
const setup = async() => {
 
  l1SignerOrProvider = new ethers.providers.JsonRpcProvider(process.env.L1URL)
  l2SignerOrProvider = new ethers.providers.JsonRpcProvider(process.env.L2URL)
 
  crossChainMessenger = new optimismSDK.CrossChainMessenger({
      l1ChainId: (await l1SignerOrProvider._networkPromise).chainId,
      l2ChainId: (await l2SignerOrProvider._networkPromise).chainId,      
      l1SignerOrProvider: l1SignerOrProvider,
      l2SignerOrProvider: l2SignerOrProvider
  })
}    // setup
 
 
// Only the part of the ABI we need to get the symbol
const ERC20ABI = [
  {
    "constant": true,
    "inputs": [],
    "name": "symbol",
    "outputs": [
        {
            "name": "",
            "type": "string"
        }
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  }
]     // ERC20ABI
 
 
 
const getSymbol = async l1Addr => {
  if (l1Addr == '0x0000000000000000000000000000000000000000')
    return "ETH"
  const l1Contract = new ethers.Contract(l1Addr, ERC20ABI, crossChainMessenger.l1SignerOrProvider)
  return await l1Contract.symbol()  
}   // getSymbol
 
// Describe a cross domain transaction, either deposit or withdrawal
const describeTx = async tx => {
  console.log(`tx:${tx.transactionHash}`)
  // Assume all tokens have decimals = 18
  console.log(`\tAmount: ${tx.amount/1e18} ${await getSymbol(tx.l1Token)}`)
  console.log(`\tRelayed: ${await crossChainMessenger.getMessageStatus(tx.transactionHash)  
                              == optimismSDK.MessageStatus.RELAYED}`)
}  // describeTx
 
 
const main = async () => {    
    await setup()
 
    // The address we trace
    const addr = "0xBCf86Fd70a0183433763ab0c14E7a760194f3a9F"
 
    const deposits = await crossChainMessenger.getDepositsByAddress(addr)
    console.log(`Deposits by address ${addr}`)
    for (var i=0; i<deposits.length; i++)
      await describeTx(deposits[i])
 
    const withdrawals = await crossChainMessenger.getWithdrawalsByAddress(addr)
    console.log(`\n\n\nWithdrawals by address ${addr}`)
    for (var i=0; i<withdrawals.length; i++)
      await describeTx(withdrawals[i])
      
}  // main
 
 
 
main().then(() => process.exit(0))
  .catch((error) => {
    console.error(error)
    process.exit(1)
  })

Install the dependencies

pnpm add @eth-optimism/sdk@^3.0.0 dotenv@^16.0.0 ethers@^5.6.1

Use Node to run the script

node view-tx.js

Results

Here are the expected results. Note that by the time you read this there might be additional transactions reported.

Deposits by address 0xBCf86Fd70a0183433763ab0c14E7a760194f3a9F
tx:0xa35a3085e025e2addd59c5ef2a2e5529be5141522c3cce78a1b137f2eb992d19
	Amount: 0.01 ETH
	Relayed: true



Withdrawals by address 0xBCf86Fd70a0183433763ab0c14E7a760194f3a9F
tx:0x7826399958c6bb3831ef0b02b658e7e3e69f334e20e27a3c14d7caae545c3d0d
	Amount: 1 DAI
	Relayed: false
tx:0xd9fd11fd12a58d9115afa2ad677745b1f7f5bbafab2142ae2cede61f80e90e8a
	Amount: 0.001 ETH
	Relayed: true

Conclusion

You should now know how to identify all the deposits and/or withdrawals done by a specific address. There are some additional tracing functions in CrossChainMessenger (opens in a new tab), but they are very similar in operation. Of course, if you have any problems you can ask on our developer support forum (opens in a new tab).

How does it work?

In this section, we go over the script line by line to learn how to use the SDK to view deposits and withdrawals.

#! /usr/local/bin/node
 
// View transfers between L1 and L2 using the Optimism SDK
 
const ethers = require("ethers")
const optimismSDK = require("@eth-optimism/sdk")
require('dotenv').config()
 
// Global variable because we need them almost everywhere
let crossChainMessenger
 
 
const setup = async() => {
 
  l1SignerOrProvider = new ethers.providers.JsonRpcProvider(process.env.L1URL)
  l2SignerOrProvider = new ethers.providers.JsonRpcProvider(process.env.L2URL)
 
  crossChainMessenger = new optimismSDK.CrossChainMessenger({
      l1ChainId: (await l1SignerOrProvider._networkPromise).chainId,
      l2ChainId: (await l2SignerOrProvider._networkPromise).chainId,      
      l1SignerOrProvider: l1SignerOrProvider,
      l2SignerOrProvider: l2SignerOrProvider
  })
}    // setup

Create the CrossChainMessenger (opens in a new tab) object that we use to view information. Note that we do not need signers here, since we are only calling view functions. However, we do need the chainId values.

// Only the part of the ABI we need to get the symbol
const ERC20ABI = [
  {
    "constant": true,
    "inputs": [],
    "name": "symbol",
    "outputs": [
        {
            "name": "",
            "type": "string"
        }
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  }
]     // ERC20ABI
 
 
 
const getSymbol = async l1Addr => {
  if (l1Addr == '0x0000000000000000000000000000000000000000')
    return "ETH"

If l1Addr is all zeroes, it means the transfer was ETH.

  const l1Contract = new ethers.Contract(l1Addr, ERC20ABI, crossChainMessenger.l1SignerOrProvider)
  return await l1Contract.symbol()  

Otherwise, ask the contract (we could have used the L1 or the L2) what is the correct symbol.

}   // getSymbol
 
 
 
// Describe a cross domain transaction, either deposit or withdrawal
const describeTx = async tx => {
  console.log(`tx:${tx.transactionHash}`)
  // Assume all tokens have decimals = 18
  console.log(`\tAmount: ${tx.amount/1e18} ${await getSymbol(tx.l1Token)}`)
  console.log(`\tRelayed: ${await crossChainMessenger.getMessageStatus(tx.transactionHash)  
                              == optimismSDK.MessageStatus.RELAYED}`)

The result of crossDomainMessenger.getMessageStatus (opens in a new tab) is a MessageStatus enumerated value (opens in a new tab). In this case we only care whether the deposit/withdrawal is still in process or if it is done.

}  // describeTx
 
 
const main = async () => {    
    await setup()
 
    // The address we trace
    const addr = "0xBCf86Fd70a0183433763ab0c14E7a760194f3a9F"
 
    const deposits = await crossChainMessenger.getDepositsByAddress(addr)

The crossChainMessenger.getDepositsByAddress function (opens in a new tab) gives us all the deposits by an address.

    console.log(`Deposits by address ${addr}`)
    for (var i=0; i<deposits.length; i++)
      await describeTx(deposits[i])
 
    const withdrawals = await crossChainMessenger.getWithdrawalsByAddress(addr)

The crossChainMessenger.getWithdrawalsByAddress function (opens in a new tab) gives us all the deposits by an address.

    console.log(`\n\n\nWithdrawals by address ${addr}`)
    for (var i=0; i<withdrawals.length; i++)
      await describeTx(withdrawals[i])
 
}  // main
 
 
 
main().then(() => process.exit(0))
  .catch((error) => {
    console.error(error)
    process.exit(1)
  })