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.
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 fromNFT
toNFT-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
toNFT-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 theNFT-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.
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.
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.
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.
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.
-
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)
- none
transfer(from, to, index, forward_amount, forward_payload)
- for both owner transfers and approved operatorsset_approval(operator, index, approved)
- adds/removes an operator for a specific item indexset_approval_for_all(operator, approved)
- allows the owner to approve all their owned items at oncetransfer_editorship(to, forward_amount, forward_payload)
- limited to editor rolemint(to, content_uri)
- mints a new item in the next index, limited to editor roleupdate_content_uri(index, content_uri)
- changes content uri for an item index, limited to editor role
is_approved(operator, index)
- is this operator approved to sell this itemis_approved_for_all(operator)
- similar but for blanket approvals for all owned itemsget_collection_data()
- returns collection name, symbol, next index, editorget_nft_data(index)
- returns the owner, content URI for a specific item indexget_all_nfts(owner)
- returns a list of all item indexes + content URIs that an account ownsroyalty_params(index)
- return numerator, denominator, and address to be paid royalties (per item index)
- none
- none
get_content()
- returns a cell containing the content according to TIP issue 64