Skip to content
🎉 Welcome to the new Aptos Docs! Click here to submit feedback!
Build
Digital Asset (DA)

Aptos Digital Asset (DA) Standard

The Digital Asset (DA) standard is a modern Non-Fungible Token (NFT) standard for Aptos. NFTs represent unique assets on-chain, and are stored in collections. These NFTs can be customized to later be transferred, soulbound, burned, mutated, or customized via your own smart contracts.

This standard replaces the legacy Aptos Token Standard. The most important improvements to note are:

ImprovementDescription
Token ExtensionTokens can be easily extended since they are implemented using Move Objects.
Direct NFT TransferYou can now directly transfer NFTs without the recipient “opting-in” on-chain.
NFT ComposabilityNFTs can own other NFTs for easy composability.

If you want a simple way to mint NFTs without the ability to customize or extend their functionality, you can use the aptos_token module which implements the DA standard (see the section on how to use it below).

Note that all Digital Asset modules are deployed at the reserved address 0x4.

Using the Digital Asset Standard

This standard is implemented with two Objects:

  1. Collections - A set of NFTs with a name and a bit of context for the group.
  2. Tokens - Digital assets which represent unique assets. These are often used to represent NFTs and usually use a uri link to point to more info about the asset (ex. a link to an image, video, etc.).
Digital Asset token and collection relationship

All Tokens are required to have a reference to a parent Collection, but the parent Collection does not own the Token. Newly minted Tokens are usually owned by the creator. From there, they can be transferred to other accounts.

Collections

FieldDescription
DescriptionAn optional string smaller than 2048 characters (modifiable with a MutatorRef).
NameA required string to identify the Collection. The name must be unique within each account. That means a single creator account cannot create more than one Collection with the same name.
RoyaltyAn optional Royalty struct indicating what % of the sale price goes to the creator of the Collection. This can be changed with a MutatorRef generated by the Royalty module. The Royalty module is an extension for the DA standard. See example usage in aptos_token.move.
URI lengthAn optional string that is smaller than 512 characters which links to relevant content for the Collection (modifiable with a MutatorRef).

Creating a Collection

There are two ways to create a Collection depending on whether you want there to be a maximum supply of Tokens it can hold.

Fixed Maximum Supply

To make a Collection with a fixed supply you can use collection::create_fixed_collection like so:

example.move
use aptos_token_objects::collection;
use std::option::{Self, Option};
 
public entry fun create_collection(creator: &signer) {
    let max_supply = 1000;
    let royalty = option::none();
    
    // Maximum supply cannot be changed after collection creation
    collection::create_fixed_collection(
        creator,
        "My Collection Description",
        max_supply,
        "My Collection",
        royalty,
        "https://mycollection.com",
    );
}

Unlimited Supply

To create a Collection with unlimited supply you can use collection::create_unlimited_collection:

example.move
use std::option::{Self, Option};
 
public entry fun create_collection(creator: &signer) {
    let royalty = option::none();
 
    collection::create_unlimited_collection(
        creator,
        "My Collection Description",
        "My Collection",
        royalty,
        "https://mycollection.com",
    );
}
⚠️

A Collection’s maximum supply cannot be changed after creation.

Customizing a Collection

Since each Collection is a Move Object, you can customize it by generating permissions called Refs. Each Ref allows you to modify an aspect of the Object later on. Beyond the normal Object Refs, Collections can also get a MutatorRef by calling get_mutator_ref like so:

example.move
use std::option::{Self, Option};
 
public entry fun create_collection(creator: &signer) {
    let royalty = option::none();
    let collection_constructor_ref = &collection::create_unlimited_collection(
        creator,
        "My Collection Description",
        "My Collection",
        royalty,
        "https://mycollection.com",
    );
    let mutator_ref = collection::get_mutator_ref(collection_constructor_ref);
    // Store the mutator ref somewhere safe
}
⚠️

Refs must be generated at creation time of an Object. The ConstructorRef used to generate other Refs expires as soon as the transaction to create the Object is finished.

You can further customize your Collection by adding more resources or functionalities via smart contract. For example, a Collection can track when it was created in order to limit when Tokens can be minted like so:

example.move
use std::option::{Self, Option};
 
struct MyCollectionMetadata has key {
    creation_timestamp_secs: u64,
}
 
public entry fun create_collection(creator: &signer) {
    let royalty = option::none();
    // Constructor ref is a non-storable struct returned when creating a new object.
    // It can generate an object signer to add resources to the collection object.
    let collection_constructor_ref = &collection::create_unlimited_collection(
        creator,
        "My Collection Description",
        "My Collection",
        royalty,
        "https://mycollection.com",
    );
    // Constructor ref can be exchanged for signer to add resources to the collection object.
    let collection_signer = &object::generate_signer(collection_constructor_ref);
    move_to(collection_signer, MyCollectionMetadata { creation_timestamp_secs: timestamp::now_seconds() } })
}

Tokens

FieldDescription
DescriptionAn optional string smaller than 2048 characters (modifiable with a MutatorRef).
NameA required string to identify the Collection that is unique within each Collection. This means a single Collection account cannot have more than one Token with the same name.
RoyaltyAn optional Royalty struct indicating what % of the sale price goes to the creator of the Collection. This can be changed with a MutatorRef generated by the Royalty module (an extension for the DA standard. See example usage in aptos_token.move). Usually royalty is set on collections, but setting it on Tokens allows the individual Token to have a custom royalty amount.
URI lengthAn optional string that is smaller than 512 characters which links to relevant content for the Collection (modifiable with a MutatorRef).

