Skip to main content

Deposit and Checkpoint Event Tracking - PoS

Quick Summary

This section of the docs deals with tracking and monitoring the pace and speed of transactions done within the Polygon ecosystem. Depositing into the network (when done with the PoS bridge) typically takes an average of 22-30 minutes but we've seen instances where users seek to see real time progress reports. As a developer, you may also want to augment the UX of your app with instant feedback to the user. In all these cases, this section might be useful.

Deposit Events

When a token is deposited from Ethereum to Polygon, a process called state sync mechanism comes into play that eventually mints the tokens for the user on the Polygon chain. This process takes about ~22-30 minutes to happen and hence listening to the deposit event is very important to create a good user experience. This is an example script that can be used to track real time deposit events.

Realtime deposit event tracking using a web socket connection

const WebSocket = require("ws");
const Web3 = require("web3");

// For Mumbai
const ws = new WebSocket("wss://ws-mumbai.matic.today/");
// For Polygon mainnet: wss://ws-mainnet.matic.network/
const web3 = new Web3();
const abiCoder = web3.eth.abi;

async function checkDepositStatus(
userAccount,
rootToken,
depositAmount,
childChainManagerProxy
) {
return new Promise((resolve, reject) => {
ws.on("open", () => {
ws.send(
`{"id": 1, "method": "eth_subscribe", "params": ["newDeposits", {"Contract": "${childChainManagerProxy}"}]}`
);

ws.on("message", (msg) => {
const parsedMsg = JSON.parse(msg);
if (
parsedMsg &&
parsedMsg.params &&
parsedMsg.params.result &&
parsedMsg.params.result.Data
) {
const fullData = parsedMsg.params.result.Data;
const { 0: syncType, 1: syncData } = abiCoder.decodeParameters(
["bytes32", "bytes"],
fullData
);

// check if sync is of deposit type (keccak256("DEPOSIT"))
const depositType =
"0x87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f821";
if (syncType.toLowerCase() === depositType.toLowerCase()) {
const {
0: userAddress,
1: rootTokenAddress,
2: depositData,
} = abiCoder.decodeParameters(
["address", "address", "bytes"],
syncData
);

// depositData can be further decoded to get amount, tokenId etc. based on token type
// For ERC20 tokens
const { 0: amount } = abiCoder.decodeParameters(
["uint256"],
depositData
);
if (
userAddress.toLowerCase() === userAccount.toLowerCase() &&
rootToken.toLowerCase() === rootTokenAddress.toLowerCase() &&
depositAmount === amount
) {
resolve(true);
}
}
}
});

ws.on("error", () => {
reject(false);
});

ws.on("close", () => {
reject(false);
});
});
});
}

// Param1 - user address
// Param2 - contract address on main chain
// Param3 - amount deposited on main chain
// Param4 - child chain manager proxy address (0xA6FA4fB5f76172d178d61B04b0ecd319C5d1C0aa for mainnet)
checkDepositStatus(
"0xFd71Dc9721d9ddCF0480A582927c3dCd42f3064C",
"0x47195A03fC3Fc2881D084e8Dc03bD19BE8474E46",
"1000000000000000000",
"0xb5505a6d998549090530911180f38aC5130101c6"
)
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});

Historical deposit completion check by querying the blockchain

This script can be used to check if a particular deposit has been completed on the child chain or not. The main chain and the child chain keep incrementing the value of a global counter variable on both the chains. The StateSender contract emits an event that has the counter value. The counter value on the child chain can be queried from the StateReceiver contract. If the counter value on child chain is greater than or equal to the same on main chain, then the deposit can be considered as successfully completed.

let Web3 = require("web3");

// For mainnet, use Ethereum RPC
const provider = new Web3.providers.HttpProvider(
"https://goerli.infura.io/v3/API-KEY"
);
const web3 = new Web3(provider);

// For mainnet, use the Polygon mainnet RPC: <Sign up for a dedicated free RPC URL at https://rpc.maticvigil.com/ or other hosted node providers.>
const child_provider = new Web3.providers.HttpProvider(
"<insert Mumbai testnet RPC URL>" //Get a free RPC URL from https://rpc.maticvigil.com/ or other hosted node providers.
);

const child_web3 = new Web3(child_provider);

const contractInstance = new child_web3.eth.Contract(
[
{
constant: true,
inputs: [],
name: "lastStateId",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
payable: false,
stateMutability: "view",
type: "function",
},
],
"0x0000000000000000000000000000000000001001"
);

async function depositCompleted(txHash) {
let tx = await web3.eth.getTransactionReceipt(txHash);
let child_counter = await contractInstance.methods.lastStateId().call();
let root_counter = web3.utils.hexToNumberString(tx.logs[3].topics[1]);
return child_counter >= root_counter;
}

// Param 1 - Deposit transaction hash
depositCompleted(
"0x29d901174acd42d4651654a502073f3c876ff85b7887b2e2634d00848f6c982e"
)
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});

Checkpoint Events

Real-time checkpoint status tracking

All transactions that occur on the Polygon chain are checkpointed to the Ethereum chain at frequent intervals of time by the validators. This time is around 10 mins on Mumbai and around 30 mins on Polygon Mainnet. The checkpoint occurs on a contract called the RootChainContract deployed on the Ethereum chain. The following script can be used to listen to real-time checkpoint inclusion events.

const Web3 = require("web3");

// Ethereum provider
const provider = new Web3.providers.WebsocketProvider(
"wss://goerli.infura.io/ws/v3/api-key"
);

const web3 = new Web3(provider);

// Sign up for a free dedicated RPC URL at https://rpc.maticvigil.com/ or other hosted node providers.
const chil_provider = new Web3.providers.HttpProvider(
"<insert Mumbai testnet RPC URL>"
);
const child_web3 = new Web3(chil_provider);

// txHash - transaction hash on Polygon
// rootChainAddress - root chain proxy address on Ethereum
async function checkInclusion(txHash, rootChainAddress) {
let txDetails = await child_web3.eth.getTransactionReceipt(txHash);

block = txDetails.blockNumber;
return new Promise(async (resolve, reject) => {
web3.eth.subscribe(
"logs",
{
address: rootChainAddress,
},
async (error, result) => {
if (error) {
reject(error);
}

console.log(result);
if (result.data) {
let transaction = web3.eth.abi.decodeParameters(
["uint256", "uint256", "bytes32"],
result.data
);
if (block <= transaction["1"]) {
resolve(result);
}
}
}
);
});
}

// Param1 - Burn transaction hash on child chain
// Param2 - RootChainProxy Address on root chain (0x86E4Dc95c7FBdBf52e33D563BbDB00823894C287 for mainnet)
checkInclusion(
"0x9d1e61d9daaa12fcd00fcf332e1c06fd8253a949b4f2a4741c964454a67ea943",
"0x2890ba17efe978480615e330ecb65333b880928e"
)
.then((res) => {
console.log(res);
provider.disconnect();
})
.catch((err) => {
console.log(err);
});

Historical checkpoint inclusion check by querying the blockchain

This can be checked using the following API. The block number of the burn transaction on the child chain has to be given as a parameter to this GET API.

// Testnet
https://apis.matic.network/api/v1/mumbai/block-included/block-number
// Mainnet
https://apis.matic.network/api/v1/matic/block-included/block-number