Skip to main content

Deploying an ERC-721 NFT Collection

Standard ERC-721 works unchanged on OXN. This guide covers the basics plus patterns unique to confidential-chain NFTs.

Standard ERC-721​

contracts/MyNFT.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MyNFT is ERC721, Ownable {
uint256 private _nextTokenId;

constructor(address initialOwner)
ERC721("MyNFT", "MNFT")
Ownable(initialOwner)
{}

function safeMint(address to) external onlyOwner {
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
}
}

Deploy with gasLimit: 3_000_000n — see Deploying Contracts.

Confidential metadata​

Standard NFTs store tokenURI publicly. On OXN, you can hide the metadata:

contract PrivateNFT is ERC721, Ownable {
mapping(uint256 => string) private _tokenURIs;
mapping(uint256 => mapping(address => bool)) private _readApprovals;

function _setTokenURI(uint256 tokenId, string memory uri) internal {
_tokenURIs[tokenId] = uri;
}

function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
require(_ownerOf(tokenId) != address(0), "nonexistent");
require(
msg.sender == _ownerOf(tokenId) || _readApprovals[tokenId][msg.sender],
"not authorized to view"
);
return _tokenURIs[tokenId];
}
}

Only the token owner (or an approved reader) can retrieve the metadata URI. Everyone else sees a revert.

Reveal mechanics​

For NFTs where metadata should be hidden until a reveal date:

uint256 public revealTime;

function tokenURI(uint256 tokenId) public view override returns (string memory) {
require(_ownerOf(tokenId) != address(0), "nonexistent");
if (block.timestamp < revealTime) {
return "ipfs://prereveal"; // placeholder image
}
return _tokenURIs[tokenId];
}

Before revealTime, everyone sees the placeholder. After, actual URIs are returned (still gated by ownership if desired).

Blind mint​

Mint tokens whose metadata is only revealed when the buyer requests to see:

mapping(uint256 => bytes) private _encryptedMetadata;

function mint(address to, bytes calldata encryptedURI) external onlyOwner {
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
_encryptedMetadata[tokenId] = encryptedURI; // stored encrypted
}

function reveal(uint256 tokenId) external view returns (bytes memory) {
require(msg.sender == _ownerOf(tokenId), "not owner");
return _encryptedMetadata[tokenId];
}

Full example​

See the OXN sample repository for a runnable Hardhat project.

Next steps​