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#
| Method | Kind | What it does | Deposit |
|---|---|---|---|
nft_approve(token_id, account_id, msg?) | change | Owner 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) | change | Remove one approval and refund its storage. | exactly 1 yoctoNEAR |
nft_revoke_all(token_id) | change | Remove every approval on the token. | exactly 1 yoctoNEAR |
nft_is_approved(token_id, approved_account_id, approval_id?) | view | true 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.
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