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:
- 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.
- 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
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
#! /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)
})