Skip to main content

Use case - ERC721 Bridge

This section aims to give you a setup flow of ERC721 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 ERC721 NFT 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 original metadata has been defined in the public chain and the only NFTs that have been transferred from Public chain can 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 NFTs from the public chain to the private chain, the NFT will be locked in ERC721 Handler contract in the public chain and the same NFT will be minted in the private chain. On the other hand, in case of transfer from the private chain to the public chain, the NFT in the private chain will be burned and the same NFT will be released from ERC721 Handler contract in the public chain.

Contracts

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

pragma solidity ^0.8.14;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";

contract SampleNFT is ERC721, ERC721Burnable, ERC721URIStorage, AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");

string public baseURI;

constructor(
string memory name,
string memory symbol,
string memory baseURI
) ERC721(name, symbol) {
_setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
_setupRole(MINTER_ROLE, _msgSender());
_setupRole(BURNER_ROLE, _msgSender());

_setBaseURI(baseURI);
}

function mint(
address recipient,
uint256 tokenID,
string memory data
) public onlyRole(MINTER_ROLE) {
_mint(recipient, tokenID);
_setTokenURI(tokenID, data);
}

function burn(uint256 tokenID)
public
override(ERC721Burnable)
onlyRole(BURNER_ROLE)
{
_burn(tokenID);
}

function tokenURI(uint256 tokenId)
public
view
virtual
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return super.tokenURI(tokenId);
}

function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, AccessControl)
returns (bool)
{
return super.supportsInterface(interfaceId);
}

function _burn(uint256 tokenId)
internal
virtual
override(ERC721, ERC721URIStorage)
{
super._burn(tokenId);
}

function _setBaseURI(string memory baseURI_) internal {
baseURI = baseURI_;
}

function _baseURI() internal view virtual override returns (string memory) {
return baseURI;
}
}

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

Step1: Deploy Bridge and ERC721 Handler contracts

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

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

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

Deploying contracts...
✓ Bridge contract deployed
✓ ERC721Handler 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: Not Deployed
----------------------------------------------------------------
Erc721 Handler: <ERC721_HANDLER_CONTRACT_ADDRESS>
----------------------------------------------------------------
Generic Handler: Not Deployed
----------------------------------------------------------------
Erc20: Not Deployed
----------------------------------------------------------------
Erc721: Not Deployed
----------------------------------------------------------------
Centrifuge Asset: Not Deployed
----------------------------------------------------------------
WETC: Not Deployed
================================================================

Step2: Deploy your ERC721 contract

You'll deploy your ERC721 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 ERC721 contract in the both chains.

$ npx hardhat deploy --contract erc721 --name <ERC721_TOKEN_NAME> --symbol <ERC721_TOKEN_SYMBOL> --uri <BASE_URI> --network mumbai
$ npx hardhat deploy --contract erc721 --name <ERC721_TOKEN_NAME> --symbol <ERC721_TOKEN_SYMBOL> --uri <BASE_URI> --network edge

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

ERC721 contract has been deployed
Address: <ERC721_CONTRACT_ADDRESS>
Name: <ERC721_TOKEN_NAME>
Symbol: <ERC721_TOKEN_SYMBOL>
Base URI: <ERC721_BASE_URI>

Step3: Register resource ID in Bridge

You will register a resource ID that associates resources in a cross-chain environment.

$ cb-sol-cli bridge register-resource \
--url https://rpc-mumbai.matic.today \
--privateKey [ADMIN_ACCOUNT_PRIVATE_KEY] \
--gasPrice [GAS_PRICE] \
# Set Resource ID for ERC721
--resourceId "0x000000000000000000000000000000e389d61c11e5fe32ec1735b3cd38c69501" \
--bridge "[BRIDGE_CONTRACT_ADDRESS]" \
--handler "[ERC721_HANDLER_CONTRACT_ADDRESS]" \
--targetContract "[ERC721_CONTRACT_ADDRESS]"
$ cb-sol-cli bridge register-resource \
--url http://localhost:10002 \
--privateKey [ADMIN_ACCOUNT_PRIVATE_KEY] \
# Set Resource ID for ERC721
--resourceId "0x000000000000000000000000000000e389d61c11e5fe32ec1735b3cd38c69501" \
--bridge "[BRIDGE_CONTRACT_ADDRESS]" \
--handler "[ERC721_HANDLER_CONTRACT_ADDRESS]" \
--targetContract "[ERC721_CONTRACT_ADDRESS]"

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

Bridge expects to work as burn/mint mode in Edge. You'll set burn/mint mode.

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

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

$ npx hardhat grant --role mint --contract [ERC721_CONTRACT_ADDRESS] --address [ERC721_HANDLER_CONTRACT_ADDRESS] --network edge
$ npx hardhat grant --role burn --contract [ERC721_CONTRACT_ADDRESS] --address [ERC721_HANDLER_CONTRACT_ADDRESS] --network edge

Step5: Mint NFT

You'll mint new ERC721 NFT in Mumbai chain.

$ npx hardhat mint --type erc721 --contract [ERC721_CONTRACT_ADDRESS] --address [ACCOUNT_ADDRESS] --id 0x50 --data hello.json --network mumbai

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

Step6: Start ERC721 transfer

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

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

$ npx hardhat approve --type erc721 --contract [ERC721_CONTRACT_ADDRESS] --address [ERC721_HANDLER_CONTRACT_ADDRESS] --id 0x50 --network mumbai

Finally, you'll start NFT transfer from Mumbai to Edge.

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

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

INFO[11-19|09:07:50] Handling nonfungible deposit event       chain=mumbai
INFO[11-19|09:07:50] Attempting to resolve message chain=polygon-edge type=NonFungibleTransfer src=99 dst=100 nonce=2 rId=000000000000000000000000000000e389d61c11e5fe32ec1735b3cd38c69501
INFO[11-19|09:07:50] Creating erc721 proposal chain=polygon-edge src=99 nonce=2
INFO[11-19|09:07:50] Watching for finalization event chain=polygon-edge src=99 nonce=2
INFO[11-19|09:07:50] Submitted proposal vote chain=polygon-edge tx=0x58a22d84a08269ad2e8d52d8dc038621f1a21109d11c7b6e0d32d5bf21ea8505 src=99 depositNonce=2 gasPrice=1
INFO[11-19|09:08:15] Submitted proposal execution chain=polygon-edge tx=0x57419844881a07531e31667c609421662d94d21d0709e64fb728138309267e68 src=99 dst=100 nonce=2 gasPrice=3

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