ton-blockchain / TIPs

Improvement Proposal repository
68 stars 13 forks source link

NFT Standard #62

Open EmelyanenkoK opened 2 years ago

EmelyanenkoK commented 2 years ago

⚠️ WARNING: Standards are now published and discussed in the TEPs repository. This page may be out of date.

Summary

A standard interface for non-fungible tokens.

Motivation

A standard interface will greatly simplify interaction and display of different entities representing right of ownership.

NFT standard describes:

Specification

The NFT collection and each NFT item are separate smart contracts.

Example: if you release a collection that contains 10 000 items, then you will deploy 10 001 smart contracts.

NFT item smart contract

Must implement:

Internal message handlers

1. transfer

Request

TL-B schema of inbound message:

transfer#5fcc3d14 query_id:uint64 new_owner:MsgAddress response_destination:MsgAddress custom_payload:(Maybe ^Cell) forward_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell) = InternalMsgBody;

query_id - arbitrary request number.

new_owner - address of the new owner of the NFT item.

response_destination - address where to send a response with confirmation of a successful transfer and the rest of the incoming message coins.

custom_payload - optional custom data.

forward_amount - the amount of nanotons to be sent to the new owner.

forward_payload - optional custom data that should be sent to the new owner.

Should be rejected if:

  1. message is not from current owner.

  2. there is no enough coins (with respect to NFT own storage fee guidelines) to process operation and send forward_amount.

  3. After processing the request, the contract must send at least in_msg_value - forward_amount - max_tx_gas_price to the response_destination address.

    If the contract cannot guarantee this, it must immediately stop executing the request and throw error.

    max_tx_gas_price is the price in Toncoins of maximum transaction gas limit of NFT habitat workchain. For the basechain it can be obtained from ConfigParam 21 from gas_limit field.

Otherwise should do:

  1. change current owner of NFT to new_owner address.

  2. if forward_amount > 0 send message to new_owner address with forward_amount nanotons attached and with the following layout:

    TL-B schema: ownership_assigned#05138d91 query_id:uint64 prev_owner:MsgAddress forward_payload:(Either Cell ^Cell) = InternalMsgBody;

    query_id should be equal with request's query_id.

    forward_payload should be equal with request's forward_payload.

    prev_owner is address of the previous owner of this NFT item.

    If forward_amount is equal to zero, notification message should not be sent.

  3. Send all excesses of incoming message coins to response_destination with the following layout:

    TL-B schema: excesses#d53276db query_id:uint64 = InternalMsgBody;

    query_id should be equal with request's query_id.

2 get_static_data

Request

TL-B schema of inbound message:

get_static_data#2fcb26a2 query_id:uint64 = InternalMsgBody;

query_id - arbitrary request number.

should do:

  1. Send back message with the following layout and send-mode 64 (return msg amount except gas fees):

    TL-B schema: report_static_data#8b771735 query_id:uint64 index:uint256 collection:MsgAddress = InternalMsgBody;

    query_id should be equal with request's query_id.

    index - numerical index of this NFT in the collection, usually serial number of deployment.

    collection - address of the smart contract of the collection to which this NFT belongs.

Get-methods

  1. get_nft_data() returns (int init?, int index, slice collection_address, slice owner_address, cell individual_content)

    init? - if not zero, then this NFT is fully initialized and ready for interaction.

    index - numerical index of this NFT in the collection. For collection-less NFT - arbitrary but constant value.

    collection_address - (MsgAddress) address of the smart contract of the collection to which this NFT belongs. For collection-less NFT this parameter should be addr_none;

    owner_address - (MsgAddress) address of the current owner of this NFT.

    individual_content - if NFT has collection - individual NFT content in any format; if NFT has no collection - NFT content in format that complies with standard TIP-64.

NFT Collection smart contract

It is assumed that the smart contract of the collection deploys smart contracts of NFT items of this collection.

Must implement:

