ERC-5773: Context-Dependent Multi-Asset NFTs
ERC-5773 lets a single NFT carry multiple asset representations — e.g. a 3D model + a 2D image + an animation + raw metadata — each selectable by viewer context. A wallet rendering on mobile picks the 2D image; a VR headset picks the 3D model; an indexer picks the raw metadata. The contract stores all assets and exposes a priority list the viewer consults to pick the right one.
Used by:
- 3D NFTs that need both a static preview and a full mesh.
- Animated NFTs with both a still cover and the animation.
- Cross-platform game items with renders for each engine.
- AR / VR collectibles with multiple resolution / format variants.
Required Interface (abridged)
interface IERC5773 {
event AssetSet(uint64 assetId);
event AssetAddedToTokens(uint256[] tokenIds, uint64 assetId, uint64 replacesAssetWithId);
event AssetAccepted(uint256 indexed tokenId, uint64 indexed assetId, uint64 replacesAssetWithId);
event AssetPrioritySet(uint256 indexed tokenId);
function getActiveAssets(uint256 tokenId) external view returns (uint64[] memory);
function getPendingAssets(uint256 tokenId) external view returns (uint64[] memory);
function getActiveAssetPriorities(uint256 tokenId) external view returns (uint16[] memory);
function getAssetReplacements(uint256 tokenId, uint64 newAssetId) external view returns (uint64);
function getAssetMetadata(uint256 tokenId, uint64 assetId) external view returns (string memory);
function acceptAsset(uint256 tokenId, uint256 index, uint64 assetId) external;
function rejectAsset(uint256 tokenId, uint256 index, uint64 assetId) external;
function setPriority(uint256 tokenId, uint16[] calldata priorities) external;
}Issuer can propose a new asset (added to pending list); the token holder accepts it (moves to active list) or rejects it. Once active, the holder can re-prioritise the asset list. This propose-accept flow prevents unwanted assets being forced onto a token.
Neo Equivalent: NEP-11 + Per-Token Asset List + Priority
The Neo port stores per-token asset arrays plus priority indices:
public static BigInteger[] GetActiveAssets(ByteString tokenId)
{
var raw = Storage.Get(Storage.CurrentContext, ActiveAssetsKey(tokenId));
return raw is null ? new BigInteger[0] : (BigInteger[])StdLib.Deserialize((ByteString)raw);
}
public static void AcceptAsset(ByteString tokenId, BigInteger pendingIndex, BigInteger assetId)
{
if (!Runtime.CheckWitness(OwnerOf(tokenId))) throw new Exception("NEP11:NotOwner");
var pending = GetPendingAssets(tokenId);
if (pendingIndex >= pending.Length || pending[pendingIndex] != assetId)
throw new Exception("NEP11:WrongAsset");
// Remove from pending, append to active.
var active = GetActiveAssets(tokenId);
var newActive = new BigInteger[active.Length + 1];
for (var i = 0; i < active.Length; i++) newActive[i] = active[i];
newActive[active.Length] = assetId;
SetActiveAssets(tokenId, newActive);
SetPendingAssets(tokenId, RemoveAt(pending, pendingIndex));
OnAssetAccepted(tokenId, assetId, 0);
}| ERC-5773 (Ethereum) | Neo Equivalent | Notes |
|---|---|---|
| Asset registry (asset id → metadata URI) | Map<uint64, string> in storage | Issuer-managed metadata catalog |
| Pending vs active asset lists | Two separate storage prefixes per tokenId | Same propose-accept flow |
acceptAsset / rejectAsset | AcceptAsset / RejectAsset owner-witness-checked | |
setPriority(uint16[]) | SetPriority(uint16[]) reorders the active list | |
getActiveAssetPriorities | View returning the active list in priority order | |
getAssetMetadata(tokenId, assetId) | View returning the asset's metadata URI |
Composition
- ERC-7160 — multi-metadata. ERC-5773 is more general (multiple typed assets vs a list of URI variants). Use 7160 for simple metadata versioning, 5773 when assets have distinct contexts.
- ERC-6220 — composable equippable parts (not yet mirrored). ERC-5773 is the asset layer; 6220 builds composable rigs on top.
- ERC-6059 — nestable NFTs. Often paired so child NFTs can be assets of a parent.
- ERC-4906 — metadata update event. Emit on asset accept / priority change so wallets refresh.
Migration Notes
For Solidity collections using ERC-5773 (typically RMRK Modular NFTs):
- Store the asset registry as
Map<int, string>for(assetId → metadataURI). - Per-token active + pending arrays as serialised storage entries.
- Issuer-side
AddAssetto create a pending asset; holder-sideAccept/Rejectto manage their token's active set. SetPrioritylets the holder reorder for default rendering.
The Neo port is verbose but mechanical — the propose-accept flow and the storage layout are the only design choices, both already settled by the Solidity reference.
