Setting a Confirmation Policy for Polygon PoS Transactions
Learn how to set a confirmation threshold on Polygon PoS that matches transaction risk: use a few Bor confirmations for low‑value payments and wait for Ethereum‑based checkpoint finality for high‑value flows.
15 Mar 2026, 02:52 UTC

Desired Outcome
Define and implement a confirmation threshold that matches the risk tolerance of your transaction flow on Polygon PoS (chain ID 137). Low‑value payments can use a small number of Bor confirmations for fast UX, while high‑value actions such as bridge exits or large transfers should wait for checkpoint finality on Ethereum.
Prerequisites
- Access to a Polygon PoS RPC endpoint (self‑hosted or paid provider) with permission to call
eth_chainId,eth_getBlockByNumber,eth_getTransactionReceipt, and read contract storage. - Knowledge of the transaction value or risk tier you want to policy‑gate (e.g.,
< $100vs.> $10 000). - The address of the Polygon RootChain contract on Ethereum (used to verify checkpoints). As of the latest mainnet deployment it is
0xA866...C3a3; verify from official docs before use. - Basic tooling: a script or backend that can issue JSON‑RPC calls (e.g., Web3.js, ethers.js) and handle retries with exponential backoff.
Focused Procedure
1. Verify Network and Chain ID
Run the following call against your RPC to ensure you are connected to Polygon PoS mainnet.
# Example using curl; replace with your endpoint
curl -X POST \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'
Expected response: {"result":"0x89"} (hex for 137). Any other value indicates a testnet or fork; adjust accordingly.
2. Determine Confirmation Tiers
- Low‑value flow: accept N Bor confirmations where N = 3 (≈6 s) or 5 (≈10 s). This tolerates the occasional short reorg that has been observed on Polygon PoS.
- High‑value flow: wait for checkpoint finality. A transaction is considered settled only when its block hash appears in a Heimdall checkpoint that has been committed to Ethereum.
3. Implement Low‑Value Confirmation Check
After sending a transaction, poll for its receipt and count confirmations:
async function waitForBorConfirmations(txHash, targetConfirmations, rpcUrl) {
let confirmations = 0;
while (confirmations < targetConfirmations) {
const receipt = await rpcCall(rpcUrl, 'eth_getTransactionReceipt', [txHash]);
if (!receipt || !receipt.blockNumber) {
await sleep(2000); // block time ~2s
continue;
}
const latestBlock = await rpcCall(rpcUrl, 'eth_getBlockByNumber', ['latest', false]);
confirmations = parseInt(latestBlock.number, 16) - parseInt(receipt.blockNumber, 16) + 1;
await sleep(2000);
}
return true;
}
Where rpcCall performs the JSON‑RPC request with retry/backoff.
4. Implement Checkpoint‑Finality Check for High‑Value Flows
Steps:
- After the transaction is included in a Bor block, record
blockHashandblockNumberfrom the receipt. - Periodically query the RootChain contract on Ethereum for the latest checkpoint. The checkpoint stores the Merkle root of Polygon block hashes; you can verify inclusion by providing a Merkle proof (usually supplied by a bridge or indexer). For a simple proxy, you can check whether the Polygon block number is less than or equal to the most recent checkpointed block number.
- Consider the transaction final once its block number ≤ latest checkpointed block number on Ethereum.
Example pseudo‑code (requires an Ethereum RPC):
async function waitForCheckpointFinality(polygonTxReceipt, polygonRpc, ethereumRpc, rootChainAddress) {
const polygonBlockNum = parseInt(polygonTxReceipt.blockNumber, 16);
while (true) {
// Get latest checkpointed block number from RootChain
const checkpointData = await rpcCall(ethereumRpc, 'eth_call', [
{
to: rootChainAddress,
data: '0x'+'0000000000000000000000000000000000000000000000000000000000000064' // example slot, replace with actual
},
'latest'
]);
// Decode checkpointData to obtain the highest Polygon block number committed
// (implementation depends on the contract ABI; see Polygon docs)
const latestCheckpointedPolygonBlock = decodeCheckpoint(checkpointData);
if (polygonBlockNum <= latestCheckpointedPolygonBlock) {
return true;
}
await sleep(15000); // check every ~15s (adjust based on observed checkpoint cadence)
}
}
5. Encode the Policy in Your Application
Create a function that selects the appropriate wait strategy based on a value threshold:
async function awaitSettlement(txHash, valueWei, polygonRpc, ethereumRpc, rootChain) {
const LOW_VALUE_THRESHOLD = web3.utils.toWei('100', 'ether'); // example: 100 POL
const receipt = await rpcCall(polygonRpc, 'eth_getTransactionReceipt', [txHash]);
if (valueWei.lt(LOW_VALUE_THRESHOLD)) {
await waitForBorConfirmations(txHash, 3, polygonRpc);
} else {
await waitForCheckpointFinality(receipt, polygonRpc, ethereumRpc, rootChain);
}
}
Expected Checks
- Verify
eth_chainIdreturns0x89before any transaction submission. - For low‑value flows, confirm that the number of Bor confirmations meets your target (e.g., ≥3) before considering the tx successful.
- For high‑value flows, confirm that the Polygon block number is ≤ the latest checkpointed block number on Ethereum (as read from RootChain).
- Monitor RPC latency and implement exponential backoff; log any failed calls.
Recovery Options
If a transaction is later reorged (possible only before checkpoint finality):
- Low‑value flows: because you accepted only a few Bor confirmations, treat the transaction as pending and resend with a new nonce if needed.
- High‑value flows: since you waited for checkpoint finality, a reorg would require an Ethereum reorg, which is extremely unlikely; no recovery action is required.
In case of RPC failure, retry the read calls with backoff; if the failure persists, switch to a backup RPC provider or a self‑hosted node.
Limitations and Practical Verification
Checkpoint cadence can change after network upgrades (e.g., Heimdall version updates). Therefore:
- Do not hardcode a fixed time (like “30 minutes”) for checkpoint finality; instead query the RootChain contract as shown.
- Periodically (e.g., every hour) log the latest checkpointed block number to detect sudden changes.
- On a testnet (currently Amoy, chain ID 80002), repeat the same verification steps to ensure your logic works before deploying to mainnet.
To practically verify your policy, send a small test transaction (< 0.001 POL) on Amoy, apply the low‑value flow, and confirm that the script returns success after ~3 Bor blocks. Then send a higher‑value test transaction, apply the high‑value flow, and ensure the script only returns success after the RootChain checkpoint reflects the transaction’s block.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.