Comment on page
Viewing Transactions between Layers
Look up transactions passed between Mantle and Ethereum
In this tutorial, we'll be going over how to use the Mantle SDK to view transactions passed between Mantle network (L2) and Ethereum (L1). This function is particularly useful for dApps that make contract calls between L2 and L1.
Let's download the JS script that we'll be using to make SDK calls. The easiest way is to download the files from the Mantle GitHub repo using a Git command like so:
git clone https://github.com/mantlenetworkio/mantle.git
We need to configure the wallet that we're going to be using to send transactions. In the main directory, you'll see a
.env.testnet
file. You can specify your wallet private key in the PRIV_KEY
field, and an L1_RPC
URL for Goerli network. Now, navigate to the
./sdk-view-tx
directory and run the yarn
command to download the necessary dependencies to your local environment, such as the ethers.js library, the SDK modules, and more. They can all be found in the node_modules
directory once you successfully run Yarn
.Let's go over the files in the
./sdk-view-tx
directory and see what purpose each of them serves.index.js
: the main JS script that sends requests to node RPCs on L1 and L2 to query on-chain datapackage.json
: specifies dependencies and commands for script automationyarn.lock
: specifies dependencies
We can now start looking at the code in
index.js
. #! /usr/local/bin/node
const ethers = require("ethers")
const mantleSDK = require("@mantleio/sdk")
// Global variable because we need them almost everywhere
let crossChainMessenger
const setup = async () => {
crossChainMessenger = new mantleSDK.CrossChainMessenger({
l1ChainId: process.env.L1_CHAINID,
l2ChainId: process.env.L2_CHAINID,
l1SignerOrProvider: l1Wallet,
l2SignerOrProvider: l2Wallet
})
}
We first create a
crossChainMessenger
object that we'll use to fetch and view transaction information. This operation is limited to fetching public on-chain data using view
functions, so we don't need signers to send these requests. However, we do need chain ID values to direct requests to the correct node RPC.// 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"
}
]
const getSymbol = async l1Addr => {
if (l1Addr == '0x0000000000000000000000000000000000000000')
return "ETH"
if (l1Addr == '0xc1dC2d65A2243c22344E725677A3E3BEBD26E604')
return "MNT"
If the
l1Addr
field contains all zeroes, this implies the transferred token was $ETH. We also check the l1Addr
for $MNT token's contract address.1
const l1Contract = new ethers.Contract(l1Addr, ERC20ABI, crossChainMessenger.l1SignerOrProvider)
2
return await l1Contract.symbol()
3
}
Otherwise, we fetch the token symbol from the contract. The same query logic can be used for both L1 and L2 contracts.
1
// Describe a cross domain transaction, either deposit or withdrawal
2
3
const describeTx = async tx => {
4
console.log(`tx:${tx.transactionHash}`)
5
// Assume all tokens have decimals = 18
6
console.log(`\tAmount: ${tx.amount / 1e18} ${await getSymbol(tx.l1Token)}`)
7
console.log(`\tRelayed: ${await crossChainMessenger.getMessageStatus(tx.transactionHash)
8
== mantleSDK.MessageStatus.RELAYED}`)
9
}
The response of
crossDomainMessenger.getMessageStatus()
is a MessageStatus
enumerated value. What we're checking for is whether the deposit/withdrawal transaction is completed or still in progress.1
const main = async () => {
2
await setup()
3
const deposits = await crossChainMessenger.getDepositsByAddress(l1Wallet.address)
4
console.log(`Deposits by address ${addr}`)
5
for (var i=0; i<deposits.length; i++)
6
await describeTx(deposits[i])
7
8
const withdrawals = await crossChainMessenger.getWithdrawalsByAddress(l1Wallet.address)
9
console.log(`\n\n\nWithdrawals by address ${addr}`)
10
for (var i=0; i<withdrawals.length; i++)
11
await describeTx(withdrawals[i])
12
}
The
crossChainMessenger.getDepositsByAddress()
function call returns records of all deposit transactions made by an address, and crossChainMessenger.getWithdrawalsByAddress()
returns records of all withdrawal transactions sent by an address. Finally, we print the deposit and withdraw records to the console.With L1 and L2 RPC endpoints and the wallet private key configured, you can go ahead and run the
index.js
script by simply running the yarn testnet
command. If you have a local L2 or L1 instance running in your environment, you can switch up the respective RPC URLs in the .env.local
configuration file in the main directory and use the yarn local
command to run the script locally.The output would look something like:
Deposits by address 0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f
tx:0x3979f14c8e890aec790fa3743c2d7ae736b48aebfc9dc990e84b77cfaf744525
Amount: 1 L1EPT
Relayed: true
Withdrawals by address 0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f
tx:0xe650064362f163a394d3123e20029ed1b03846a6ae62e4cc8e962482c9cd4814
Amount: 1 L1EPT
Relayed: false
At this point, you should be able to look up deposits and withdrawals performed by any specific address. There are some additional tracing functions in
CrossChainMessenger
, but they are very similar in terms of operation.