Skip to content

Instantly share code, notes, and snippets.

@talkol
Last active February 3, 2022 22:10
Show Gist options
  • Save talkol/c4ec062aa426f0e21cb6202e22b52523 to your computer and use it in GitHub Desktop.
Save talkol/c4ec062aa426f0e21cb6202e22b52523 to your computer and use it in GitHub Desktop.

TON NFT Standard - Feedback

The feedback is based on our review of TIP issue 62 and its example implementation.

Thank you for your hard work on creating a unified NFT standard! The community is learning a lot from your efforts. We have a rare opportunity to design a standard that is truly focused on delivering the best product, and is not driven by gas limitations like on other networks (ERC721 for example). We also have the opportunity to apply many lessons learned from previous implementations in the crypto community.

High level feedback

1. We propose to move all business logic from the NFT child to the NFT-Collection parent

The current proposal defines different interfaces for NFT and NFT-Collection and dictates that a collection will always be split to multiple contracts - Bored Apes for example would have 10,001 different contracts deployed. We see several disadvantages with this architecture:

  • Phishing risk - it's easier to create a fake NFT that is not really part of the collection

    The NFT market shows that the most successful NFTs normally belong to collections such as Crypto Punks or Bored Apes. Their value holds high because of the collection's floor price. Consider an attacker Alice trying to profit off of Bored Apes. This attacker deploys a new fake child NFT contract with similar interface and data and tries to sell this NFT peer to peer to user Bob by claiming it's a real part of the collection.

    Phishing is not new to blockchain and can happen with fungible tokens. Alice can easily create a fake ERC20 token named USDC and try to convince users that this is the real version in a peer to peer sale. The common method for users to protect themselves is by identifying the contract address they're transacting with. Hardware wallets for example display this address so users can verify they are dealing with the authentic instance.

    This common protection technique will not work with the existing architecture since users are transacting with the NFT child and not the collection parent. This means Bored Apes will have 10,000 different contract addresses instead of just a single one. By moving the transfer action from NFT to NFT-Collection this problem will be resolved because users will always transact with a single well known contract address.

    In the existing draft, to protect themselves from phishing, the buyer needs to call the method get_nft_address_by_index on the collection parent and compare the result to the child address, an action that only tech savvy users would probably know how to do.

  • Minting a 10,000 item collection has a high deployment cost of 500 TON

    The spec recommends 0.05 TON balance per NFT contract instance to pay rent. A 10,000 item collection like Crypto Punks will cost 500 TON to deploy. This is a very high deployment cost for the creator, particularly when it's not known in advance if a collection will succeed or not. We realize that this cost is still x500 cheaper than Ethereum, but is still a high amount that will be a barrier of entry to amateur artists.

    The cost stems from the requirement to split the collection to 10,000 contracts. If the business logic is moved from NFT to NFT-Collection, it would be possible to launch large collections that only have a single contract. The collection creator should have a choice whether to hold each child NFT content on-chain (via a separate child contract, this is more secure but expensive) or off-chain (via some more cost effective solution such as TON Storage or IPFS).

  • Difficult enumeration of all collection items belonging to a user

    Since NFT collections tends to scale to O(10,000) items, and we can expect to have many thousands of popular collections, we must be careful of certain inefficient queries. This "mistake" was made on Ethereum with ERC721 not supporting easy enumeration of all items that a user holds and requiring a specific extension to add this support. This is an important query because any wallet that wants to show the user all of the NFTs they're holding, will need to make it.

    In the current architecture, the wallet would be required to iterate over 10,000 contracts and run method get_nft_data on each to check whether the user is the owner or not. If the wallet wants to support thousands of collections, this would quickly become problematic. We can always rely on dedicated external off-chain indexes for this query (the wallet would have to rely on a centralized backend), but since it's such a basic thing for a user to know what they own, it is recommended to support this without external infrastructure. By moving the business logic to the NFT-Collection contract, it should be easy to add a get method to return a list of all items in the collection a user owns, in a single query.

We should note that we still support deploying N child contracts to hold the content only (without business logic) in the case the creator wants to store the NFT content on-chain. But this should be optional allowing creators to store content off-chain, and if content is indeed off-chain, a single collection contract would be enough.

2. We propose to add approval mechanism to support third-party marketplaces

Approval mechanisms allow different accounts or contracts to transfer ownership on a user's behalf. This mechanism is currently supported in TRC20 under the allowances action. This mechanism is currently absent from the NFT draft. We recommend to add it and support a list of approved operators due to the following disadvantages:

  • Difficulty to list an item on multiple third-party marketplaces at the same time

    In successful decentralized ecosystems there would normally be multiple marketplaces where an NFT could be listed for sale. Sellers are incentivized to maximize their potential revenues by listing on multiple marketplaces concurrently since each marketplace may have access to different potential buyers. This is similar with real estate in the real world, a seller would normally list their house for sale on multiple channels.

    This is difficult to achieve in the current architecture because in order to sell on a marketplace, the user needs to transfer the item to the ownership of the marketplace and this can be done only once. By adding a list of approved operators, the user would be able to grant this permission to multiple contracts or accounts.

We should note that approvals normally include a convenient set_approval_for_all(operator, approved) action. Suppose a user owns 30 different Bored Apes. This user could approve all of their apes for sale on OpenSea with a single transaction by using this action. This is another example where moving the business logic from the NFT child to the NFT-Collection parent is useful (our first recommendation). This action is much easier to implement when all business logic lies in the NFT collection parent.

3. We propose to move editor role actions to internal messages instead of external messages

