HelloERC721
HelloERC721
is an ERC721
token implementation showcasing cross-chain NFT functionality without the need for traditional bridges, making the NFT cross-chain native. Utilizing the Via Labs package it is trivial to add native cross-chain functionality to any NFT contract. Below is a bare-bones ERC721
contract to showcase the ease of use.
Prerequisites
Node.js and npm
# Install using APT
sudo apt update
sudo apt install nodejs npm
Git
# Install using APT
sudo apt install git
Testnet Tokens
- Ethereum Sepolia: Ethereum Testnet Faucet
- Polygon Amoy: Polygon Testnet Faucet
Installation
Open a terminal to run the following commands. You can use any terminal of your choice, including the built in terminal in (vscode) (Terminal -> New Terminal)
- Clone the Repository:
git clone https://github.com/VIALabs-io/hello-erc721.git
- Change into Project Directory:
cd hello-erc721
- Install Dependencies:
npm install
- Set Up Environment Variables:
Create a new.env
file to set your EVM private key for contract deployment or copy and edit the existing.env.example
to.env
PRIVATE_KEY=0000000000000000000000000000
Deployment
Deploy the HelloERC721
contract to your desired networks. This must be done for each network you wish to operate on. You can see a list of our networks in the Package documentation.
- Ethereum Sepolia Testnet Deployment:
npx hardhat --network ethereum-sepolia deploy
- Polygon Testnet Deployment:
npx hardhat --network polygon-amoy deploy
Configuration
Edit the networks-testnet.json
file and include all of the networks the contract is deployed on.
[
"ethereum-sepolia",
"polygon-amoy"
]
Once all contracts are deployed across the desired networks and listed in networks-testnet.json
, configure them using the provided script. Remember, if a new network is added later, all contracts must be reconfigured.
- Ethereum Sepolia Testnet Configuration:
npx hardhat --network ethereum-sepolia configure
- Polygon Testnet Configuration:
npx hardhat --network polygon-amoy configure
Usage
Minting an NFT
To mint an NFT on a chain:
npx hardhat --network polygon-amoy mint-nft
You will get the next available NFT. NFTs start at chain-id0000 so the first NFT minted on Polygon Testnet will be 800020000 and the next 800020001 etc.. You can look up the chain ids in the Package documentation. You can also look up the transaction on the testnet explorer to see the NFT details using your wallet address.
Viewing NFT Details
To view the details of an NFT including its Metadata and Owner:
npx hardhat --network polygon-amoy get-nft ---nftid 800020000
Bridging NFTs to Another Chain
To send NFTs to another chain it is required to set the --dest
parameter to the destination chain id. The example below uses the id for the Ethereum Sepolia Testnet. Chain IDs can be looked up in the Package documentation.
npx hardhat --network polygon-amoy bridge-nft --dest 11155111 --nftid 800020000
Certainly! Here's a detailed breakdown of the HelloERC721
contract including implementation details:
Contract Breakdown of HelloERC721
HelloERC721
is an ERC721
token contract designed for cross-chain operations, leveraging Via Labs's message-passing capabilities.
Contract Overview
pragma solidity =0.8.17;
import "@vialabs-io/contracts/message/MessageClient.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "base64-sol/base64.sol";
contract HelloERC721 is ERC721, MessageClient {
uint public nextNftId;
constructor() ERC721("Hello ERC721!", "H721") {
nextNftId = block.chainid * 10**4;
}
function mint() external {
_mint(msg.sender, nextNftId);
nextNftId++;
}
function bridge(uint _destChainId, address _recipient, uint _nftId) external onlyActiveChain(_destChainId) {
require(ownerOf(_nftId) == msg.sender, "HelloERC721: caller is not the owner of the nft");
// burn nft
_burn(_nftId);
// send cross chain message
_sendMessage(_destChainId, abi.encode(_recipient, _nftId));
}
function messageProcess(uint, uint _sourceChainId, address _sender, address, uint, bytes calldata _data) external override onlySelf(_sender, _sourceChainId) {
// decode message
(address _recipient, uint _nftId) = abi.decode(_data, (address, uint));
// mint tokens
_mint(_recipient, _nftId);
}
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
return string(abi.encodePacked('data:application/json;base64,',
Base64.encode(bytes(abi.encodePacked(
'{"name":"Via Labs Hello ERC721 #', tokenId, '", "description":"Hello ERC721 cross chain NFT example. https://github.com/VIALabs-io/hello-erc721", "image":"https://i.postimg.cc/FKkpPByb/cl-logo.png"}')
)))
);
}
}
Constructor
constructor() ERC721("Hello ERC721!", "H721") {
nextNftId = block.chainid * 10**4;
}
Initializes the token with a unique name ("Hello ERC721!") and symbol ("H721"). It sets the nextNftId
based on the chain ID, ensuring unique NFT IDs across different chains.
Mint Function
function mint() external {
_mint(msg.sender, nextNftId);
nextNftId++;
}
Allows users to mint new NFTs. Each minted NFT gets a unique ID, which is then incremented for the next minting operation.
Bridge Function
function bridge(uint _destChainId, address _recipient, uint _nftId) external onlyActiveChain(_destChainId) {
require(ownerOf(_nftId) == msg.sender, "HelloERC721: caller is not the owner of the nft");
_burn(_nftId);
_sendMessage(_destChainId, abi.encode(_recipient, _nftId));
}
Enables users to bridge their NFTs to a different chain. It verifies the ownership, burns the NFT on the current chain, and sends a message to mint it on the destination chain.
Message Processing
function messageProcess(uint, uint _sourceChainId, address _sender, address, uint, bytes calldata _data) external override onlySelf(_sender, _sourceChainId) {
(address _recipient, uint _nftId) = abi.decode(_data, (address, uint));
_mint(_recipient, _nftId);
}
Processes incoming messages from other chains. It decodes the message to get the recipient's address and the NFT ID, and then mints the NFT on the current chain.
Token URI
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
return string(abi.encodePacked('data:application/json;base64,',
Base64.encode(bytes(abi.encodePacked(
'{"name":"Via Labs Hello ERC721 #', tokenId, '", "description":"Hello ERC721 cross chain NFT example. https://github.com/VIALabs-io/hello-erc721", "image":"https://i.postimg.cc/FKkpPByb/cl-logo.png"}')
)))
);
}
Generates a token URI for each NFT. The URI includes a base64-encoded JSON object with the NFT's name, description, and image URL.