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.