Non-Fungible Token
In the world of digital assets, not all tokens are alike. This becomes important in situations like real estate, voting rights, or collectibles, where some items are valued more than others due to their usefulness, rarity, etc. On Stellar, you can create non-fungible tokens (NFTs), where each token is unique and represents something distinct, with ownership tracked through Soroban smart contracts.
Overview
The non-fungible module provides three different NFT variants that differ in how certain features like ownership tracking, token creation and destruction are handled:
-
Default base implementations (
NonFungibleToken
andNonFungibleBurnable
), suitable for most use cases. -
Consecutive extension (
NonFungibleConsecutive
) that fits needs where batch minting is envisioned, the implementation is optimized for creation of large amounts of tokens. -
Enumerable extension (
NonFungibleEnumerable
), for cases where on-chain enumerability is required, enabling a smart contract to list all the NFTs an address owns.
These three variants share core functionality and a common interface, exposing identical contract functions as entry-points. However, composing custom flows must be handled with extra caution. That is required because of the incompatible nature between the business logic of the different NFT variants or the need to wrap the base functionality with additional logic.
Usage
We’ll use an NFT to track game items, each having their own unique attributes. Whenever one is to be
awarded to a player, it will be minted and sent to them. Players are free to keep or burn their token or
trade it with other people as they see fit. Please note any account can call award_item
and we might
want to implement access control to restrict who can mint.
Here’s what a contract for tokenized items might look like:
use soroban_sdk::{contract, contractimpl, Address, Env, String};
use stellar_default_impl_macro::default_impl;
use stellar_non_fungible::{
burnable::NonFungibleBurnable,
Balance, Base, ContractOverrides, NonFungibleToken, TokenId,
};
#[contract]
pub struct GameItem;
#[contractimpl]
impl GameItem {
pub fn __constructor(e: &Env) {
Base::set_metadata(
e,
String::from_str(e, "www.mygame.com"),
String::from_str(e, "My Game Items Collection"),
String::from_str(e, "MGMC"),
);
}
pub fn award_item(e: &Env, to: Address) -> TokenId {
// access control might be needed
Base::sequential_mint(e, &to)
}
}
#[default_impl]
#[contractimpl]
impl NonFungibleToken for GameItem {
type ContractType = Base;
}
#[default_impl]
#[contractimpl]
impl NonFungibleBurnable for GameItem {}
Base and Extensions
The default base variant is split into two parts:
-
Non-Fungible Token: The base logic for NFT transfers, approvals, minting and metadata handling.
-
Non-Fungible Burnable: Optional Extension with the base logic for token destruction by token holders.
Separating the burn functionality from NonFungibleToken
aims to accommodate flexibility and
customization for various use cases.
The following optional extensions are also provided:
-
Non-Fungible Consecutive: Extension for optimized minting of batches of tokens.
-
Non-Fungible Enumerable: Extension that allows enumerating the tokens on-chain.