Get-methods

  1. get_collection_data() returns (int next_item_index, cell collection_content, slice owner_address)

    next_item_index - the count of currently deployed NFT items in collection. Generally, collection should issue NFT with sequential indexes (see Rationale(2) ). -1 value of next_item_index is used to indicate non-sequential collections, such collections should provide their own way for index generation / item enumeration.

    collection_content - collection content in a format that complies with standard TIP-64.

    owner_address - collection owner address, zero address if no owner.

  2. get_nft_address_by_index(int index) returns slice address

    Gets the serial number of the NFT item of this collection and returns the address (MsgAddress) of this NFT item smart contract.

  3. get_nft_content(int index, cell individual_content) returns cell full_content

    Gets the serial number of the NFT item of this collection and the individual content of this NFT item and returns the full content of the NFT item in format that complies with standard TIP-64.

    As an example, if an NFT item stores a metadata URI in its content, then a collection smart contract can store a domain (e.g. "https://site.org/"), and an NFT item smart contract in its content will store only the individual part of the link (e.g "kind-cobra").

    In this example the get_nft_content method concatenates them and return "https://site.org/kind-cobra".

Implementation example

https://github.com/ton-blockchain/token-contract/tree/main/nft

Rationale

  1. "One NFT - one smart contract" simplifies fees calculation and allows to give gas-consumption guarantees.

  2. NFT collection with sequential NFT index provide easy way of association and search of linked NFTs.

  3. Division of NFT content into individual and common (collection) part allows to deduplicate storage as well as cheap mass update.

Why not a single smart contract with a token_id -> owner_address dictionary?

  1. Unpredictable gas consumption

    In TON, gas consumption for dictionary operations depends on exact set of keys.

    Also, TON is an asynchronous blockchain. This means that if you send a message to a smart contract, then you do not know how many messages from other users will reach the smart contract before your message.

    Thus, you do not know what the size of the dictionary will be at the moment when your message reaches the smart contract.

    This is OK with a simple wallet -> NFT smart contract interaction, but not acceptable with smart contract chains, e.g. wallet -> NFT smart contract -> auction -> NFT smart contract.

    If we cannot predict gas consumption, then a situation may occur like that the owner has changed on the NFT smart contract, but there were no enough Toncoins for the auction operation.

    Using smart contracts without dictionaries gives deterministic gas consumption.

  2. Does not scale (becomes a bottleneck)

    Scaling in TON is based on the concept of sharding, i.e. automatic partitioning of the network into shardchains under load.

    The single big smart contract of the popular NFT contradicts this concept. In this case, many transactions will refer to one single smart contract.

    The TON architecture provides for sharded smart contracts(see whitepaper), but at the moment they are not implemented.

Why are there no "Approvals"?

TON is an asynchronous blockchain, so some synchronous blockchain approaches are not suitable.

You cannot send the message "is there an approval?" because the response may become irrelevant while the response message is getting to you.

If a synchronous blockchain can check alowance and if everything is OK do transferFrom in one transaction, then in an asynchronous blockchain you will always need to send a transferFrom message at random, and in case of an error, catch the response message and perform rollback actions.

This is a complex and inappropriate approach.

Fortunately, all cases that arose during the discussion can be implemented by a regular transfer with notification of the new owner. In some cases, this will require an additional smart contract.

The case when you want to place NFT on several marketplaces at the same time is solved by creating auction smart contracts that first accept payment, and then NFT is sent to one of auction smart contracts.

Why are there no obligatory royalties to the author from all sales?

In the process of developing this idea, we came to the conclusion that it is possible to guarantee royalties to the author from absolutely any sale on the blockchain only in 1 case:

All transfers must be carried out through an open long-term auction, and other types of transfers are prohibited.

If you want to transfer NFT to yourself to another wallet, then you need to start an auction and win it.

Another variation of this scheme is to make all transfers chargeable.

By prohibiting the free transfer of tokens, we make tokens inconvenient in many cases - the user simply updated the wallet, the user wants to donate NFT, the user wants to send NFT to some smart contract.

Given the poor usability and that NFTs are a general concept and not all of them are created for sale - this approach was rejected.

Standard extensions

The functionality of the basic NFT standard can be extended:

TL-B schema

