Skip to main content

Use case - ERC20 Bridge

This section aims to give you a setup flow of ERC20 Bridge for a practical use case.

In this guide, you will use Mumbai Polygon PoS testnet and Polygon Edge local chain. Please make sure you have JSON-RPC endpoint for Mumbai and you've set up Polygon Edge in local environment. Please refer to Local Setup or Cloud Setup for more details.

Scenario

This scenario is to setup a Bridge for the ERC20 token that has been deployed in public chain (Polygon PoS) already in order to enable low-cost transfer in a private chain (Polygon Edge) for users in a regular case. In such a case, the total supply of token has been defined in the public chain and only the amount of the token which has been transferred from the public chain to the private chain must exist in the private chain. For that reason, you'll need to use lock/release mode in the public chain and burn/mint mode in the private chain.

When sending tokens from the public chain to the private chain, the token will be locked in ERC20 Handler contract of the public chain and the same amount of token will be minted in the private chain. On the other hand, in case of transfer from the private chain to the public chain, the token in the private chain will be burned and the same amount of token will be released from ERC20 Handler contract in the public chain.

Contracts

Explaining with a simple ERC20 contracts instead of the contract developed by ChainBridge. For burn/mint mode, ERC20 contract must have mint and burnFrom methods in addition to the methods for ERC20 like this:

pragma solidity ^0.8.14;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

contract SampleToken is ERC20, AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");

constructor(string memory name, string memory symbol) ERC20(name, symbol) {
_setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
_setupRole(MINTER_ROLE, _msgSender());
_setupRole(BURNER_ROLE, _msgSender());
}

function mint(address recipient, uint256 amount)
external
onlyRole(MINTER_ROLE)
{
_mint(recipient, amount);
}

function burnFrom(address owner, uint256 amount)
external
onlyRole(BURNER_ROLE)
{
_burn(owner, amount);
}
}

All codes and scripts are in Github Repo Trapesys/chainbridge-example.

Step1: Deploy Bridge and ERC20 Handler contracts

Firstly, you'll deploy Bridge and ERC20Handler contracts using cb-sol-cli in the both chains.

# Deploy Bridge and ERC20 contracts in Polygon PoS chain
$ cb-sol-cli deploy --bridge --erc20Handler --chainId 99 \
--url https://rpc-mumbai.matic.today \
--privateKey [ADMIN_ACCOUNT_PRIVATE_KEY] \
--gasPrice [GAS_PRICE] \
--relayers [RELAYER_ACCOUNT_ADDRESS] \
--relayerThreshold 1
# Deploy Bridge and ERC20 contracts in Polygon Edge chain
$ cb-sol-cli deploy --bridge --erc20Handler --chainId 100 \
--url http://localhost:10002 \
--privateKey [ADMIN_ACCOUNT_PRIVATE_KEY] \
--relayers [RELAYER_ACCOUNT_ADDRESS] \
--relayerThreshold 1

You'll get Bridge and ERC20Handler contract addresses like this:

Deploying contracts...
✓ Bridge contract deployed
✓ ERC20Handler contract deployed

================================================================
Url: https://rpc-mumbai.matic.today
Deployer: <ADMIN_ACCOUNT_ADDRESS>
Gas Limit: 8000000
Gas Price: 20000000
Deploy Cost: 0.00029065308

Options
=======
Chain Id: <CHAIN_ID>
Threshold: <RELAYER_THRESHOLD>
Relayers: <RELAYER_ACCOUNT_ADDRESS>
Bridge Fee: 0
Expiry: 100

Contract Addresses
================================================================
Bridge: <BRIDGE_CONTRACT_ADDRESS>
----------------------------------------------------------------
Erc20 Handler: <ERC20_HANDLER_CONTRACT_ADDRESS>
----------------------------------------------------------------
Erc721 Handler: Not Deployed
----------------------------------------------------------------
Generic Handler: Not Deployed
----------------------------------------------------------------
Erc20: Not Deployed
----------------------------------------------------------------
Erc721: Not Deployed
----------------------------------------------------------------
Centrifuge Asset: Not Deployed
----------------------------------------------------------------
WETC: Not Deployed
================================================================

Step2: Deploy your ERC20 contract

You'll deploy your ERC20 contract. This example guides you with hardhat project Trapesys/chainbridge-example.

$ git clone https://github.com/Trapesys/chainbridge-example.git
$ cd chainbridge-example
$ npm i

Please create .env file and set the following values.

PRIVATE_KEYS=0x...
MUMBAI_JSONRPC_URL=https://rpc-mumbai.matic.today
EDGE_JSONRPC_URL=http://localhost:10002

Next you'll deploy ERC20 contract in the both chains.

$ npx hardhat deploy --contract erc20 --name <ERC20_TOKEN_NAME> --symbol <ERC20_TOKEN_SYMBOL> --network mumbai
$ npx hardhat deploy --contract erc20 --name <ERC20_TOKEN_NAME> --symbol <ERC20_TOKEN_SYMBOL> --network edge

After deployment is successful, you'll get a contract address like this:

ERC20 contract has been deployed
Address: <ERC20_CONTRACT_ADDRESS>
Name: <ERC20_TOKEN_NAME>
Symbol: <ERC20_TOKEN_SYMBOL>

