NFTs
MonoHub provides native ERC-721 NFT support on the Monolythium network. The NFT system is live on Sprintnet and enables users to view, manage, and transfer non-fungible tokens through both the MonoHub web interface and Monolythium wallets.
How NFTs Work on Monolythium
Monolythium's EVM layer supports the full ERC-721 standard. NFT collections are deployed as standard Solidity contracts, and tokens within each collection are uniquely identified by a tokenId. The MonoHub frontend discovers and displays NFTs through on-chain multicall queries -- no centralized metadata server is required for basic functionality.
Core Concepts
| Concept | Description |
|---|---|
| Collection | A deployed ERC-721 contract representing a set of related tokens (e.g., MonoLands Land, Mono NFT) |
| Token | An individual NFT within a collection, identified by its tokenId |
| Metadata | A JSON document describing the token's name, description, and image, referenced by the tokenURI |
| Transfer | Moving ownership of a token from one address to another via safeTransferFrom |
On-Chain Discovery
MonoHub uses a 4-step multicall waterfall to discover NFTs for a given wallet address:
- Contract introspection -- Query
balanceOf,name,symbol, andsupportsInterface(0x780e9d63)for each known collection contract. - Token enumeration -- For Enumerable contracts with a balance greater than zero, call
tokenOfOwnerByIndex(address, idx)to list individual token IDs. - URI resolution -- Call
tokenURI(tokenId)for each discovered token. - Metadata fetch -- HTTP fetch the metadata JSON (cached with 5-minute stale time).
Not all ERC-721 contracts implement the ERC721Enumerable extension. You must check supportsInterface(0x780e9d63) before calling tokenOfOwnerByIndex. Calling enumeration methods on a non-enumerable contract will revert.
The MonoLandsLand contract is a notable example -- it is ERC-721 compliant but does not implement Enumerable. MonoHub can detect a wallet's balance for non-enumerable contracts but cannot list individual tokens without an indexer.
Known NFT Contracts (Sprintnet)
| Address | Name | Enumerable |
|---|---|---|
0x72021ba0835622076987E712A7C3f0bCA8A28f55 | Mono NFT | Yes |
0x8B21A8F716D9cadc3311a5B02C342aB51f26B926 | Test NFT | Yes |
0xEAC0277359e2e219D9997ff83E5E42d37618f6E8 | Test NFT 2 | Yes |
0xc88eF5306895A5C80B31F8E41196C8c27B16e45C | MonoLands Land | No |
For non-enumerable contracts, the UI shows the token count and a link to Monoscan where individual tokens can be explored via Transfer event history.
MonoLandsLand Integration
The MonoLandsLand contract is a coordinate-indexed ERC-721 where each token represents a land parcel in the MonoLands game world. Token IDs encode the planet and coordinate data for the claimed parcel.
Key characteristics:
- Not Enumerable -- Does not implement
ERC721Enumerable. Token discovery requires indexingTransferevents from the contract. - Coordinate-based IDs -- Each
tokenIdencodes spatial coordinates, making it possible to map tokens to in-game locations. - Treasury hardcoded -- The contract has the treasury address hardcoded at deployment. Mainnet deployment requires redeploying with the mainnet Contracts Treasury address (
0x70773914287A8D92F732296bFa8866217aF13BeD).
Since MonoLandsLand is not enumerable, MonoHub displays the number of land parcels owned but cannot list them individually in the frontend. Use the Monoscan explorer to view individual land tokens by checking Transfer events for your address.
URI Security
All NFT metadata URIs are validated and sanitized before rendering. This prevents malicious metadata from executing scripts or loading unauthorized resources.
Allowed URI Schemes
Only the following URI schemes are permitted for tokenURI values and metadata image fields:
| Scheme | Purpose |
|---|---|
https:// | Standard HTTPS resources |
ipfs:// | IPFS content-addressed resources |
data: | Inline data URIs (e.g., on-chain SVG images) |
URIs using http://, javascript:, or any other scheme are rejected and the token metadata is treated as unavailable.
Metadata Sanitization
The sanitizeMetadata() function extracts only safe fields from fetched metadata JSON:
| Field | Max Length | Notes |
|---|---|---|
name | 200 characters | Token display name |
description | 1,000 characters | Token description |
image | 2,000 characters | Image URI (validated against allowed schemes) |
Additional protections:
- 100 KB response body limit -- Metadata JSON responses larger than 100 KB are rejected before parsing.
- 50 NFT cap per collection -- Prevents denial-of-service from contracts with inflated
balanceOfvalues. - Image error fallback -- Broken or unreachable images display a placeholder rather than a broken image icon.
Content Security Policy (CSP)
MonoHub's CSP headers must explicitly whitelist IPFS gateways for NFT metadata and images to load correctly.
IPFS gateways must be listed in both connect-src (for metadata JSON fetches) and img-src (for image rendering). Using a wildcard https: in img-src is not permitted -- only specific trusted domains are allowed.
Required CSP Entries
connect-src (for metadata JSON fetch):
ipfs.io
cloudflare-ipfs.com
w3s.link
img-src (for NFT images):
ipfs.io
cloudflare-ipfs.com
w3s.link
If your application uses additional IPFS gateways or pinning services, add them to both directives. The api.qrserver.com domain used elsewhere in MonoHub must also remain in img-src to avoid breaking QR code rendering.
Wallet Integration
Browser Wallet -- NFT Tab
The MonoHub browser interface includes an NFT tab in the wallet view. Features:
- Collection filter -- Dropdown populated dynamically from on-chain data (not hardcoded).
- Responsive grid -- Image cards with skeleton loading states.
- Chain-switch reset -- Filters reset when the user switches networks.
- Non-enumerable notice -- For contracts that do not support enumeration, a notice displays the count with a link to Monoscan.
Desktop Wallet -- NFT Gallery Plugin
The Desktop Wallet's plugin system (v0.2.0) supports an NFT Gallery plugin that follows the same architecture as the Validator Staking plugin. See the Wallet Plugins page for details on the plugin system.
Indexer API Endpoints
The MonoHub indexer provides REST endpoints for querying NFT data. These endpoints aggregate on-chain NFT data with cached metadata for faster queries.
GET /api/nfts/wallet/:address
Returns all NFTs owned by the specified wallet address.
{
"address": "0x366d8135D7413C09564044D345A245771c9BaC5B",
"nfts": [
{
"contract": "0x72021ba0835622076987E712A7C3f0bCA8A28f55",
"tokenId": "1",
"name": "Mono NFT #1",
"description": "A Monolythium NFT",
"image": "ipfs://QmExample...",
"collection": "Mono NFT"
}
],
"total": 1
}
GET /api/nfts/collections
Returns all known NFT collections with metadata and token counts.
{
"collections": [
{
"address": "0x72021ba0835622076987E712A7C3f0bCA8A28f55",
"name": "Mono NFT",
"symbol": "MNFT",
"totalSupply": 100,
"isEnumerable": true
}
]
}
GET /api/nfts/:contract/:tokenId
Returns metadata for a specific NFT.
{
"contract": "0x72021ba0835622076987E712A7C3f0bCA8A28f55",
"tokenId": "1",
"owner": "0x366d8135D7413C09564044D345A245771c9BaC5B",
"name": "Mono NFT #1",
"description": "A Monolythium NFT",
"image": "ipfs://QmExample...",
"tokenURI": "ipfs://QmExampleURI..."
}
Developing with NFTs
Minimal ERC-721 ABI
MonoHub exports a minimal ABI for interacting with NFT contracts:
const ERC721_ABI = [
// Read methods
"function balanceOf(address owner) view returns (uint256)",
"function ownerOf(uint256 tokenId) view returns (address)",
"function tokenURI(uint256 tokenId) view returns (string)",
"function name() view returns (string)",
"function symbol() view returns (string)",
"function supportsInterface(bytes4 interfaceId) view returns (bool)",
// Enumerable (check supportsInterface first)
"function tokenOfOwnerByIndex(address owner, uint256 index) view returns (uint256)",
// Transfer
"function safeTransferFrom(address from, address to, uint256 tokenId)",
] as const;
Interface IDs
| Interface | ID | Description |
|---|---|---|
| ERC-165 | 0x01ffc9a7 | Standard interface detection |
| ERC-721 | 0x80ac58cd | Non-fungible token standard |
| ERC-721 Metadata | 0x5b5e139f | tokenURI and name/symbol |
| ERC-721 Enumerable | 0x780e9d63 | Token enumeration by index |
Call supportsInterface(0x780e9d63) before attempting any tokenOfOwnerByIndex or tokenByIndex calls. This prevents reverts and allows your code to gracefully fall back to event-based discovery for non-enumerable contracts.
Related
- Contract Addresses -- Deployed contract addresses per chain
- Trading -- MonoSwap DEX
- Wallet Plugins -- Desktop Wallet NFT Gallery plugin
- Monoscan Explorer -- View NFT transfers and contract state