nothing$0 {X:Type} = Maybe X;
just$1 {X:Type} value:X = Maybe X;
left$0 {X:Type} {Y:Type} value:X = Either X Y;
right$1 {X:Type} {Y:Type} value:Y = Either X Y;
var_uint$_ {n:#} len:(#< n) value:(uint (len * 8))
         = VarUInteger n;

addr_none$00 = MsgAddressExt;
addr_extern$01 len:(## 9) external_address:(bits len) 
             = MsgAddressExt;
anycast_info$_ depth:(#<= 30) { depth >= 1 }
   rewrite_pfx:(bits depth) = Anycast;
addr_std$10 anycast:(Maybe Anycast) 
   workchain_id:int8 address:bits256  = MsgAddressInt;
addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) 
   workchain_id:int32 address:(bits addr_len) = MsgAddressInt;
_ _:MsgAddressInt = MsgAddress;
_ _:MsgAddressExt = MsgAddress;

transfer query_id:uint64 new_owner:MsgAddress response_destination:MsgAddress custom_payload:(Maybe ^Cell)  forward_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell)  = InternalMsgBody;

ownership_assigned query_id:uint64 prev_owner:MsgAddress forward_payload:(Either Cell ^Cell) = InternalMsgBody;

excesses query_id:uint64 = InternalMsgBody;
get_static_data query_id:uint64 = InternalMsgBody;
report_static_data query_id:uint64 index:uint256 collection:MsgAddress = InternalMsgBody;

Tags were calculated via tlbc as follows (request_flag is equal to 0x7fffffff and response flag is equal to 0x80000000):

crc32('transfer query_id:uint64 new_owner:MsgAddress response_destination:MsgAddress custom_payload:Maybe ^Cell forward_amount:VarUInteger 16 forward_payload:Either Cell ^Cell = InternalMsgBody') = 0x5fcc3d14 & 0x7fffffff = 0x5fcc3d14

crc32('ownership_assigned query_id:uint64 prev_owner:MsgAddress forward_payload:Either Cell ^Cell = InternalMsgBody') = 0x85138d91 & 0x7fffffff = 0x05138d91

crc32('excesses query_id:uint64 = InternalMsgBody') = 0x553276db | 0x80000000 = 0xd53276db

crc32('get_static_data query_id:uint64 = InternalMsgBody') = 0x2fcb26a2 & 0x7fffffff = 0x2fcb26a2

crc32('report_static_data query_id:uint64 index:uint256 collection:MsgAddress = InternalMsgBody') = 0xb771735 | 0x80000000 = 0x8b771735

Changelog

01 Feb 2022 02 Feb 2022 04 Feb 2022 08 Feb 2022 11 Feb 2022 30 Jul 2022

tolya-yanot commented 2 years ago

We are grateful to the TonWhales developers for collaborating on the current draft of the standard 🤝

HEHYHAX commented 2 years ago

Весьма неплохо🤔

AlexeyPryanishnikov commented 2 years ago

What is the reason to expect individual_content in member_content() method? Looks like this will only lead to unnecessary get_nft_data call and\or bigger payload on every full_content retrieval

KarabutinAlex commented 2 years ago

I have a few points:

1) It's not clear how people can use forwarding funds functionality. Could you please provide a few use-cases?

2) It's worth describing a mechanism of interaction with NFT from the point of third-party services (e.g., marketplaces). For example, ERC-721 supports permitting a token transfer (via method "approve(operatorAddress)"). We could introduce a role "operator" who could transfer a token, a method for permitting this role (e.g., permit - query_id:uint64 operator:MsgAddress), and an appropriate notification.

Thank you.

EmelyanenkoK commented 2 years ago

What is the reason to expect individual_content in member_content() method? Looks like this will only lead to unnecessary get_nft_data call and\or bigger payload on every full_content retrieval Idea is in separation of common and individual part of the content and keeping all contracts as small as reasonable and thus highly predictable in terms gas usage, which is important for ability of transactional behavior of financial operations.

Artificial example is https://emelyanenkok.github.io/nft-standard-draft/ where each nft stores it's own image onchain and nft-collection stores common png-header as well as uri prefix.

It is worth noting, by the way, that TVM execution is highly local (almost no context is used). That means that it is possible to download code/storage of collection contract and then run member_content locally.

EmelyanenkoK commented 2 years ago

I have a few points:

