ERC20 Deposit and Withdraw Guide

High Level Flow

Depositing ERC20 -

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

Withdrawing ERC20 -

  1. Burn tokens on matic 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

Approve ERC20Predicate to spend tokens by calling the approve function of token contract. This function takes two arguments spender and amount. spender is the address that is being approval to spend user's tokens. amount is the amount of tokens that can be spent. Keep amount equal to deposit amount for one time approval or pass a bigger number to avoid approving multiple times.

await rootTokenContract.methods
.approve(erc20Predicate, amount)
.send({ from: userAddress })

Deposit

Note that token needs to be mapped and amount 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 matic chain. rootToken is the address of token on main chain. depositData is abi encoded amount.

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

Burn

Tokens can be burned on matic chain by calling the withdraw function on child token contract. This function takes a single argument, amount indicating the number of tokens 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
.withdraw(amount)
.send({ from: userAddress })
const burnTxHash = burnTx.transactionHash

Exit

Exit function on RootChainManager contract has to be called to unlock and receive the tokens back from ERC20Predicate. This function takes a single bytes argument that proves the burn transaction. Wait for the checkpoint containing burn tenasaction to be submitted before calling this function. The Proof is generated by RLP ecoding following fields -

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

Generating proof manually can be tricky so it is advisable to use the matic SDK. If you want to send the transaction manually, you can pass encodeAbi as true in the options object to get raw calldata.

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

Send this calldata to RootChainManager.

await mainWeb3.eth.sendTransaction({
from: userAddress,
to: rootChainManagerAddress,
data: exitCalldata
})