Skip to main content

Non-Fungible Tokens (NFT)

In contrast with fungible tokens, non-fungible tokens (NFT) are unitary and therefore unique. This makes NFTs ideal to represent ownership of assets such as a piece of digital content, or a ticket for an event.

As with fungible tokens, NFTs are not stored in the user's wallet, instead, each NFT lives in a NFT contract. The NFT contract works as a bookkeeper, this is: it is in charge of handling the creation, storage and transfers of NFTs.

In order for a contract to be considered a NFT-contract it has to follow the NEP-171 and NEP-177 standards. The NEP-171 & NEP-177 standards explain the minimum interface required to be implemented, as well as the expected functionality.

NFT & Marketplaces

Be mindful of not confusing an NFT with an NFT-marketplace. NFT simply store information (metadata), while NFT-marketplaces are contracts where NFT can be listed and exchanged for a price.


Community Projectsโ€‹

The easiest way to create and handle NFTs is by using one of the existing community projects.

  1. Paras - a classic NFT marketplace. Just login with your NEAR account, create a collection and share the link with your community.
  2. Mintbase - a marketplace that allows to create NFT collections, and buy NFTs using credit cards or stablecoins.
  3. Enleap - a no-code launchpad for NFTs. Provides NFT minting, staking, whitelist managing, tracking functionality.

Deploying a NFT Contractโ€‹

If you want to deploy your own NFT contract, you can create one using our reference implementation

Simply personalize it and deploy it to your account.

near deploy <account-id> --wasmFile contract.wasm --initFunction new
tip

Check the Contract Wizard to create a personalized NFT contract!.


Minting a NFTโ€‹

To create a new NFT (a.k.a. minting it) you will call the nft_mint method passing as arguments the metadata that defines the NFT.

const tokenData = Near.call(
"nft.primitives.near",
"nft_mint",
{
token_id: "1",
receiver_id: "bob.near",
token_metadata: {
title: "NFT Primitive Token",
description: "Awesome NFT Primitive Token",
media: "string", // URL to associated media, preferably to decentralized, content-addressed storage
}
},
undefined,
10000000000000000000000, // Depends on your NFT metadata
);
info

See the metadata standard for the full list of TokenMetadata parameters.

warning

Values of gas and deposit might vary depending on which NFT contract you are calling.


Minting Collectionsโ€‹

Many times people want to create multiple 100 copies of an NFT (this is called a collection). In such cases, what you actually need to do is to mint 100 different NFTs with the same metadata (but different token-id).

tip

Notice that minting in Mintbase allows you to pass a num_to_mint parameter.


Royaltiesโ€‹

You might have noticed that one of the parameters is a structure called royalties. Royalties enable you to create a list of users that should get paid when the token is sell in a marketplace. For example, if anna has 5% of royalties, each time the NFT is sell, anna should get a 5% of the selling price.


Querying NFT dataโ€‹

You can query the NFT's information and metadata by calling the nft_token.

const tokenData = Near.view("nft.primitives.near", "nft_token", {
token_id: "1",
});
Example response
{
"token_id": "1",
"owner_id": "bob.near",
"metadata": {
"title": "string", // ex. "Arch Nemesis: Mail Carrier" or "Parcel #5055"
"description": "string", // free-form description
"media": "string", // URL to associated media, preferably to decentralized, content-addressed storage
"media_hash": "string", // Base64-encoded sha256 hash of content referenced by the `media` field. Required if `media` is included.
"copies": 1, // number of copies of this set of metadata in existence when token was minted.
"issued_at": 1642053411068358156, // When token was issued or minted, Unix epoch in milliseconds
"expires_at": 1642053411168358156, // When token expires, Unix epoch in milliseconds
"starts_at": 1642053411068358156, // When token starts being valid, Unix epoch in milliseconds
"updated_at": 1642053411068358156, // When token was last updated, Unix epoch in milliseconds
"extra": "string", // anything extra the NFT wants to store on-chain. Can be stringified JSON.
"reference": "string", // URL to an off-chain JSON file with more info.
"reference_hash": "string" // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included.
}
}

Transferring a NFTโ€‹

Transferring an NFT can happen in two scenarios: (1) you ask to transfer an NFT, and (2) an authorized account asks to transfer the NFT.

In both cases, it is necessary to invoke the nft_transfer method, indicating the token id, the receiver, and an (optionally) an approval_id.