The current proposal relies on external messages to handle priviliged roles such as content editor. We recommend to handle the roles in internal messages instead, and base authentication on checking the message sender instead of manually checking a signature. The current approach has the following disadvantages:

  • Difficulty to support multi-sig editors

    Priviliged roles that can influence the value of a token, such as the content editor, are susceptible to attack and are scrutinized by users for having too much centralized power. The classic methodology that evolved to handle this is transfering this priviliged role to be held by a multi-sig wallet. This both protects users from attacks (multiple admins need to be hacked simultaneously which is less likely) and from corruption (multiple admins need to collude so control is more decentralized).

    The current implementation relies on external messages and performs the signature authentication directly. This shouldn't be a concern of the NFT contract. By switching to internal messages and delegating this concern to a separate contract, like a wallet contract, these limitations can be avoided. The creator would be able to choose whether to deploy a regular wallet contract as the editor or a multi-sig wallet contract.

  • Difficulty to support NFT breeding pattern that relies on a minter contract

    One of the popular patterns with NFTs is breeding - which allows users to create new NFT instances in a collection by breeding multiple items they currently hold and paying a fee. We can see this pattern in popular NFT games like Axie Infinity. Breeding is often implemented via a separate contract which contains this business logic. The current implementation requires the editor role for breeding which is handled by external messages and thus cannot be held by a contract. By switching to internal messages, this scenario would be supported as well.

4. We propose to support off-chain property JSONs

NFT collections often rely on properties like skin color, shirt style, eye shape, etc to add diversity to the collection and create interesting rarity distributions that make certain items more scarce and thus more valuable. The current proposal supports storing media content (such as an image or video) off-chain, but requires that all property values will be stored natively on-chain. We recommend adding optional support for content URI pointing at some JSON stored off-chain (on a service like TON Storage, IPFS or HTTPS), due to the following disadvantages:

  • Difficulty to implement the reveal collection pattern

    A common pattern with NFT collections is launching the collection without revealing the properties of each item in the beginning. The fact that the rarity of each item is not known yet makes the market bid equally on all items and sets a price floor. After some period of speculation, the creator normally reveals the entire collection at once.

    Supporting this pattern is very easy when the properties are stored off-chain using a content URI pointing at some JSON. The collection is normally launched with a null base content URI, so all property JSONs are hidden. On reveal, the creator simply updates the base content URI to the real value - a single transaction. With the existing proposal that relies on native properties on-chain, to implement a reveal, the creator would have to update all items separately by making thousands of transactions.

  • Cumbersome interoperability with off-chain subsystems

    Most existing NFT standards on other networks and most existing creation tools today rely on JSON to encode these properties. The properties are also often parsed by multiple off-chain subsystems, for example a Unity game engine that renders a game character based on the NFT the user holds. Allowing the JSON standard will make interoperability easier in certain cases.

  • Expensive storage and complex de-duplication of property values

    Large NFT collections with thousands of items can grow in content size since property values are often quite verbose (eg. "Eyes: Green laser eyes"). Storing this information on-chain can get expensive over time due to rent costs. The current implementation tries to optimize duplications by differentiating between individual content and common content. This makes the problem less severe but doesn't solve it completely since many properties vary between items.

    Storage costs off-chain may be significantly lower or even negligible, making de-duplication unnecessary.

5. Think about supporting quantity ownership per item similar to ERC 1155

The current proposal is similar to ERC 721 in that every item can be owned by one account and the account can only own one of this item. NFT games proved that this limitation is too restrictive and games often require multiple owners and owning a large quantity. Consider for example the NFT game Star Atlas where users can purchase starships. Assume there's a collection of 5 different starships, each with a quantity of 100. Users can purchase a quantity of more than one and several different users can purchase the same exact starship. ERC 1155 was designed to support this use-case.

We may want to plan ahead for this scenario and have a single standard that supports it. The main requirement is adding a quantity argument to the transfer method. The traditional use-use of ERC 721 is implemented by using a quantity of 1. We're not certain this is a good idea, because single ownership per item is a concept you want to emphasize, this is why many new NFT collections on Ethereum are still implemented as ERC 721 although ERC 1155 is more general.

 

Proposed interface

Architecture

  • Single contract per collection holding all business logic - NFT-Collection

  • Optional N child contracts to store only item content (but no business logic). Used when the content is desired to be held on-chain (more expensive but more secure) - NFT-Content

  • Content URI is a string that designates through the protocol segment (eg. "https://") if the content is stored on-chain (on which contract address) or off-chain and which off-chain service is used (eg. TON Storage, IPFS, website)

NFT-Collection contract

External actions

  • none

Internal actions

  • transfer(from, to, index, forward_amount, forward_payload) - for both owner transfers and approved operators
  • set_approval(operator, index, approved) - adds/removes an operator for a specific item index
  • set_approval_for_all(operator, approved) - allows the owner to approve all their owned items at once
  • transfer_editorship(to, forward_amount, forward_payload) - limited to editor role
  • mint(to, content_uri) - mints a new item in the next index, limited to editor role
  • update_content_uri(index, content_uri) - changes content uri for an item index, limited to editor role

Get-methods

  • is_approved(operator, index) - is this operator approved to sell this item
  • is_approved_for_all(operator) - similar but for blanket approvals for all owned items
  • get_collection_data() - returns collection name, symbol, next index, editor
  • get_nft_data(index) - returns the owner, content URI for a specific item index
  • get_all_nfts(owner) - returns a list of all item indexes + content URIs that an account owns
  • royalty_params(index) - return numerator, denominator, and address to be paid royalties (per item index)

NFT-Content contract

External actions

  • none

Internal actions

  • none

Get-methods

  • get_content() - returns a cell containing the content according to TIP issue 64
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment