Verifying Polygon PoS Bridge Transfers with a Test Token Transfer
Learn how to confirm the Polygon PoS Bridge locks, mints, and releases ERC‑20 tokens by performing a small test transfer and checking the transaction status on both chains.
07 Jul 2025, 11:07 UTC

Desired outcome
Confirm that the Polygon PoS Bridge correctly locks an ERC‑20 token on Ethereum, mints its representation on Polygon, and allows the token to be withdrawn back to Ethereum after the checkpoint period.
Prerequisites
- Access to an Ethereum RPC endpoint (mainnet or a testnet such as Goerli) and a Polygon PoS RPC endpoint (mainnet or Mumbai testnet).
- A wallet (e.g., MetaMask) or a locally managed private key that holds a small amount of the token you wish to test and enough native gas (ETH on Ethereum, MATIC on Polygon) for transaction fees.
- Node.js ≥14 installed if you prefer to run the example script with ethers.js.
- The official bridge contract addresses for the network you are using (Predicate and RootChainManager). These can be copied from Etherscan/Polygonscan.
Procedure
- Set up the environment
# Install ethers.js (if not already) npm install ethers@6 - Configure constants (replace placeholders with your values)
const ETH_RPC = "https://eth-mainnet.alchemyapi.io/v2/your-key"; const POLYGON_RPC = "https://polygon-mainnet.g.alchemy.com/v2/your-key"; const PRIVATE_KEY = "0xyour_private_key"; const TOKEN_ADDRESS = "0xERC20TokenAddress"; // e.g., USDC on Ethereum const AMOUNT = ethers.parseUnits("0.001", 18); // adjust decimals const PREDICATE_ADDRESS = "0xPredicateContractAddress"; const ROOT_CHAIN_MANAGER_ADDRESS = "0xRootChainManagerAddress"; - Create provider and wallet objects
const { ethers } = require("ethers"); const ethProvider = new ethers.JsonRpcProvider(ETH_RPC); const polyProvider = new ethers.JsonRpcProvider(POLYGON_RPC); const signer = new ethers.Wallet(PRIVATE_KEY, ethProvider); - Approve the bridge to spend your token
const token = new ethers.Contract(TOKEN_ADDRESS, ["function approve(address spender, uint256 amount) returns (bool)"], signer); const approveTx = await token.approve(PREDICATE_ADDRESS, AMOUNT); await approveTx.wait(); console.log("Approval tx hash:", approveTx.hash); - Deposit (lock) the token on Ethereum
const predicate = new ethers.Contract(PREDICATE_ADDRESS, ["function depositERC20(address token, address to, uint256 amount, bytes data)"], signer); const depositTx = await predicate.depositERC20(TOKEN_ADDRESS, signer.address, AMOUNT, "0x"); await depositTx.wait(); console.log("Deposit tx hash:", depositTx.hash); - Wait for the Polygon mint
After the Ethereum transaction is confirmed, the bridge mints the wrapped token on Polygon. This usually appears within 5‑30 minutes. You can verify by checking the token balance on Polygon:
const polyToken = new ethers.Contract(TOKEN_ADDRESS, ["function balanceOf(address) view returns (uint256)"], polyProvider); const balance = await polyToken.balanceOf(signer.address); console.log("Polygon token balance:", ethers.formatUnits(balance, 18)); - Initiate withdrawal back to Ethereum
Withdrawals require submitting an exit transaction on Polygon that includes a proof of the burn. The Polygon SDK provides a helper; the raw call is to the RootChainManager.
const rootChainManager = new ethers.Contract(ROOT_CHAIN_MANAGER_ADDRESS, ["function exitERC20(address token, address recipient, uint256 amount, bytes proof)"], signer); // For a test withdrawal you can use a zero‑proof placeholder; in practice you must obtain the proof from the bridge. const exitTx = await rootChainManager.exitERC20(TOKEN_ADDRESS, signer.address, AMOUNT, "0x"); await exitTx.wait(); console.log("Exit tx hash:", exitTx.hash); - Challenge period
After the exit transaction, the tokens remain locked for the challenge window (typically ~30‑60 minutes). Once the window passes, you can claim the tokens on Ethereum via the RootChainManager’s
processExitfunction.const processTx = await rootChainManager.processExit(TOKEN_ADDRESS, signer.address, AMOUNT, "0x"); await processTx.wait(); console.log("Process exit tx hash:", processTx.hash);
Expected checks
- On Etherscan, the deposit transaction shows a call to
depositERC20on the Predicate contract with status “Success”. - On Polygonscan, the mint event (usually
Transferfrom the zero address to your address) appears for the same token contract address. - The withdrawal exit transaction on Polygonscan shows a call to
exitERC20on the RootChainManager. - After the challenge window, a
processExittransaction on Etherscan releases the original token to your Ethereum address, and your Ethereum balance increases by the withdrawn amount (minus gas).
Recovery options
- If the deposit transaction fails due to insufficient gas, increase the gas limit or resubmit with a higher fee.
- If the Polygon mint does not appear after the expected window, verify that the deposit transaction was finalized on Ethereum (check its confirmation count) and that you are querying the correct token contract on Polygon (the bridged version may have a different address; use the bridge’s mapping).
- If the exit transaction reverts because of an invalid proof, obtain the correct proof from the bridge’s off‑chain API or use the Polygon SDK’s
withdrawExithelper which constructs the proof automatically. - Should the challenge period expire without a successful
processExit, you can re‑submit theprocessExitcall; it is idempotent and will succeed once the window is over.
Limitations
- Withdrawals are not instant; plan for the checkpoint delay plus challenge window in any application logic.
- Large amounts may affect liquidity on Polygon DEXes, causing slippage.
- Always verify you are interacting with the official contract addresses; phishing clones exist.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.