const tokenData = Near.call(
"nft.primitives.near",
"nft_transfer",
{
token_id: "1",
receiver_id: "bob.near"
},
undefined,
1,
);

Attaching NFTs to a Callโ€‹

Natively, only NEAR tokens (โ“ƒ) can be attached to a function calls. However, the NFT standard enables to attach a non-fungible tokens in a call by using the NFT-contract as intermediary. This means that, instead of you attaching tokens directly to the call, you ask the NFT-contract to do both a transfer and a function call in your name.

near call <nft-contract> nft_transfer_call '{"receiver_id": "<receiver-contract>", "token_id": "<token_id>", "msg": "<a-string-message>"}' --accountId <your-account> --depositYocto 1
info

Optionally, a memo parameter can be passed to provide more information to your contract.


How Does it Work?โ€‹

Assume you want to attach an NFT (๐ŸŽซ) to a call on the receiver contract. The workflow is as follows:

  1. You call nft_transfer_call in the NFT-contract passing: the receiver, a message, and the token-id of ๐ŸŽซ.
  2. The NFT contract transfers the NFT ๐ŸŽซ to the receiver.
  3. The NFT contract calls receiver.nft_on_transfer(sender, token-owner, token-id, msg).
  4. The NFT contract handles errors in the nft_resolve_transfer callback.
  5. The NFT contract returns true if it succeeded.

The nft_on_transfer methodโ€‹

From the workflow above it follows that the receiver we want to call needs to implement the nft_on_transfer method. When executed, such method will know:

  • Who is sending the NFT, since it is a parameter
  • Who is the current owner, since it is a parameter
  • Which NFT was transferred, since it is a parameter.
  • If there are any parameters encoded as a message

The nft_on_transfer must return true if the NFT has to be returned to the sender.


Approving Usersโ€‹

You can authorize other users to transfer an NFT you own. This is useful, for example, to enable listing your NFT in a marketplace. In such scenario, you trust that the marketplace will only transfer the NFT upon receiving a certain amount of money in exchange.

near call <nft-contract> nft_approve '{
"token_id": "<token-unique-id>",
"account_id": "<authorized-account>",
"msg": "<json-structure>"
}' --accountId <your-account> --depositYocto 1
info

If the msg parameter is included, then a cross-contract call will be made to <authorized_account>.nft_on_approve(msg). Which in turn will make a callback to nft_resolve_transfer in your NFT contract.


List a NFT for saleโ€‹

Basic NFT contracts following the NEP-171 and NEP-177 standards do not implement marketplace functionality.

For this purpose, there are ecosystem apps such as Paras or Mintbase, that use dedicated marketplace contracts.

In order to put a NFT for a sale on a marketplace you need to do two actions:

  1. Cover data storage costs in the marketplace contract.
  2. Approve the marketplace to sell the NFT in your NFT contract.

Near.call(
"marketplace.paras.near",
"storage_deposit",
{
receiver_id: "bob.near"
},
undefined,
9390000000000000000
);

Near.call(
"nft.primitives.near",
"nft_approve",
{
token_id: "1e95238d266e5497d735eb30",
account_id: "marketplace.paras.near",
msg: {
price: "200000000000000000000000",
market_type: "sale",
ft_token_id: "near"
}
}
);

The method nft_approve will call nft_on_approve in marketplace.paras.near.


Buy a NFTโ€‹

Basic NFT contracts following the NEP-171 and NEP-177 standards do not implement marketplace functionality.

For this purpose, there are ecosystem apps such as Paras or Mintbase, that use dedicated marketplace contracts.

const tokenData = Near.call(
"x.paras.near",
"nft_buy",
{
token_series_id: "299102",
receiver_id: "bob.near",
},
undefined,
205740000000000000000000 // NFT price + storage cost
);

Example response:

"299102:1"

Additional Resourcesโ€‹

  1. NFT Tutorial (NEAR examples, JavaScript SDK) - a set of tutorials that cover how to create a NFT contract using JavaScript.
  2. NFT Tutorial (NEAR examples, Rust SDK) - a set of tutorials that cover how to create a NFT contract using Rust.
  3. NFT Tutorial by Keypom (a fork of the NEAR example tutorial).
  4. Paras API documentation.
  5. Mintbase API documentation.
  6. Mintbase JS SDK - a set of methods to get data from blockchain, interact with Mintbase contracts, etc.
Was this page helpful?