Ga naar hoofdinhoud

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

ConceptDescription
CollectionA deployed ERC-721 contract representing a set of related tokens (e.g., MonoLands Land, Mono NFT)
TokenAn individual NFT within a collection, identified by its tokenId
MetadataA JSON document describing the token's name, description, and image, referenced by the tokenURI
TransferMoving 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:

  1. Contract introspection -- Query balanceOf, name, symbol, and supportsInterface(0x780e9d63) for each known collection contract.
  2. Token enumeration -- For Enumerable contracts with a balance greater than zero, call tokenOfOwnerByIndex(address, idx) to list individual token IDs.
  3. URI resolution -- Call tokenURI(tokenId) for each discovered token.
  4. Metadata fetch -- HTTP fetch the metadata JSON (cached with 5-minute stale time).
ERC721Enumerable Detection

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)

AddressNameEnumerable
0x72021ba0835622076987E712A7C3f0bCA8A28f55Mono NFTYes
0x8B21A8F716D9cadc3311a5B02C342aB51f26B926Test NFTYes
0xEAC0277359e2e219D9997ff83E5E42d37618f6E8Test NFT 2Yes
0xc88eF5306895A5C80B31F8E41196C8c27B16e45CMonoLands LandNo

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 indexing Transfer events from the contract.
  • Coordinate-based IDs -- Each tokenId encodes 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).
Viewing MonoLands Land NFTs

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:

SchemePurpose
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:

FieldMax LengthNotes
name200 charactersToken display name
description1,000 charactersToken description
image2,000 charactersImage 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 balanceOf values.
  • 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.

CSP Configuration

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.

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

InterfaceIDDescription
ERC-1650x01ffc9a7Standard interface detection
ERC-7210x80ac58cdNon-fungible token standard
ERC-721 Metadata0x5b5e139ftokenURI and name/symbol
ERC-721 Enumerable0x780e9d63Token enumeration by index
Always Check Before Enumerating

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.