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
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:
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:
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:
// 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:
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
- Testnet explorer: https://testnet.teloscan.io/
- Mainnet explorer: https://www.teloscan.io/
Paste the deployed contract address into the explorer to inspect transactions, source verification, and token activity.