# NFT Standard Discussion

**URL:** <https://gov.near.org/t/nft-standard-discussion/853>\
**Category:** Standards\
**Created:** [March 6, 2021, 5:19am UTC](https://gov.near.org/t/nft-standard-discussion/853 "2021-03-06T05:19:09Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![mattlockyer](https://dub1.discourse-cdn.com/flex005/user_avatar/gov.near.org/mattlockyer/32/56_2.png) [@mattlockyer](https://gov.near.org/u/mattlockyer)\
**Post date:** [March 6, 2021, 5:19am UTC](https://gov.near.org/t/nft-standard-discussion/853/1 "2021-03-06T05:19:09Z")

</div>

# Why?

We have a lot of questions floating around various groups and stakeholders about the NFT standard. I would like to align those discussions somewhere official, here on the forum, to establish the following things:

# Goal: achieve soft consensus here on the interface and high level architecture. Take it to GH for implementation.

### Here’s what we would love to see happen:

- everyone bring their requirements
- lay all ideas out on the table
- look for patterns in the possible solutions
- choose solution(s)

### Here’s what we don’t want:

- promoting a particular approach
- not listening to others
- trying to “own” the standard
- starting my own standard

# Discussion

### 1. Standard interface for NFTs

- account ownership of tokens and transfer of ownership
- approval of other accounts to transfer ownership
- markets (stretch goal for this discussion)

### 2. Suggested architecture and reference implementation that tackles how to:

- split up the interface across contracts
- deal with storage
- deal with UX
- maximize for extensibility future use cases

# NFT Basic Requirements

At the core NFT contracts have a unique account owning 1 or many unique token IDs. The ownership can be transferred from account to account. That’s it.

The original creator of the NFT has additional functionality e.g. mint, burn, etc.. but that’s outside the scope of this discussion.

## Metadata

NFTs may have some associated metadata _per token_ e.g. a URL.

## Enumeration (optional)

A user can retrieve an array of all their tokens from the contract.

## Markets & Sales

NFTs have a popular function in secondary markets. If you trust a market you give it approval to transfer your token after the sale has been completed.

Market should handle the buying and selling of NFTs. This is also outside the scope of this discussion, but needs to be mentioned within the context of transfer approvals per token.

**Note** in NEAR the contract pays for how much data is stored, so a dynamically expanding and contracting list of approvals will affect the NEAR tokens locked for the NFT contract owner, which is not always the owner of an individual token ID.

# Requirements (incomplete, please discuss)

1. a token ID (token) can only be owned by one account at a time
2. accounts can own many tokens
3. accounts can transfer tokens that they own to other accounts (by passing ownership)
4. accounts can approve many other accounts (markets) on a per token basis, to transfer their token
5. one or more tokens can be associated with one or more metadata records

**???**

# Interface (INCOMPLETE)

### Data Structures

```rust
Token {
    owner_id: AccountId,
    approved_id: AccountId,
    metadata: String,
}

```

???

### Fields

```rust
owner_id: AccountId,
tokens: UnorderedMap<TokenId, Token>,

```

???

### Methods

```rust
nft_transfer(new_owner_id: AccountId, token_id: TokenId),
nft_transfer_call: (new_owner_id: AccountId, token_id: TokenId, memo: Option<String>),
on_nft_transfer: (sender_id: ValidAccountId, token_id: TokenId, memo: String)

```

???

# Implementation Discussion (INCOMPLETE)

As I understand, from discussions with stakeholders, there are a few implementations in the wild and some ideas that need to be reconciled, or we will end up with a fairly fractured ecosystem.

### Storage Issues

Storage is dynamic means for the NFT contract, when a user adds an approval for a marketplace, the contract’s NEAR tokens are locked up to reserve that storage. This means users must “pre-pay” storage by “registering”.

Unless the contract pays all storage for users, in A & B a user must “register” with each NFT contract and provide storage deposits for functionality that has “dynamic” storage (e.g. ever expanding list of approved markets). This also means a user must register with a contract when they are receiving NFTs from airdrops or as gifts.

 ![image](https://europe1.discourse-cdn.com/flex005/uploads/gov_near/original/1X/4464d9ce844d3b20ed333e6b5294802e286112fb.jpeg)

A) Some implementations in the wild have the NFT contract and markets rolled into one monolithic contract  
B) Many proposals make NFTs handle the approvals of the markets they are sold on, on a per token basis. Storage is dynamic.  
C) A proposal where many common (from the same developer) NFTs share a single “approver” contract that manages the approval of many markets where the NFT can be sold. The user can grant permissions on a per token basis. Storage for NFTs is static. Storage for approver is dynamic.

