Pallet Precompile

Use this precompile to interact with the nft runtime pallet to create an ERC-721 collection.

Contract Address

0x00000000000000000000000000000000000006b9

Solidity Interfaces

interface TRNNFT {
  event InitializeCollection(address indexed collectionOwner, address precompileAddress);
  function initializeCollection(address owner, bytes calldata name, uint32 maxIssuance, bytes calldata metadataPath, address[] calldata royaltyAddresses, uint32[] calldata royaltyEntitlements) external returns (address, uint32);
}

Token Precompile

Contract Address

The ERC-721 contract address for The Root Network native non-fungible token uses the following format:

0xAAAAAAAA[4-byte-collection-id]000000000000000000000000

To make things easier, the @therootnetwork/evm provides a handy function to convert token asset ID to its equivalent contract address.

import { collectionIdToERC721Address } from "@therootnetwork/evm";

const COLLECTION_ID = 1;
const COLLECTION_CONTRACT_ADDRESS = collectionIdToERC721Address(COLLECTION_ID);

Solidity Interfaces

interface IERC165 {
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
interface IERC721 is IERC165 {
  event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
  event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
  event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

  function balanceOf(address owner) external view returns (uint256 balance);
  function ownerOf(uint256 tokenId) external view returns (address owner);
  function safeTransferFrom(address from, address to, uint256 tokenId) external;
  function transferFrom(address from, address to, uint256 tokenId) external;
  function approve(address to, uint256 tokenId) external;
  function getApproved(uint256 tokenId) external view returns (address operator);
  function setApprovalForAll(address operator, bool _approved) external;
  function isApprovedForAll(address owner, address operator) external view returns (bool);
  function safeTransferFrom(address from,address to,uint256 tokenId,bytes calldata data) external;
}
interface IERC721Metadata is IERC721 {
    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function tokenURI(uint256 tokenId) external view returns (string memory);
}
interface IERC721Burnable is IERC721 {
    function burn(uint256 tokenId) external;
}
interface TRN721 is IERC165 {
    event MaxSupplyUpdated(uint32 maxSupply);
    event BaseURIUpdated(string baseURI);
    event PublicMintToggled(bool indexed enabled);
    event MintFeeUpdated(address indexed paymentAsset, uint256 indexed mintFee);

    function totalSupply() external view returns (uint256);
    function mint(address owner, uint32 quantity) external;
    function setMaxSupply(uint32 maxSupply) external;
    function setBaseURI(bytes calldata baseURI) external;
    function ownedTokens(address who, uint16 limit, uint32 cursor) external view returns (uint32, uint32, uint32[] memory);
    function togglePublicMint(bool enabled) external;
    function setMintFee(address paymentAsset, uint256 mintFee) external;
}
interface IERC5484 is IERC165 {
  enum BurnAuth { IssuerOnly, OwnerOnly, Both, Neither };

  event Issued(address indexed from, address indexed to, uint256 indexed tokenId, BurnAuth burnAuth);

  function burnAuth(uint256 tokenId) external view returns (BurnAuth);
}
interface Ownable is IERC165 {
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    function owner() external view returns (address);
    function renounceOwnership() external;
    function transferOwnership(address owner) external;
}

Soulbound Tokens

The ERC721 precompile supports Soulbound token functionality and implements the IERC5484 interface.

enum BurnAuth { IssuerOnly, OwnerOnly, Both, Neither };

Soulbound token ABI

    "event PendingIssuanceCreated(address indexed to, uint256 issuanceId, uint256 quantity, uint8 burnAuth)",
    "event Issued(address indexed from, address indexed to, uint256 indexed tokenId, uint8 burnAuth)",

    "function issueSoulbound(address,uint32,uint8)",
    "function acceptSoulboundIssuance(uint32)",
    "function pendingIssuances(address) external view returns (uint256[] issuanceIds memory, (uint256 quantity, uint8 burnAuth)[] issuances memory)",
    "function burnAuth(uint256) external view returns (uint8)",

The minting of Soulbound tokens is a two-step process. The collection owner creates a pending issuance via issueSoulbound, and states the burn authority on this call. The token is only minted once the token owner calls acceptSoulbound.

// create a pending issuance as the collection owner
await erc721Precompile
    .connect(collectionOwner)
    .issueSoulbound(
        tokenOwner.address,     // token owner
        5,                      // quantity
        BurnAuth.Both           // burn authority
    )
    .then(tx => tx.wait());

// query all pending issuances for the token owner
const [issuanceIds, issuances] = await erc721Precompile.pendingIssuances(tokenOwner.address);

// accept pending issuance as token owner
await erc721Precompile
    .connect(tokenOwner)
    .acceptSoulboundIssuance(issuanceIds[0])
    .then(tx => tx.wait());