Near Learn

NEP-178: NFT approval management

NEP-178 NFT approval management on NEAR: nft_approve, nft_revoke, nft_is_approved, approval IDs and nft_on_approve — how marketplaces list NFTs safely.

Intermediate6 min read3-question check

NEP-178 lets an NFT owner authorise other accounts — usually marketplaces — to transfer a specific token on their behalf. It is the NEAR counterpart of ERC-721’s approve / setApprovalForAll, with two important twists: approvals are per token and each one carries an approval ID.

A token can have several approved accounts at once (list on three marketplaces), and every approval is cleared when the token is transferred.

Methods#

MethodKindWhat it doesDeposit
nft_approve(token_id, account_id, msg?)changeOwner approves account_id for one token. If msg is given, the contract also calls account_id.nft_on_approve(...).at least 1 yoctoNEAR, plus storage for the approval
nft_revoke(token_id, account_id)changeRemove one approval and refund its storage.exactly 1 yoctoNEAR
nft_revoke_all(token_id)changeRemove every approval on the token.exactly 1 yoctoNEAR
nft_is_approved(token_id, approved_account_id, approval_id?)viewtrue if the account is approved — and, if approval_id is given, approved with exactly that ID.—
nft_on_approve(token_id, owner_id, approval_id, msg)change (on the approved contract)Notification to the marketplace, typically used to create the listing from msg (e.g. a price).—

Why approval IDs exist#

Every time the contract approves an account it hands out a new, increasing approval_id, and nft_token shows them in approved_account_ids (e.g. { "market-a.near": 3, "market-b.near": 4 }). When a marketplace later calls nft_transfer, it passes the approval_id it was given. If the IDs do not match, the transfer fails.

This defeats a real race. Alice lists a token on markets A and B and sells it on A. The buyer later sells it back to Alice, who approves B again at a higher price. Market B may still hold her old listing at the old price; without IDs, a buyer could fill that stale, cheaper listing. With IDs, B’s old listing carries the old approval_id, the transfer fails, and only the fresh listing works. The ID proves the token’s state has not changed since the approval was granted.

Marketplace side: create a listing when approved
Rust
use near_contract_standards::non_fungible_token::approval::NonFungibleTokenApprovalReceiver;
use near_contract_standards::non_fungible_token::TokenId;
use near_sdk::json_types::U128;
use near_sdk::store::LookupMap;
use near_sdk::{env, near, AccountId, PanicOnDefault, PromiseOrValue};

#[near(serializers = [borsh, json])]
pub struct Listing {
    owner_id: AccountId,
    approval_id: u64,
    price: U128,
}

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

#[near]
impl NonFungibleTokenApprovalReceiver for Market {
    fn nft_on_approve(
        &mut self,
        token_id: TokenId,
        owner_id: AccountId,
        approval_id: u64,
        msg: String,
    ) -> PromiseOrValue<String> {
        assert_eq!(env::predecessor_account_id(), self.collection, "unknown collection");
        let price: U128 = near_sdk::serde_json::from_str(&msg).expect("msg must be a price");
        // remember the approval_id: we must pass it to nft_transfer when we sell
        self.listings.insert(token_id, Listing { owner_id, approval_id, price });
        PromiseOrValue::Value("listed".to_string())
    }
}

Check yourself

3 questions · progress saved in this browser

  1. 1.A marketplace tries to sell a token using an approval ID it stored a week ago, but the call fails. What is the most likely reason?
  2. 2.What is the NEP-178 equivalent of ERC-721 setApprovalForAll?
  3. 3.Why does nft_approve typically need more than 1 yoctoNEAR attached?