A pseudo code example of C is here:

 ![image](https://europe1.discourse-cdn.com/flex005/uploads/gov_near/original/1X/0ad542e395c6b3e4751ec1efc539d03238525add.jpeg)

# Summary

This is by no means incomplete. I am calling on the community for feedback, ideas and a healthy discussion to reach soft consensus. We must band together and quickly around the best (basic) standards so we don’t face composability issues down the road.

# Rules

- be kind, open, flexible and fair
- critique ideas not people
- ???

---

<div class="post-metadata">

**Author:** ![evgenykuzyakov](https://dub1.discourse-cdn.com/flex005/user_avatar/gov.near.org/evgenykuzyakov/32/393_2.png) [@evgenykuzyakov](https://gov.near.org/u/evgenykuzyakov)\
**Post date:** [March 7, 2021, 3:21am UTC](https://gov.near.org/t/nft-standard-discussion/853/2 "2021-03-07T03:21:38Z")

</div>

As we discussed before, the `nft_transfer(new_owner_id: AccountId, token_id: TokenId)` might need to have a field to enforce the previous owner if it’s called by escrow. Because the owner might have changed when after escrow issued a transaction, and the new owner can set the same escrow account again. This will allow the transfer to complete from the new owner account which is not desired behavior.  
So whenever we’re dealing with any approval system, we have to make sure the escrow contract is acting on behalf of the specific owner.

Example of the problem:

1. `token1` is owned by `alice`
2. `alice` sets `approved_escrow_id` to `escrow`
3. `alice` list token on the market through `escrow`
4. `alice` transfer token to `bob` (this resets `approved_escrow_id` to `null`)
5. `bob` sets `approved_escrow_id` to `escrow` (they use same escrow contract)
6. market sells token for `alice` through `escrow`, `escrow` calls token contract `nft_transfer(new_owner_id: "charlie", token_id: "token1")`
7. this gets approved, because `token1` has `approved_escrow_id == "escrow"`

Solution.

Introduce `nft_transfer(new_owner_id: AccountId, token_id: TokenId, enforce_owner_id: Option<AccountId>),` which will check that the owner is `alice` at the example item #6 and fail, so the transfer will not be completed.

---

<div class="post-metadata">

**Author:** ![evgenykuzyakov](https://dub1.discourse-cdn.com/flex005/user_avatar/gov.near.org/evgenykuzyakov/32/393_2.png) [@evgenykuzyakov](https://gov.near.org/u/evgenykuzyakov)\
**Post date:** [March 7, 2021, 3:22am UTC](https://gov.near.org/t/nft-standard-discussion/853/3 "2021-03-07T03:22:41Z")

</div>

Also we prefer to use GH discussions for NEP proposals, since it allows to link code and other github issues better. E.g. [Account Storage Standard (formerly Account Registration) · Discussion #145 · near/NEPs · GitHub](https://github.com/near/NEPs/discussions/145)

---

<div class="post-metadata">

**Author:** ![mikedotexe](https://dub1.discourse-cdn.com/flex005/user_avatar/gov.near.org/mikedotexe/32/18_2.png) [@mikedotexe](https://gov.near.org/u/mikedotexe)\
**Post date:** [March 7, 2021, 5:21am UTC](https://gov.near.org/t/nft-standard-discussion/853/4 "2021-03-07T05:21:59Z")

</div>

As a note, Bowen has looked at how a couple large projects do their proposal workflow and he’s advocating that we start with the free-form discussions on this site. This is something we’re planning to address more seriously in about a month.

---

<div class="post-metadata">

**Author:** ![mikedotexe](https://dub1.discourse-cdn.com/flex005/user_avatar/gov.near.org/mikedotexe/32/18_2.png) [@mikedotexe](https://gov.near.org/u/mikedotexe)\
**Post date:** [March 7, 2021, 6:55am UTC](https://gov.near.org/t/nft-standard-discussion/853/5 "2021-03-07T06:55:53Z")

</div>

Here’s a good reference to keep, Thor made this which lines up some ERC-721 core and extensions (like metadata) and what they’d look like in Rust.

> <https://github.com/Mintbase/near-nft-standards/blob/main/ERC721_reference.md#rust-equivalent>

---

<div class="post-metadata">

**Author:** ![mikedotexe](https://dub1.discourse-cdn.com/flex005/user_avatar/gov.near.org/mikedotexe/32/18_2.png) [@mikedotexe](https://gov.near.org/u/mikedotexe)\
**Post date:** [March 7, 2021, 6:58am UTC](https://gov.near.org/t/nft-standard-discussion/853/6 "2021-03-07T06:58:11Z")

</div>

And also a NFT draft in `core-contracts` that Eugene made. I believe we’ll want to build a library similar to how we did with fungible tokens, but a good reference when reviewing current approaches. This uses the storage management standard.

> <https://github.com/near/core-contracts/pull/129/files#diff-d216d98a73d5aafc5011471e9b0fa2e190ab1e4777eb342eaf472290c007f65f>
>
> Remaining items as I see it:
> \- \[x\] Use Storage Management standard because this… uses enumeration, which adds to a new map.
> \- \[\] The \`NonFungibleTokenCore\` trait has mixed other standards in there, including approvals and enumeration. Let's separate these into different traits and corresponding files.

---

<div class="post-metadata">

**Author:** ![mikedotexe](https://dub1.discourse-cdn.com/flex005/user_avatar/gov.near.org/mikedotexe/32/18_2.png) [@mikedotexe](https://gov.near.org/u/mikedotexe)\
**Post date:** [March 7, 2021, 7:22am UTC](https://gov.near.org/t/nft-standard-discussion/853/7 "2021-03-07T07:22:13Z")

</div>

I feel like it’s a very good idea to seriously consider moving away from the unique identifier as being an autoincrementing number. I’ve brought this up a few times, after first hearing the founder of Rarible discuss it on a podcast. As mentioned by Mykle Hansen from the Telegram group, the truly unique combination would be:

**BlockchainIdentifier:ContractName:TokenType:TokenID**

If we hash this value using SHA256 and take the first 20 bytes, we can keep the size predictable. In a perfect world, this is the only unique identifier needed, but as we enter a world where bridging is likely, we may want to keep the simple autoincrementing number as well, or as optional.

`BlockchainIdentifier` could use a little thought. I believe it would be where the NFT was originally created. (Not bridged.) If the NFT was created on a Layer 1, use the token symbol of that chain. (Ex. “NEAR”) If the NFT was created on a Layer 2 solution it would be the symbol of the Layer 1 followed by a dash, and the chain ID as cataloged in [https://chainid.network](https://chainid.network). So for XDai it would be “ETH-100”

`TokenType` would be for contracts that use a multi-token approach like EIP-1155 shows here: [EIP-1155: ERC-1155 Multi Token Standard](https://eips.ethereum.org/EIPS/eip-1155#non-fungible-tokens). Perhaps `TokenType` is conditional based on if the contract uses the multi-token approach. If not, it can be omitted and instead hashes **BlockchainIdentifier:ContractName:TokenID**.

`TokenID` at the end is the autoincrementing large number that we’re familiar with. And again, for the sake of bridges and backwards compatibility with Ethereum’s ERC-721, perhaps store this as well or as optional.

In a few years when The Graph (or something similar) has indexed the various competing blockchains, having a cross-chain identifier will be tremendously useful and help avoid collisions.

Avoid this kind of scenario with the current setup where numbers are the unique identifiers… A musician starts using Ethereum and has audio NFTs. Uses a future version of the Rainbow Bridge to bring over 2-3 NFTs to NEAR. Realizes that minting on NEAR is his preferred choice, and mints several new NFTs that auto-increment a number. Another band member accidentally mints several NFTs on Ethereum and now there are competing token ID numbers to resolve.

---

<div class="post-metadata">

**Author:** ![nategeier](https://dub1.discourse-cdn.com/flex005/user_avatar/gov.near.org/nategeier/32/182_2.png) [@nategeier](https://gov.near.org/u/nategeier)\
**Post date:** [March 7, 2021, 5:11pm UTC](https://gov.near.org/t/nft-standard-discussion/853/8 "2021-03-07T17:11:23Z")

</div>

Not a fan of escrow. I’m pro keeping it simple and handling it just like Ethereum, add `function approve(address \_approved, uint256 \_tokenId) external payable;

Mintbase will be putting the burden of paying the storage of listing an item on the user listing it. An approver can only be added if an amount paid is put in. Adding a proxy for the markets adds a ton of complexity and not a direction we are heading.

We strongly believe a simple approved by token Id needs to be on the NFT near standard ASAP.

As it stands in NEP4 now, if a contract owner approves Mintbase to do this, we will be able to move all the tokens on the contract regardless if the owner of one NFT wants MIntbase to be able to transfer it whenever we please.

**Looking at your approved\_escrow\_id on the token struct**  
You still need to add storage to the contract when the token `approved_escrow_id` gets added right? It’s the same as adding to a hashmap and removing it after transfer or purchase.

or is the idea to mint it with a fake string to fill the storage? (Got it @mikedotexe filled me in on the 64-character account name)

Also, can we list on multiple markets at a time with this setup?

---

<div class="post-metadata">

**Author:** ![mattlockyer](https://dub1.discourse-cdn.com/flex005/user_avatar/gov.near.org/mattlockyer/32/56_2.png) [@mattlockyer](https://gov.near.org/u/mattlockyer)\
**Post date:** [March 7, 2021, 5:47pm UTC](https://gov.near.org/t/nft-standard-discussion/853/9 "2021-03-07T17:47:22Z")

</div>

> [@nategeier](#):
>
> You still need to add storage to the contract when the token `approved_escrow_id` gets added right? It’s the same as adding to a hashmap and removing it after transfer or purchase.
> 
> or is the idea to mint it with a fake string to fill the storage?
> 
> Also, can we list on multiple markets at a time with this setup?

Great question. Not a big fan of calling this “escrow” anymore. Instead think of this extra contract as the “approvals / approver” contract, not sure on the naming here. But it is NOT an escrow because the NFT isn’t transferred to it, ever.

The idea is each token have 1 contract that manages approvals. The approvals contract can be used by _many_ NFT contracts (Mintbase or otherwise) This can be set at time of minting e.g. all Mintbase contracts set the MintbaseApprover contract when minting tokens.

 ![image](https://europe1.discourse-cdn.com/flex005/uploads/gov_near/original/1X/cd6a8c67773972f90d5af3842fb2bd8bcff5f50e.png)

If this architecture is used then the storage for the NFT can be static and so each NFT contract will not allow owners to arbitrarily increase size of storage rent by approving multiple markets for multiple tokens.

The users only have to register for dynamic storage with 1 escrow contract to handle multiple NFTs they might own from multiple contracts (e.g. all the Mintbase contracts).

And _yes_ you can list any tokens on multiple markets with this approach, but the approvals are all managed by the “approver” contract.

---

<div class="post-metadata">

**Author:** ![mattlockyer](https://dub1.discourse-cdn.com/flex005/user_avatar/gov.near.org/mattlockyer/32/56_2.png) [@mattlockyer](https://gov.near.org/u/mattlockyer)\
**Post date:** [March 7, 2021, 5:48pm UTC](https://gov.near.org/t/nft-standard-discussion/853/10 "2021-03-07T17:48:19Z")

</div>

Will use GH for implementation, but we need proper documentation of the “high level” discussion, which is currently being lost in TG Chats and Calls.

---

<div class="post-metadata">

**Author:** ![myklemykle](https://dub1.discourse-cdn.com/flex005/user_avatar/gov.near.org/myklemykle/32/709_2.png) [@myklemykle](https://gov.near.org/u/myklemykle)\
**Post date:** [March 8, 2021, 9:01pm UTC](https://gov.near.org/t/nft-standard-discussion/853/11 "2021-03-08T21:01:40Z")

</div>

There’s various ways to encode those fields, of course. I’ve been looking at a few other efforts including [CAIP-19](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-19.md) by this group called CASA. Their standard is a little vague about TokenType, but they do add another field to describe the contract standard (“nep4”, “erc721”, etc) which is helpful. Rewriting your example in their syntax would give something like:

` BlockchainId:Standard/ContractName:TokenId`

or (maybe)

` BlockchainId:Standard/ContractName:TokenID;TokenType`

(Actually they haven’t defined how to split TokenType and TokenId, or I can’t find where they did. The semicolon is just an example. Anything other than slash or colon or reserved chars would work.)

The difference is slight, but starts to improve legibility when fields are left blank – which you can do when there’s an obvious default value for that field. For instance, a contract could leave out the BlockchainID field when referring to other contracts on the same blockchain; that’s probably the 99% case anyway. And if a token contract only knowns one type of token, TokenType has no meaning there. If you can assume defaults for Blockchain and TokenType, you get a chain-agnostic ID that looks like:

` standard/contractID:tokenID`

Or for instance:

`nep4/contract.near:340289309`

That ends up needing only slightly more storage than the non-universal version, but can be resolved universally. And the positions of the sigils make it clear what’s there & what’s missing, whereas if you did the same thing with just colons, it would be vague how to resolve it.

---

<div class="post-metadata">

**Author:** ![evgenykuzyakov](https://dub1.discourse-cdn.com/flex005/user_avatar/gov.near.org/evgenykuzyakov/32/393_2.png) [@evgenykuzyakov](https://gov.near.org/u/evgenykuzyakov)\
**Post date:** [March 8, 2021, 11:28pm UTC](https://gov.near.org/t/nft-standard-discussion/853/12 "2021-03-08T23:28:17Z")

</div>

It looks like forum doesn’t support proper threading. So it’s not as good for discussion as GH discussions.

---

<div class="post-metadata">

**Author:** ![mikedotexe](https://dub1.discourse-cdn.com/flex005/user_avatar/gov.near.org/mikedotexe/32/18_2.png) [@mikedotexe](https://gov.near.org/u/mikedotexe)\
**Post date:** [March 9, 2021, 4:48pm UTC](https://gov.near.org/t/nft-standard-discussion/853/13 "2021-03-09T16:48:44Z")

</div>

Over the weekend I was spitballing with Nate and want to clarify something. The terms “escrow” and “approvals” are, in my mind, synonymous, but I believe in this discussion “escrow” is used to refer to the separate contract keeping track of approvals. That’s okay and I can work with that. I think this is the core item we’re discussing: to separate or to not separate the approvals.

We can separate these concerns but not enforce that they live in separate contracts, letting developers do as they prefer. So you can put both standards in the same contract if you want, the same way that a fungible token has the “core” and “metadata” standards in one contract. The two standards:

1. NFT Core standard — only the basic mapping between owners and tokens, and the necessary token info, including `nft_transfer` and `nft_transfer_call`. (May or may not use Storage Management standard)
2. Approval standard — this uses the familiar approve per Token ID (which is critical and missing from NEP4) and would include the [Storage Management standard](https://nomicon.io/Standards/StorageManagement.html).

### NFT Core standard only

```auto
pub struct Token {
    pub owner_id: AccountId,
    pub metadata: String,
}

pub struct Core {
    /// TokenID » Token object
    pub tokens: LookupMap<TokenId, Token>,
    …
}

```

* * *

### NFT Core + Approval standards (same contract)

```auto
pub struct Token {
  pub owner_id: AccountId,
  pub approver_id: AccountId, // Always env::current_account_id()
  pub metadata: String,
  pub approvals: Vec<AccountId>, // Can add this field here or elsewhere
}

impl Contract {
  pub fn get_approvals(&self, token_id: String) -> Vec<AccountId> {}
  …
}

```

* * *

### NFT Core + Approval standards (separate contracts)

**NFT Core contract**

```auto
pub struct Token {
  pub owner_id: AccountId,
  pub approver_id: AccountId, // Points to another contract
  pub metadata: String,
}

```

**Approvals contract**

```auto
// NFT Contract + ID. Example: "volcano-art:19"
pub type ContractTokenId = String;

pub struct Approvals {
  pub approval_map: LookupMap<ContractTokenId, UnorderedSet<AccountId>>,
}

```

Let’s remember that standards are primarily defining the methods. What parameters they take, what functionality they do, when they should fail, what they should return. As long as the required functions on the standard are behaving in a way consistent with the spec, we can have both the options outlined above.

---

<div class="post-metadata">

**Author:** ![mattlockyer](https://dub1.discourse-cdn.com/flex005/user_avatar/gov.near.org/mattlockyer/32/56_2.png) [@mattlockyer](https://gov.near.org/u/mattlockyer)\
**Post date:** [March 9, 2021, 6:04pm UTC](https://gov.near.org/t/nft-standard-discussion/853/14 "2021-03-09T18:04:46Z")

</div>

From meeting 09-03-2021

Requirements

1. TokenID

- 
  1. Incrementing number? String? CAIPs?

- 
  1. What is returned from a view method? Can it meet a universal standard?

- 
  1. chainID:typeID:contractID:tokenID ???

1. Multiple approvals by token ID
2. NFT without approvals should be able to be simple transferred to UserX without UserX having to pre-register
3. Contract must charge for storage per approvals

- 
  1. 1 yocto NEAR anyway to redirect and require FAK (some wallet) to add approval

- 
  1. Contract manages approval storage themselves

1. Recommendation: use storage manager and refund storage for approvals

- 
  1. Won’t break interface if not followed

- 
  1. Risk of not following is Wallets won’t be able to show accurate storage user has locked per contract if not using standard storage manager (Potentially can show something but will be a bit awkward)

- 
  1. Not refunding will be viewed as a “fee” for approvals

1. DO NOT want approvals contract as “middleman” for approvals/storage management

- 
  1. Increased complexity of standard

1. Must enforce owner during transfer (e.g. ERC721 transfer\_from provides address)

- 
  1. Risk: can send 2 transfers simul.

- 
  1. One is approved but transfers are not removed

- 
  1. Second transfer is approved

- 
  1. We’ll add another field to enforce the from owner that the marketplace thinks it’s transferring from so we don’t get a race condition / second transfer

1. Bridging NFTs will require a lockup step (need for escrow contract anyway?)

- 
  1. Bridge factory?

1. Contract Level Metadata

- 
  1. Format?