1. It's not clear how people can use forwarding funds functionality. Could you please provide a few use-cases?

2. It's worth describing a mechanism of interaction with NFT from the point of third-party services (e.g., marketplaces). For example, [ERC-721](https://eips.ethereum.org/EIPS/eip-721) supports permitting a token transfer (via method "approve(operatorAddress)").
   We could introduce a role "operator" who could transfer a token, a method for permitting this role (e.g., `permit` - `query_id:uint64 operator:MsgAddress`), and an appropriate notification.

Our thoughts on these 2 questions are actually coupled: asynchronous nature of TON requires to keep in mind that it is very dangerous to rely on volatile data. Indeed if contract asynchronously ask NFT is it an owner by the time response arrives at caller the answer may become incorrect. That way all this permitting-based flows check-and-then-send familiar for ethereum developer (when in one tx we can actually check ownership or correct transfer and then start operation if correct) not very compatible to TON reality. Instead flow should looks like send-and-request-processing: for instance if there is auction contract and you want to init the sale you need to send that nft to auction (it becomes a temporal owner) with forward_payload which will tell auction what to do with obtained NFT (start sale for instance).

KarabutinAlex commented 2 years ago

Thank you, @EmelyanenkoK, for your clarification. I got the idea.

Following this approach, it's is essential for service contracts (e.g., an auction) to know where the token came from. However, we can achieve it by putting the sender's address in the body of the transfer notification. In my opinion, this may be a widespread case, and to avoid liberties of interpretation, I would add the sender's address to the body (next to forward_payload) to standardize it.

Please correct me if I'm missing something.

LaDoger commented 2 years ago

Hey🤗 Would you consider allowing the contract to also mint fungible tokens? The end result would be similar to ERC-1155, multiple FTs and NFTs issued by one single contract and with shared properties.

