Digital Assets


This is the tutorial to read if you want to learn about NFTs, or the ERC-721 equivalent on Aptos.


Overview

Digital Asset is recommended for any new collections or protocols that want to build NFT's (non-fungible tokens). It provides extendability to customize richer functionalities.

Here we will focus on the composability aspect.


Why Digital Assets?

Digital Assets use Aptos objects rather than account resources traditionally used in Move. This allows for storing data outside the account and adding flexibility in this way.

  • Tokens can be easily extended with custom data and functionalities without requiring any changes in the framework.
  • Transfers are simply a reference update.
  • Direct transfer is allowed without an opt in.
  • NFTs can own other NFTs adding easy composability.
  • Soul bound tokens can be easily supported.

If you're familiar with Aptos token V1 (the old standard), digital assets is the new standard that we strongly recommend to use.


Aptogotchi Example

Source Code

Digital Asset Composition

  • Digital Assets are objects and can be owned by another object. That means we can compose multiple NFT's into one, singular NFT.
  • In our example, a user can mint a bowtie as an NFT. When an Aptogotchi wears a bowtie, ownership of the bowtie NFT is transferred from the user to the Aptogotchi NFT.

public entry fun create_accessory(user: &signer, category: String) acquires ObjectController {
let uri = string::utf8(ACCESSORY_COLLECTION_URI);
let description = string::utf8(ACCESSORY_COLLECTION_DESCRIPTION);
let constructor_ref = token::create_named_token(
&get_app_signer(get_app_signer_address()),
string::utf8(ACCESSORY_COLLECTION_NAME),
description,
get_accessory_token_name(&address_of(user), category),
option::none(),
uri,
);
let token_signer = object::generate_signer(&constructor_ref);
let transfer_ref = object::generate_transfer_ref(&constructor_ref);
let category = string::utf8(ACCESSORY_CATEGORY_BOWTIE);
let id = 1;
let accessory = Accessory {
category,
id,
};
move_to(&token_signer, accessory);
object::transfer_with_ref(object::generate_linear_transfer_ref(&transfer_ref), address_of(user));
}

  • A user can also decompose Aptogotchi and bowtie by transferring the bowtie back from the Aptogotchi to themselves. The bowtie will show up in the user's wallet again by doing so.

public entry fun unwear_accessory(owner: &signer, category: String) acquires ObjectController {
let owner_addr = &address_of(owner);
// retrieve the accessory object by category
let accessory_address = get_accessory_address(owner_addr, category);
let has_accessory = exists<Accessory>(accessory_address);
if (has_accessory == false) {
assert!(false, error::unavailable(EACCESSORY_NOT_AVAILABLE));
};
let accessory = object::address_to_object<Accessory>(accessory_address);
object::transfer(owner, accessory, address_of(owner));
}


Digital Asset Transfer

For Digital Assets, transfers are a reference update. There is no data movement from one account to another.

There are 3 APIs we could use to transfer a token:

  • Since Digital Asset is Object, we can use object::transfer when the action is performed by the owner.
  • We can also use object::transfer_with_ref(transfer_ref, to_addr) to transfer when holding the transferRef. This allows an admin to transfer tokens without actually owning the token.
  • If we transfer the ownership to an object instead of a user, we can use object::transfer_to_object(owner, object, to_object).

public entry fun wear_accessory(owner: &signer, category: String) acquires ObjectController {
let owner_addr = &address_of(owner);
// retrieve the aptogotchi object
let token_address = get_aptogotchi_address(owner_addr);
let gotchi = object::address_to_object<Aptogotchi>(token_address);
// retrieve the accessory object by category
let accessory_address = get_accessory_address(owner_addr, category);
let accessory = object::address_to_object<Accessory>(accessory_address);
object::transfer_to_object(owner, accessory, gotchi);
}