Creating Tokens

There are a few ways to create a Token:

  1. Named tokens. These use the name of the Token to generate a named Object. This makes it easy to find the address for the token if you know the token and Collection name, but named Objects are not deletable. Trying to delete the a named token will only delete the data, not the Object itself.
example.move
use aptos_token_objects::token;
use std::option::{Self, Option};
 
public entry fun mint_token(creator: &signer) {
    let royalty = option::none();
    token::create_named_token(
        creator,
        "Collection Name",
        "Description",
        "Token Name",
        royalty,
        "https://mycollection.com/my-named-token.jpeg",
    );
}

You can derive the address for named tokens by:

  1. Concatenating the creator address, collection name and token name.
  2. Doing a sha256 hash of that new string.
  1. “Unnamed” tokens. These create unnamed Objects (which are deletable) but still have a Token name. Because the Object address is not deterministic, you must use an Indexer to find the address for them.
example.move
use aptos_token_objects::token;
use std::option::{Self, Option};
 
public entry fun mint_token(creator: &signer) {
    let royalty = option::none();
    token::create(
        creator,
        "Collection Name",
        "Description",
        "Token Name",
        royalty,
        "https://mycollection.com/my-named-token.jpeg",
    );
}

Finding Unnamed Token Addresses via Indexer

You can find the addresses of your recently created “unnamed” Tokens by using the Aptos Indexer with queries like the following:

  1. Looking up the collection id by using your account address and the name of the Collection.
Loading...
  1. Then look up the address (token_data_id) of the Token by using the collection_id (from above) and the name of the token:
Loading...

In general, using unnamed tokens give you the most flexibility because the Object can be deleted later, but named tokens simplify looking up addresses.

Using Tokens

Transfer Tokens

Transferring a Token can be done by calling object::transfer.

example.move
public entry fun transfer<T: key>(owner: &signer, object: object::Object<T>, to: address)

Burning Tokens

Burning / deleting a Token requires storing a BurnRef with token::generate_burn_ref, then calling token::burn.

example.move
use std::option::{Self, Option};
 
public entry fun mint_token(creator: &signer) {
    let royalty = option::none();
    let token_constructor_ref = &token::create(
        creator,
        "My Collection",
        "My named Token description",
        "My named token",
        royalty,
        "https://mycollection.com/my-named-token.jpeg",
    );
    let burn_ref = token::generate_burn_ref(token_constructor_ref);
    // Store the burn ref somewhere safe
}
 
public entry fun burn_token(token: Object<Token>) {
    // Remove all custom data from the token object.
    let token_address = object::object_address(&token);
    let CustomData { ... } = move_from<CustomData>(token_address);
 
    // Retrieve the burn ref from storage
    let burn_ref = ...;
    token::burn(burn_ref);
}
⚠️

If any custom resources were moved onto the Token, those must be removed / deleted first beforetoken::burn can delete the Token. For named tokens which cannot be deleted, token::burn will For named Tokens token::burn will remove all Token content instead.

Modifying Tokens After Creation

Mutating a Token’s URI or description requires a MutatorRef (which must be generated when creating the Token, then stored for later).

example.move
use std::option::{Self, Option};
 
public entry fun mint_token(creator: &signer) {
    let royalty = option::none();
    // Constructor ref is a non-storable struct returned when creating a new object.
    // It can be exchanged for signer to add resources to the token object.
    let token_constructor_ref = &token::create(
        creator,
        "My Collection",
        "My named Token description",
        "My named token",
        royalty,
        "https://mycollection.com/my-named-token.jpeg",
    );
    let mutator_ref = token::generate_mutator_ref(token_constructor_ref);
    // Store the mutator ref somewhere safe
}
⚠️

Changing the royalty requires generating a separate MutatorRef from the Royalty module.

Extending Tokens

Tokens can be extended either by adding additional resources (since they are an Object) or using Refs to modify the Object.

Aptos Token

For NFT creators who want to avoid writing their own logic for how your NFT should work, you can use the aptos_token module to mint an NFT. This module is already deployed at 0x4 and allows you to:

  1. Mint a Token you can transfer with royalties.
  2. Mint a soulbound Token.
  3. Manage the resources your NFT has.

See the aptos_token reference docs for all the helper functions you can use.

⚠️

The main drawback of using the aptos_token module is that the Tokens are not extensible (the mint function does not return a ConstructorRef).

Minting with aptos_token

Minting a Token using aptos_token requires the same parameters as any token that implements the DA standard. In addition though, the aptos_token module allows you to specificy a property map of key/value pairs for any other properties your specific NFT may require.

You can mint your Token by calling aptos_token::mint like so:

example.move
public entry fun mint(
    creator: &signer,
    collection: String,
    description: String,
    name: String,
    uri: String,
    property_keys: vector<String>,
    property_types: vector<String>,
    property_values: vector<vector<u8>>,
) acquires AptosCollection, AptosToken

Soulbound Tokens

To mint a soul bound Token, you can call aptos_token::mint_soul_bound instead:

example.move
public entry fun mint_soul_bound(
    creator: &signer,
    collection: String,
    description: String,
    name: String,
    uri: String,
    property_keys: vector<String>,
    property_types: vector<String>,
    property_values: vector<vector<u8>>,
    soul_bound_to: address,
) acquires AptosCollection
⚠️

In the near future, a new module TokenMinter will be released to replace aptos_token. You can follow the status of that proposal here.

Examples and Useful Links