NFT Standards
Overview
The NFT Standards document outlines the specifications for implementing Non-Fungible Tokens (NFTs) on the Aleo blockchain. This standard emerged from the ARC-0721 proposal and was officially approved through community voting on Aleo Governance.
Similar to the Token Registry Program, the NFT standard faces challenges with program composability due to Aleo's current limitations on dynamic cross-program calls. To address this, an NFT Registry Program (ARC-722) has been proposed, which would serve as a central hub for NFT collections and enable better interoperability between NFTs and DeFi applications.
Key Features
The standard leverages Aleo's unique privacy features to provide:
- Private or public token owner visibility
- Private or public data associated with the token
- Flexible on-chain and off-chain data storage options
Data Storage Options
On-chain vs Off-chain Data
The standard allows for NFT data to be stored in three ways:
-
On-chain Data
- Direct storage of data on the blockchain
- Can be either the complete data or a hash of the data
-
Off-chain Data
- Reduces storage fees on the network
- Typically stored as URIs pointing to external metadata
- Can be combined with on-chain data for hybrid approaches
-
Hybrid Approach
- Combination of on-chain and off-chain storage
- Data can be public is stored on-chain (NFT ownership for example)
- Private data is stored off-chain (NFT data for example)
Data Structures
NFT Record
record NFT {
private owner: address,
private data: data,
private edition: scalar,
}
NFT Record Fields
owner
: The private address of the NFT ownerdata
: The private data associated with the NFTedition
: A scalar value used for uniqueness and privacy
NFT View Record
record NFTView {
private owner: address,
private data: data,
private edition: scalar,
private is_view: bool
}
NFT View Record Fields
owner
: The private address of the NFT ownerdata
: The private data associated with the NFTedition
: A scalar value used for uniqueness and privacyis_view
: A boolean flag to differentiate NFTView from NFT (always true)
Data Structure
struct attribute {
trait_type: [field; 4],
_value: [field; 4],
}
struct data {
metadata: [field; 4], // URI of offchain metadata JSON
// (optional) name: [field; 4],
// (optional) image: [field; 16],
// (optional) attributes: [attribute; 4],
// (optional) ...
}
Data Structure Fields
metadata
: URI pointing to off-chain metadata JSONname
: Optional name of the NFTimage
: Optional image dataattributes
: Optional array of attributes
An example of such an off-chain metadata JSON can be found here.
NFT Content Struct
struct nft_content {
data: data,
edition: scalar
}
NFT Content Struct Fields
data
: The data associated with the NFTedition
: The edition number of the NFT
Mappings
mapping nft_commits: field => bool;
Mapping of NFT commits to their existence status.
mapping nft_owners: field => address;
Mapping of NFT commits to their public owners.
mapping nft_contents: field => nft_content;
Mapping of NFT commits to their public content.
Functions
commit_nft()
Description
Creates a unique identifier for an NFT by committing its data and edition to a field.
Parameters
nft_data: data
: The data of the NFTnft_edition: scalar
: The edition number of the NFT
Returns
field
: The NFT commit identifier
transfer_private_to_public()
Description
Converts a privately owned NFT to public ownership.
Parameters
private nft: NFT
: The NFT record to convertpublic to: address
: The public recipient address
Returns
NFTView
: The NFT view recordFuture
: A Future to finalize the transfer
publish_nft_content()
Description
Publishes NFT content to make it publicly accessible.
Parameters
public nft_data: data
: The NFT data to publishpublic nft_edition: scalar
: The edition number to publish
Returns
Future
: A Future to finalize the publication
update_edition_private()
Description
Updates the edition of a private NFT to re-obfuscate its content.
Parameters
private nft: NFT
: The NFT record to updateprivate new_edition: scalar
: The new edition number
Returns
NFT
: The updated NFT recordFuture
: A Future to finalize the update
String Encoding
NFTs heavily rely on the use of strings, either for URL to off-chain data or for data itself. The standard specifies the following encoding for strings:
// Leo
string: [field; 4],
// Aleo instructions
string as [field; 4u32];
The length of the array can be freely adapted to match the maximum amount of characters required by the collection. The choice of fields type is motivated by the fact that they offer close to twice the amount of data for the same constraints as u128.
For JavaScript/TypeScript applications, an example for converting between JavaScript strings and Aleo plaintexts is available in the ARC-721 implementation.
Privacy Features
The standard implements privacy through several mechanisms:
Ownership Privacy
- Private ownership is achieved through Aleo records
- Public ownership can be enabled via the
nft_owners
mapping - Programs can own NFTs without revealing their data
Data Privacy
- NFT data is kept private by default in records
- The
edition
scalar ensures uniqueness without revealing data - NFT commits serve as unique identifiers without exposing underlying data
Re-obfuscation
NFTs can be re-obfuscated through a two-step process:
- Transfer back to private ownership
- Update the edition using
update_edition_private()
async transition update_edition_private(
private nft: NFT,
private new_edition: scalar,
) -> (NFT, Future) {
let out_nft: NFT = NFT {
owner: nft.owner,
data: nft.data,
edition: new_edition,
};
let nft_commit: field = commit_nft(nft.data, new_edition);
let update_edition_private_future: Future = finalize_update_edition_private(
nft_commit
);
return (out_nft, update_edition_private_future);
}
async function finalize_update_edition_private(
nft_commit: field,
) {
assert(nft_commits.contains(nft_commit).not());
nft_commits.set(nft_commit, true);
}
Important privacy considerations:
- Previous NFT commits remain in the mapping to prevent revealing data relationships
- New editions must be unique
- Process maintains data privacy while creating new public identifiers
Approvals
The standard includes an approval mechanism that allows designated addresses to transfer NFTs on behalf of the owner. The approval system supports both collection-wide and individual NFT approvals:
struct approval {
approver: address,
spender: address
}
mapping for_all_approvals: field => bool;
// Approval hash => Is approved
mapping nft_approvals: field => field;
// NFT commit => Approval hash
The approval system provides two main functions:
set_for_all_approval
: Allows an owner to approve a spender for all NFTs in the collectionapprove_public
: Allows an owner to approve a spender for a specific NFT
Once approved, the spender can use transfer_from_public
to transfer the NFT from the approver to a recipient address.
Settings
Collection-level settings are managed through a mapping:
mapping general_settings: u8 => field;
// Setting index => Setting value
Available settings indices and their purposes:
0u8
: Amount of mintable NFTs (all editions)1u8
: Number of total NFTs (first-editions) that can be minted2u8
: Symbol for the NFT3u8
: Base URI for NFT, part 14u8
: Base URI for NFT, part 25u8
: Base URI for NFT, part 36u8
: Base URI for NFT, part 47u8
: Admin address hash
These settings allow for fine-grained control over the NFT collection's properties, including minting limits, metadata location, and administrative controls.
Implementation Notes
-
The standard is compatible with ARC21 standard for name and symbol of fungible tokens.
-
For collections where data can become public ("publishable collections"), the standard provides mechanisms to publish and manage public content while maintaining the option to re-obfuscate data when needed.
-
The NFT Registry Program (ARC-722) is proposed to address program composability challenges, similar to how the Token Registry Program works for fungible tokens. This registry would allow multiple implementations with different data structures, identified by the unique pair (registry_program_id, collection_id).