Step3: Register resource ID in Bridge

You will register a resource ID that associates resource in a cross-chain environment. You need to set the same resource ID in the both chain.

$ cb-sol-cli bridge register-resource \
--url https://rpc-mumbai.matic.today \
--privateKey [ADMIN_ACCOUNT_PRIVATE_KEY] \
--gasPrice [GAS_PRICE] \
--resourceId "0x000000000000000000000000000000c76ebe4a02bbc34786d860b355f5a5ce00" \
--bridge "[BRIDGE_CONTRACT_ADDRESS]" \
--handler "[ERC20_HANDLER_CONTRACT_ADDRESS]" \
--targetContract "[ERC20_CONTRACT_ADDRESS]"
$ cb-sol-cli bridge register-resource \
--url http://localhost:10002 \
--privateKey [ADMIN_ACCOUNT_PRIVATE_KEY] \
--resourceId "0x000000000000000000000000000000c76ebe4a02bbc34786d860b355f5a5ce00" \
--bridge "[BRIDGE_CONTRACT_ADDRESS]" \
--handler "[ERC20_HANDLER_CONTRACT_ADDRESS]" \
--targetContract "[ERC20_CONTRACT_ADDRESS]"

Step4: Set Mint/Burn mode in ERC20 bridge of the Edge

Bridge expects to work as burn/mint mode in Polygon Edge. You'll set burn/mint mode using cb-sol-cli.

$ cb-sol-cli bridge set-burn \
--url http://localhost:10002 \
--privateKey [ADMIN_ACCOUNT_PRIVATE_KEY] \
--bridge "[BRIDGE_CONTRACT_ADDRESS]" \
--handler "[ERC20_HANDLER_CONTRACT_ADDRESS]" \
--tokenContract "[ERC20_CONTRACT_ADDRESS]"

And you need to grant a minter and burner role to the ERC20 Handler contract.

$ npx hardhat grant --role mint --contract [ERC20_CONTRACT_ADDRESS] --address [ERC20_HANDLER_CONTRACT_ADDRESS] --network edge
$ npx hardhat grant --role burn --contract [ERC20_CONTRACT_ADDRESS] --address [ERC20_HANDLER_CONTRACT_ADDRESS] --network edge

Step5: Mint Token

You'll mint new ERC20 tokens in Mumbai chain.

$ npx hardhat mint --type erc20 --contract [ERC20_CONTRACT_ADDRESS] --address [ACCOUNT_ADDRESS] --amount 100000000000000000000 --network mumbai # 100 Token

After the transaction is successful, the account will have the minted token.

Step6: Start ERC20 transfer

Before starting this step, please make sure that you've started a relayer. Please check Setup for more details.

During token transfer from Mumbai to Edge, ERC20 Handler contract in Mumbai withdraws tokens from your account. You'll call approve before transfer.

$ npx hardhat approve --type erc20 --contract [ERC20_CONTRACT_ADDRESS] --address [ERC20_HANDLER_CONTRACT_ADDRESS] --amount 10000000000000000000 --network mumbai # 10 Token

Finally, you'll start token transfer from Mumbai to Edge using cb-sol-cli.

# Start transfer from Mumbai to Polygon Edge chain
$ cb-sol-cli erc20 deposit \
--url https://rpc-mumbai.matic.today \
--privateKey [PRIVATE_KEY] \
--gasPrice [GAS_PRICE] \
--amount 10 \
# ChainID of Polygon Edge chain
--dest 100 \
--bridge "[BRIDGE_CONTRACT_ADDRESS]" \
--recipient "[RECIPIENT_ADDRESS_IN_POLYGON_EDGE_CHAIN]" \
--resourceId "0x000000000000000000000000000000c76ebe4a02bbc34786d860b355f5a5ce00"

After the deposit transaction is successful, the relayer will get the event and vote for the proposal. It executes a transaction to send tokens to the recipient account in the Polygon Edge chain after the required number of votes are submitted.

INFO[11-19|08:15:58] Handling fungible deposit event          chain=mumbai dest=100 nonce=1
INFO[11-19|08:15:59] Attempting to resolve message chain=polygon-edge type=FungibleTransfer src=99 dst=100 nonce=1 rId=000000000000000000000000000000c76ebe4a02bbc34786d860b355f5a5ce00
INFO[11-19|08:15:59] Creating erc20 proposal chain=polygon-edge src=99 nonce=1
INFO[11-19|08:15:59] Watching for finalization event chain=polygon-edge src=99 nonce=1
INFO[11-19|08:15:59] Submitted proposal vote chain=polygon-edge tx=0x67a97849951cdf0480e24a95f59adc65ae75da23d00b4ab22e917a2ad2fa940d src=99 depositNonce=1 gasPrice=1
INFO[11-19|08:16:24] Submitted proposal execution chain=polygon-edge tx=0x63615a775a55fcb00676a40e3c9025eeefec94d0c32ee14548891b71f8d1aad1 src=99 dst=100 nonce=1 gasPrice=5

Once the execution transaction is successful, you will get tokens in the Polygon Edge chain.