ER721 Deposit and Withdraw Guide

High Level Flow

Depositing ERC721 -

  1. Approve ERC721Predicate contract to spend the tokens that have to be deposited.
  2. Make depositFor call on RootChainManager.

Withdrawing ERC721 -

  1. Burn tokens on Polygon chain.
  2. Call exit function on RootChainManager to submit proof of burn transaction. This call can be made after checkpoint is submitted for the block containing burn transaction.

Step Details

Instantiate the contracts

const mainWeb3 = new Web3(mainProvider)
const maticWeb3 = new Web3(maticProvider)
const rootTokenContract = new mainWeb3.eth.Contract(rootTokenABI, rootTokenAddress)
const rootChainManagerContract = new mainWeb3.eth.Contract(rootChainManagerABI, rootChainManagerAddress)
const childTokenContract = new maticWeb3(childTokenABI, childTokenAddress)


Approve ERC721Predicate to spend tokens by calling the approve function of token contract. This function takes two arguments spender and tokenId. spender is the address that is being approval to spend user's tokens. tokenId is the id of token that can be spent. You can also call the setApprovalForAll function if you want to do multiple deposits.

await rootTokenContract.methods
.approve(erc721Predicate, tokenId)
.send({ from: userAddress })


Note that token needs to be mapped and tokenId has to be approved for deposit before making this call.
Call the depositFor function of RootChainManager contract. This function takes 3 arguments user, rootToken and depositData. user is the address of user that will receive the deposit on Polygon chain. rootToken is the address of token on main chain. depositData is abi encoded tokenId.

const depositData = mainWeb3.eth.abi.encodeParameter('uint256', tokenId)
await rootChainManagerContract.methods
.depositFor(userAddress, rootToken, depositData)
.send({ from: userAddress })


Tokens can be burned on matic chain by calling the withdraw function on child token contract. This function takes a single argument, tokenId indicating the token to be burned. Proof of this burn needs to be submitted in the exit step. So store the transaction hash.

const burnTx = await childTokenContract.methods
.send({ from: userAddress })
const burnTxHash = burnTx.transactionHash


Completing the cycle means calling the exit function on the RootChainManager contract which unlocks and receives the tokens back from the ERC20Predicate. This function takes a single byte argument from the withdraw function and uses it as proof of burn for the transaction. Its important that we wait for the checkpoint containing the burn transaction to be submitted before calling this function. The proof of burn us generated by RLP encoding the following fields:

  • headerNumber - Checkpoint header block number containing the burn transaction
  • blockProof - Proof that the block header (in the child chain) is a leaf in the submitted merkle root
  • blockNumber - Block number containing the burn transaction on child chain
  • blockTime - Burn transaction block time
  • txRoot - Transactions root of block
  • receiptRoot - Receipts root of block
  • receipt - Receipt of the burn transaction
  • receiptProof - Merkle proof of the burn receipt
  • branchMask - 32 bits denoting the path of receipt in merkle patricia tree
  • receiptLogIndex - Log Index to read from the receipt

Generating all this information manually has historically proven to be tricky, so we advise you use the matic.js SDK. If you're still interested in sending the transaction manually, please pass encodeAbi as true in the options object to get raw call data.

const exitCalldata = await maticPOSClient
.exitERC721(burnTxHash, { from, encodeAbi: true })

Send this calldata to RootChainManager.

await mainWeb3.eth.sendTransaction({
from: userAddress,
to: rootChainManagerAddress,
Last updated on