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.