Near Learn

NEP-171: Non-fungible tokens

NEP-171 non-fungible token (NFT) core standard on NEAR: nft_transfer, nft_transfer_call, nft_on_transfer, nft_resolve_transfer and nft_token vs ERC-721.

Intermediate5 min read3-question check

NEP-171 is NEAR’s NFT core — the ERC-721 counterpart. A token is identified by a string token_id inside one contract, has exactly one owner_id, and can be transferred directly or with a transfer-and-call that notifies a receiving contract.

The core is deliberately small. Display data is NEP-177, marketplace approvals are NEP-178, listing tokens is NEP-181 and royalties are NEP-199. A typical NFT contract implements all five plus NEP-297 events (nft_mint, nft_transfer, nft_burn).

Methods#

MethodKindWhat it doesDeposit
nft_transfer(receiver_id, token_id, approval_id?, memo?)changeTransfer a token. Callable by the owner, or by an approved account passing its approval_id (NEP-178).exactly 1 yoctoNEAR
nft_transfer_call(receiver_id, token_id, approval_id?, memo?, msg)changeTransfer, then call receiver_id.nft_on_transfer(...). Resolves to true if the token ended up with the receiver.exactly 1 yoctoNEAR
nft_token(token_id)viewThe Token (token_id, owner_id, plus metadata / approved_account_ids when those extensions are implemented), or null.—
nft_on_transfer(sender_id, previous_owner_id, token_id, msg)change (on the receiver)Implemented by contracts that accept NFTs. Return true to send the token back.—
nft_resolve_transfer(previous_owner_id, receiver_id, token_id, approved_account_ids?)private callbackRuns after nft_on_transfer. Returns the token to the previous owner if the receiver asked for it or panicked.—

How nft_transfer_call works#

  1. Alice calls nft.nft_transfer_call(receiver_id: "escrow.near", token_id: "42", msg) with 1 yoctoNEAR.
  2. The NFT contract makes escrow.near the owner immediately and clears the token’s approvals.
  3. It calls escrow.near.nft_on_transfer(sender_id: "alice.near", previous_owner_id: "alice.near", token_id: "42", msg) (sender_id differs from the previous owner when an approved account initiated the transfer).
  4. nft_resolve_transfer runs. If the receiver returned true, or panicked, the token goes back to Alice and her old approvals are restored; otherwise the receiver keeps it.
A receiver that accepts NFTs from one collection only
Rust
use near_contract_standards::non_fungible_token::core::NonFungibleTokenReceiver;
use near_contract_standards::non_fungible_token::TokenId;
use near_sdk::store::LookupMap;
use near_sdk::{env, near, AccountId, PanicOnDefault, PromiseOrValue};

#[near(contract_state)]
#[derive(PanicOnDefault)]
pub struct Escrow {
    collection: AccountId,
    deposited_by: LookupMap<TokenId, AccountId>,
}

#[near]
impl NonFungibleTokenReceiver for Escrow {
    fn nft_on_transfer(
        &mut self,
        sender_id: AccountId,
        previous_owner_id: AccountId,
        token_id: TokenId,
        msg: String,
    ) -> PromiseOrValue<bool> {
        let _ = (sender_id, msg);
        // anyone can call this — reject NFTs from other contracts
        if env::predecessor_account_id() != self.collection {
            return PromiseOrValue::Value(true); // "send it back"
        }
        self.deposited_by.insert(token_id, previous_owner_id);
        PromiseOrValue::Value(false) // keep it
    }
}

Check yourself

3 questions · progress saved in this browser

  1. 1.Your marketplace contract’s nft_on_transfer returns true. What happens?
  2. 2.Which check is essential inside nft_on_transfer?
  3. 3.Where does ERC-721’s ownerOf(tokenId) live in the NEAR NFT standards?