(I'm referring to the collection contract design by the way)

CipPlant commented 2 years ago

Hello, TON! I have a team and we are making a grate project for your NFT marketplace. We are doing it for improving your future system. Need your comment for our common thoughts, write me in Telegram, @cipplant, thank you!

GilgameshH0 commented 2 years ago

БУНТ БУНТ БУНТ! ТРЕБУЕМ БЕСПЛАТНЫЕ NFT! БУНТ БУНТ

konak commented 2 years ago

upon receiving internal message with specified layout not from current owner contract should revert or send back message with 0xfffffffe op-code; Maybe it will be useful to send a notification message to the owner too ? Maybe not on the first try but on continuous ones.

AaronMbuzi commented 2 years ago

Awesome. hope those of us in the ton network can get some free or cheap nft samples

konak commented 2 years ago

Will it be useful to store the history of NFT changes, so editing will not change the content of current the NFT, but will provide a new one with new data? So there will be some kind of property like 'serial' (or 'version') which will indicate the last version of NFT.

konak commented 2 years ago

Will there be any cases where content of the NFT will not be acceptable by the community ? For example any porn content, some kind of nazi propaganda or drugs adware ? Will there be any possibility to block content of NFT ? Or at least be marked as 18+ content ?

DocInn commented 2 years ago

Навряд ли надо блокировать само содержимое NFT, скорее будет блокироваться его распространение на площадках, которые смогут блокировать адрес этого NFT. А содержимое NFT - это собственность его владельца.

Google Translate to English: It is hardly necessary to block the content of the NFT itself; rather, its distribution on sites that can block the address of this NFT will be blocked. And the contents of the NFT are the property of its owner.

easmith commented 2 years ago

What about bulk transfer? What is NFT ID?

konak commented 2 years ago

Наврядли надо блокировать само содержимое NFT, скорее будет блокироваться его распространение на площадках, которые смогут блокировать адрес этого NFT. А содержимое NFT - это собственность его владельца.

Его собственность, которую как минимум нужно соответственно промаркировать ... Как минимум 18+ ... (adult content)

DocInn commented 2 years ago

Маркировать NFT могут как раз площадки, на которых они будут размещаться. Никто не может обязать меня ставить маркировку на своем NFT. Например Ютуб и соцсети сами фильтруют такой контент.

Google Translate to English: NFTs can be labeled just by the sites where they will be placed. No one can force me to label my NFT. For example, YouTube and social networks themselves filter such content.

DocInn commented 2 years ago

Предлагаю добавить в NFT возможность просмотра контента, ограниченного доступа и доступа по подписке списку указанных адресов, которые владелец NFT сможет менять (аренда NFT по установленной цене). Это хорошо привлечет к NFT творческих людей.

Google Translate to English: I propose to add to the NFT the ability to view content, limited access and subscription access to a list of specified addresses that the owner of the NFT can change (rent the NFT at a set price). This will well attract creative people to NFT.

konak commented 2 years ago

Навряд ли надо блокировать само содержимое NFT, скорее будет блокироваться его распространение на площадках, которые смогут блокировать адрес этого NFT. А содержимое NFT - это собственность его владельца.

А как насчёт вируса ? На сколько я понял payload - всего лишь набор байтов никаким форматом не ограниченный.

EmelyanenkoK commented 2 years ago

@konak @DocInn Please use English. Blocking of NFT onchain is impossible and undesirable. We do not want any entity with such authority. Indeed content display should be blocked (if desired) on clients: marketplaces, explorers, nft-wallets.

@easmith

What about bulk transfer?

Each NFT is separate contract and thus should be transferred by distinct message. At the same time wallet or contracts may send multiple messages per transactions so it is possible to send request to change owners of many NFT.

@konak

Maybe it will be useful to send a notification message to the owner too ?

What is the usecase of notification owner about unsuccessful attempt to move his NFT?

@LaDoger Fungible and semi-fungible tokens will be the subject of other standard.

DocInn commented 2 years ago

Распишу ещё желательные возможности NFT в сети TON.

  1. Владение одним NFT несколькими собственниками в указанных пропорциях (доли недвижимости, доли организаций и т.д.)
  2. Возможность продажи или передачи своей доли NFT.
  3. Возможность одному NFT быть собственником других NFT. (возможно уже реализовано, в других проектах есть)
  4. Возможность создавать NFT со сроком действия, после которого NFT уничтожается или перестает быть актуальным. (использовать для этого Редактор?)

Google Translate to English: I will also write down the desirable features of NFT in the TON network.

  1. Ownership of one NFT by several owners in the specified proportions (shares of real estate, shares of organizations, etc.)
  2. Ability to sell or transfer your NFT share.
  3. The ability for one NFT to be the owner of other NFTs. (possibly already implemented, in other projects there is)
  4. The ability to create NFTs with an expiration date, after which the NFT is destroyed or ceases to be relevant. (use an editor for this?)
KarabutinAlex commented 2 years ago

Thank you, @EmelyanenkoK, for your clarification. I got the idea.

Following this approach, it's is essential for service contracts (e.g., an auction) to know where the token came from. However, we can achieve it by putting the sender's address in the body of the transfer notification. In my opinion, this may be a widespread case, and to avoid liberties of interpretation, I would add the sender's address to the body (next to forward_payload) to standardize it.

Please correct me if I'm missing something.

One more thing. I've noticed the current implementation of the NFT Collection contract supports updating NFT code. On the other hand, calculating the token address looks like hash(code + initial_data + token_index). It means that after updating the NFT code, the method get_nft_address_by_index will return an address of a non-existing token.

EmelyanenkoK commented 2 years ago

@KarabutinAlex Standard does not cover logic of get_nft_address_by_index. In the case which you mentioned, obvious solution will be to store code of both nft versions as well as index threshold at which code has changed. That way for indexes before threshold will be used v1 and after threshold v2.

EmelyanenkoK commented 2 years ago

Standard update:

Naltox commented 2 years ago

Pretty cool stuff, guys!

I have some thoughts on smart contracts in TON and NFT contracts in specific:

I think we need some kind of mechanic's to distinguish some contracts from others. In other term i think we need some way to look at the contract code & data on blockchain and distinguish that it's an NFT contract (or some other contract implementing some other well-known interface). In case of NFT's this stuff will be useful for marketplaces to find new NFT's deployed to network. It's also useful for blockchain explorers, we can implement some UI to interact with contracts (just like etherscan did)

I see three possible solutions for this stuff:

1 - each contract that wats to follow introspection mechanics implements some well-known GET method which returns either TL-B schema of it's interface or hash of that schema or some other data which uniquely defines interfaces of contract. This approach was also described in original paper on TON ( 4.3.14 at https://newton-blockchain.github.io/docs/ton.pdf ) Actually we already have some kind of proposal for this: https://github.com/tonwhales/specs/blob/master/specs/tws-0001.md

2 - instead of well-known GET method we can put information about contract interface at the beginning of smart contract data cell.

3 - We can use some indirect signs to distinguish contracts. For example we can locally run contract and test it against list of GET and internal methods that contract should implement.

Since 1-2 don't actually guarantee anything - we can use some sort of combination of 1/2 + 3. This approach have some benefits too: Lets say we are searching blockchain for new NFT's, in that case we can firstly invoke magical GET method on contract and weed out contracts that definitely do not implement NFT interface. After we found contract that claims to be NFT we can run test as described in p. 3 to be confident that it's a genuine NFT contract.

LevZed commented 2 years ago

for instance if there is auction contract and you want to init the sale you need to send that nft to auction (it becomes a temporal owner) with forward_payload which will tell auction what to do with obtained NFT (start sale for instance).

I don't know much about smart contracts, but how do I know what the auction contract will do with my nfts? This is not a problem for "verified" marketplaces, but how to trust unverified ones? The owner of marketplace, at any time, can simply write a smart contract that will send nft to his address sorry if i misunderstood

ru39 commented 2 years ago

Было бы крутой фичей для создателей если бы в смартконтракте NFT, по желанию можно было задать % и кошелёк, на который в автоматическом режиме отчислялся бы комиссионный процент от любых продаж или перепродаж.

Google Translate: It would be a cool feature for the creators if in the NFT smart contract, if desired, it was possible to set % and wallet, to which a commission percentage from any sales or resales would be automatically deducted.

atapin commented 2 years ago

From POV of Marketplace developer, having a way to fetch royalties and fees from the chain is great timesaver both for collection owners and marketplaces.

tontechio commented 2 years ago

How to migrate NFT between collections? For example, NFT stands for Mona Lisa, Collection stands for museum. Mona Lisa is able to change museum during it's lifecycle - on sale, on exhibition, on gift, on contribution, etc

konak commented 2 years ago

Извиняюсь если что не так сейчас сморожу, новичок в крипте, но скоро должен начать с ней работать ...

Вопрос:

А почему под NFT подразумеваются только картинки (2d графика, фотки, 3d анимация) ? Ведь исходя из названия это может быть любой контент ... не взаимозаменяемый ... Может тогда изначально построить архитектуру исходя из любого контента ?

Тогда на базовом уровне описания NFT нужно только:

На втором уровне создаём уже типизированные NFT (наследники от базового класса)

что-то подобное просто для обсуждения опубликовал сюда:

https://github.com/konak/ton-discusson-example

Чтобы избежать проблем с параллельными процессами TON -а может куплю продажу / передачу осуществлять через кошельки с несколькими подписями ( multi signature wallet ) ? Когда все участники, в рамках общего кошелька убедятся в наличии ресурсов, каждый своей подписью подтвердит вывод своих ресурсов:

На всякий случай отмечу, перед сделкой, покупатель в общий кошелёк зачисляет криптовалюту, продавец NFT ... Либо в случае аукциона, чуть по сложнее, аукцион <==> продавец один кошелёк аукцион <==> покупатель второй общий кошелёк, потом уже имплементация на аукционе ...

В рамках кошелька с множественными подписями можно так же осуществить и групповое владение NFT ...

Таким образом купля / продажа NFT сводится к работе с кошельками, которая в TON должна быть реализована.

В документе и обсуждении я не увидел как должна происходить проверка на уникальность и выявление дубликатов .. Думаю для этого у NFT должно существовать два параметра: payload_size и payload_hash. При совпадении этих двух параметров можно считать содержимое токена дублирующим. (хотя здесь есть с чем поспорить и в конечном итоге возможно придётся всё же сравнивать содержимое ...)

А вопрос редактирования ресурса наверное должен решаться созданием уже третьего / четвёртого уровня ресурсов типа:

Не знаю как хранятся физически NFT в блокчейне, но думаю при редактировании NFT, должен создаваться новый экземпляр, а старый, исходя из реализации очередного уровня архитектуры будет либо затираться (сжигаться), либо сохраняться как очередная версия изменения ...

Если где не прав извините ! Пока не знаю как это всё работает, но рано или поздно должен войти в этот процесс, так как подобные задачи запланированы.

KarabutinAlex commented 2 years ago

I have some thoughts about the existing NFTCollection method royalty_params().

So my proposal is to add two extensions for NFT-collection and NFT, accordingly:

These extensions extend a smart contract with the next get-method:

((int,int), slice) royalty_params()
EmelyanenkoK commented 2 years ago

Standard update:

EmelyanenkoK commented 2 years ago

@Naltox Probably, in your specification CRC-16 should be read as CRC-32? What is the maximal number of interfaces which can be practically supported by contract (have you any stats from other blockchains)? What is the order of expected number of different interfaces? Do we afraid interface ID collision? Straight-forward enumeration looks excessive (especially if we have a lot of mutually coexisting standard extensions as with nft). Since we are not going to trust interface self-declaration but formally check it, may be some Bloom-filter with variable data length is appropriate here? For instance when contract support 10 interfaces it should return 256bit filter, 10-100 512bit filter etc up to 1023 (numbers were guessed arbitrarily, some calculations are required here). @LevZed Well if you need to make some complex-logic actions with you nft you need to check or trust programs and clients which implement that logic. @KarabutinAlex @atapin @ru39 NFTRoyalty extension was introduced for issues you have mentioned. @tontechio Collection as we understand it stand for some common "by right of birth". In you example Mona Lisa should be in Leonardo da Vinci collection, and Louvre should be it's owner. So transfer of NFTs between collection is not provided.

P.S. Take a look at the NFT data standard https://github.com/ton-blockchain/TIPs/issues/64

Dimitreee commented 2 years ago

What if nft can be soulbound? ref's to https://vitalik.eth.limo/general/2022/01/26/soulbound.html

talkol commented 2 years ago

Thank you for your hard work!

Our team spent a long time analyzing this proposal and previous NFT implementations in the TON community and other chains. Our feedback is a little too long to fit in a comment, so I put it in a github gist.

We will be very happy to join any working group discussion on the standard and help hands-on wherever we can!

Our feedback:

https://gist.github.com/talkol/c4ec062aa426f0e21cb6202e22b52523

EmelyanenkoK commented 2 years ago

Standard update:

EmelyanenkoK commented 2 years ago

@Dimitreee basic property of tokens is transferability. While it is interesting to think what can be token with no transferability, it is out of the framework of current standard. @talkol 1. 2. Please check updated Rationale section in standard. There are some explanations of our concerns related to central ownership database and approvals. You can also check this thread, both question were raised and discussed. Note, that since NFT-code will be stored in libraries and common part of content will be stored in collection there will be almost no additional cost for duplicated data storage. 0.05 TON per NFT is required for storing of NFT data. If you move all NFTs into one contract you still need the comparable amount of TONs for storing in centrally, however management of storage fees between all owners will be much more complicated. 3. NFT creation is not covered by standard. That means that collection may create nfts receiving both internal and external requests. NFTs even can be created without interaction with collection (it will be a little bit harder, though). 4. Check https://github.com/ton-blockchain/TIPs/issues/64, offchain data storage is supported in NFT data storage contract. 5. Semi-fungible tokens will have it's own standard.

NoelJacob commented 2 years ago

You could add a field for the hash of the NFT or its properties JSON file if it is stored off-chain. Then the off-chain NFT cannot be modified without also modifying its on chain hash. Otherwise the buyer will have to trust the off-chain source.

EmelyanenkoK commented 2 years ago

@NoelJacob Probably it is the issue related to #64 NFT Data Standard?

talkol commented 2 years ago

@EmelyanenkoK

re. Approvals

Maybe this should be moved to a future standard extension. The interesting product use case is allowing the single owner to list their NFT for sale on multiple marketplaces. It's not exactly co-ownership because these marketplaces are not really owners.

I understand cross contract messages make this harder but the following pattern can work: approved marketplace attempts to resell (transfer) the nft, if the message rejects (due to ownership race), the marketplace will be notified of the bounce and refund the potential buyer.

If you want, we can help with a proposed implementation of this extension. Supporting marketplaces will expedite adoption

talkol commented 2 years ago

re. Big unitary contract vs many small contracts

Thanks for clarifying the rationale.

Sharding is a spectrum. Shard too little, we will have congestion. Shard too much, some actions become cumbersome. The question is what's the ideal shard size here - a single item or a collection. If we're optimizing for collections with millions of items like Axie Infinity item per shard makes sense. If we're optimizing for small <10K collections, the congestion on the entire collection will likely be lower than on most single fungible tokens. I'm worried for example about listing all items that belong to a user. This is a very basic operation that will require off-chain indexes to implement if we shard too aggressively in day 1.

Decidability and unbounded structures is indeed super tricky under the ton paradigms. It's worth to mention that a simple single collection contract with off-chain data and off-chain content will behave very similarly in this regard to the current NFT collection standard. Existing standard has item_index->child_address which is bounded by num items. The single collection approach will have item_index->owner_address and item_index->content_uri which are same order of magnitude.

AlexeyPryanishnikov commented 2 years ago

What is the reason to expect individual_content in member_content() method? Looks like this will only lead to unnecessary get_nft_data call and\or bigger payload on every full_content retrieval Idea is in separation of common and individual part of the content and keeping all contracts as small as reasonable and thus highly predictable in terms gas usage, which is important for ability of transactional behavior of financial operations.

Artificial example is https://emelyanenkok.github.io/nft-standard-draft/ where each nft stores it's own image onchain and nft-collection stores common png-header as well as uri prefix.

It is worth noting, by the way, that TVM execution is highly local (almost no context is used). That means that it is possible to download code/storage of collection contract and then run member_content locally.

The problem here is if someone (say - some third-party) wants to retrieve common part only, they must still obtain individual content first (1-st request to NFT smart-contract or internal storage) and only then query for a full content (2-nd request to NFT-collection smart-contract) - this may provoke useless additional computations and data sending (does not matter, onnet or offnet).

Furthermore, NFT-collection contract should not store any individual content (or we will end up with a data duplication), thus sending such content within the query is absolutely useless: it will be either dropped either (best case which is still not good enough) returned back to the sender "as is" as a part of the full content. So actually, absolutely any data could be sent as an individual content - NFT-collection contract just can not validate it's correctness by any mean. I would suggest at least to think about member_content(int index) which returns common_content part from NFT-collection.

AlexeyPryanishnikov commented 2 years ago

p.s. someone, say "happy new year" to the changelog dates, as they are still in 2021

EmelyanenkoK commented 2 years ago

@AlexeyPryanishnikov consider the case when full_content is not just concatenation of common_content and individual_content (and it will be this way if you consider #64). There could be arbitrary complex logic of building full_content. As I understand you propose to give methods to retrieving common content and individual content and then give the client ability to build full content himself. Our architecture allows (at least in theory) the same thing but in more convenient packaging: client retrieve individual content, then retrieve collection (that means both common content and logic of full content) and then locally build full content by locally executing collection get_method.

EmelyanenkoK commented 2 years ago

Current standard does not guarantee that forward_amount will be sent. Indeed, behavior when there is not enough coins to send forward_amount is undefined. In most simple case (see code of nft draft), execution will be silently reverted. Besides, current scheme does not allow to precisely control balance of nft. We suggest to add additional 1-byte flag before forward amount with the following behavior:

web3judy commented 2 years ago

VALIDATE_ACCOUNT_STATEcannot deserialize account proofinvalid bag-of-cells failed to deserialize cell #34 [Error : 0 : First depth mismatch in a MerkleProof special cell]

what causes this?

metadiel commented 2 years ago

Transfers in ERC-721 may be initiated by:

Current standart allows only transfers initiated by the owner. Is there a way to use this NFT standart in broker/auction/nft-market applications ?

EmelyanenkoK commented 2 years ago

Standard update:

EmelyanenkoK commented 2 years ago

Standard update: