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.