Skip to main content

ERC721 NFT

This guide shows how to deploy an ERC721 NFT contract to Telos testnet with Hardhat 3 and the current OpenZeppelin contracts package.

Prerequisites

  • Node.js 22 or newer
  • A Telos testnet wallet with testnet TLOS for gas
  • A hosted NFT metadata JSON URI, such as an ipfs://... URI
caution

Never commit private keys. Store them in .env, add .env to .gitignore, and use a funded testnet wallet while learning.

Create the project

mkdir telos_nft_minter
cd telos_nft_minter
npm init -y
npm pkg set type=module
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox-mocha-ethers dotenv @openzeppelin/contracts
mkdir contracts scripts

Configure Hardhat

Create hardhat.config.ts:

hardhat.config.ts
import "dotenv/config";
import { defineConfig } from "hardhat/config";
import hardhatToolboxMochaEthers from "@nomicfoundation/hardhat-toolbox-mocha-ethers";

const telosPrivateKey = process.env.TELOS_PRIVATE_KEY;
const telosTestnetPrivateKey = process.env.TELOS_TESTNET_PRIVATE_KEY;

export default defineConfig({
plugins: [hardhatToolboxMochaEthers],
solidity: {
version: "0.8.28",
},
networks: {
telos: {
type: "http",
url: "https://rpc.telos.net",
chainId: 40,
accounts: telosPrivateKey ? [telosPrivateKey] : [],
},
telosTestnet: {
type: "http",
url: "https://rpc.testnet.telos.net",
chainId: 41,
accounts: telosTestnetPrivateKey ? [telosTestnetPrivateKey] : [],
},
},
});

Create .env and add your deployment private key:

.env
TELOS_TESTNET_PRIVATE_KEY=0xYOUR_TESTNET_PRIVATE_KEY
TELOS_PRIVATE_KEY=0xYOUR_MAINNET_PRIVATE_KEY

Add .env to .gitignore:

printf "\n.env\n" >> .gitignore

Add the ERC721 contract

Create contracts/TelosNFTMinter.sol:

contracts/TelosNFTMinter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {ERC721URIStorage, ERC721} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";

contract TelosNFTMinter is ERC721URIStorage {
uint256 private _nextTokenId;

constructor() ERC721("TelosNFT", "SURF") {}

function mintNFT(string memory tokenURI) public returns (uint256) {
uint256 tokenId = _nextTokenId;
_nextTokenId++;

_safeMint(msg.sender, tokenId);
_setTokenURI(tokenId, tokenURI);

return tokenId;
}
}

This example uses a simple uint256 counter. Older tutorials may use OpenZeppelin Counters, but that utility is not included in current OpenZeppelin Contracts releases.

Compile the contract:

npx hardhat compile

Test deployment locally

Create scripts/deploy.ts:

scripts/deploy.ts
import { network } from "hardhat";

const metadataUri = "ipfs://REPLACE_WITH_YOUR_METADATA_CID";
const { ethers } = await network.create();

const nft = await ethers.deployContract("TelosNFTMinter");
await nft.waitForDeployment();

const tx = await nft.mintNFT(metadataUri);
await tx.wait();

console.log(`TelosNFTMinter deployed to ${await nft.getAddress()}`);
console.log("Minted token 0");

Run it on the local Hardhat network first:

npx hardhat run scripts/deploy.ts

Expected result:

TelosNFTMinter deployed to 0x...
Minted token 0

Deploy to Telos testnet

Replace ipfs://REPLACE_WITH_YOUR_METADATA_CID in scripts/deploy.ts with your NFT metadata URI, then deploy:

npx hardhat --network telosTestnet run scripts/deploy.ts

To deploy to Telos mainnet, use:

npx hardhat --network telos run scripts/deploy.ts

Verify the contract

Verify the testnet deployment with Sourcify:

npx hardhat --network telosTestnet verify sourcify DEPLOYED_CONTRACT_ADDRESS

Verify the mainnet deployment with Sourcify:

npx hardhat --network telos verify sourcify DEPLOYED_CONTRACT_ADDRESS

The TelosNFTMinter constructor has no arguments, so the verify command only needs the deployed address.

View the NFT contract

Paste the deployed contract address into the explorer to inspect transactions, source verification, and token activity.