aeternity / AEXs

Aeternity expansions repository — application layer standards
10 stars 25 forks source link

Non-Fungible Token standard #141

Open arjanvaneersel opened 3 years ago

arjanvaneersel commented 3 years ago

Discussions-To:

https://forum.aeternity.com/t/aeternity-nft-token-standard/9781/16

Content URL

https://github.com/aeternity/AEXs/blob/master/AEXS/aex-141.md

mradkov commented 3 years ago

Can you update the specification doc to also have a full interface code block for easier readability?

marc0olo commented 3 years ago

thanks for your proposal, @arjanvaneersel!

I agree with @mradkov it's currently a bit hard to read.

suggestions / thoughts from my side for the moment:

I think it makes sense to be compliant to ERC-721 for the most common and basic functions. if some things don't make sense we could drop them or if we have other ideas we could add them.

opinions about that?

arjanvaneersel commented 3 years ago

@mradkov @marc0olo Included a full code block of the interfaces in the doc, as well as in this issue.

Thanks for the suggestions.

@marc0olo The idea of MetaInfo is to be similar to the Fungible Token. TokenData is typically for the NFT standard. For that reason I didn't combine them. Also TokenData is an option, not every NFT implementation might need it. Therefore I'm also having some doubts whether it really belongs in MetaInfo if MetaInfo is to be considered a standard by itself and to be similar to the FT. @mradkov, What is your opinion? I implemented an FT compatible MetaInfo based on your suggestion.

I agree with all other suggestions of @marc0olo. I will meanwhile dive into AEX-9 to see how things were done there.

marc0olo commented 3 years ago

personally I am not sure if a NFT (contract/collection) needs a symbol. IMO a name of the collection is sufficient. also saw some discussions about that for ERC-721 back then.

I think it's way more important to think about how we want to deal with the "real" metainfo (properties of the specific NFT ID)

marc0olo commented 3 years ago

we should keep the discussion alive here. will hopefully find some time again to have a deeper look.

@arjanvaneersel are you planning to provide an update or are you waiting for further responses?

@thepiwo maybe you can also share some thoughts here? would appreciate it :)

marc0olo commented 3 years ago

@arjanvaneersel btw I wouldn't recommend to duplicate the proposed interface on various places (issue description, separate file, ...)

we should discuss a proposal which is written in one specific place IMO. the request of @mradkov, when I understood correctly, was about improving the readability of https://github.com/appinesshq/ae-nft/blob/main/AEX-xx.md (@mradkov correct me if I am wrong 😅 )

arjanvaneersel commented 3 years ago

My planning was to start implementing the first batch of ideas from next week. Think everybody then had a reasonable amount of time to share ideas.

On Thu, Sep 23, 2021, 16:26 Marco Walz @.***> wrote:

we should keep the discussion alive here. will hopefully find some time again to have a deeper look.

@arjanvaneersel https://github.com/arjanvaneersel are you planning to provide an update or are you waiting for further responses?

@thepiwo https://github.com/thepiwo maybe you can also share some thoughts here? would appreciate it :)

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/aeternity/AEXs/issues/141#issuecomment-925815654, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABKAWCZLKCWA5FDIRSQAERDUDMTJPANCNFSM5D2RT6XQ .

thepiwo commented 3 years ago

I'd say to make the code and readme style more inline with other ae contracts and AEXs:

Whats the reason to have the safe_ methods? Please include an example for onNFTReceived. Why forbid owner to be removed as minter? Why default to 0 for balance_of? Why does burn make the NFT contract owner of the NFT, not actually burn? Why consider 0 address for approvals? Why does minting need a id passed? Can you explain how approvals work and what they are used for? Tests should cover all functions, requires and edge cases known (for final implementation).

Thats the things I found for now, haven't checked all function calls and checks in detail.

marc0olo commented 3 years ago

thanks @thepiwo! currently you shouldn't care about the actual implementation. we should agree upon the interface before digging deep into the implementation details.

but anyway there are interesting questions.

what's your opinion about having mintable as extension? IMO it shouldn't be part of the default standard

marc0olo commented 3 years ago

@arjanvaneersel any update here from your side? we decided in our workshop that @thepiwo and I want to actively support you here and get this standard and the implementation ready to be reviewed and tested as soon as possible. do you have some time next week to have a call to discuss this? we can do that on discord ;-)

marc0olo commented 3 years ago

Summary (Call between @marc0olo and @thepiwo)

Metadata

We definitely need to discuss how to deal with metadata on item level:

Maybe use an algebraic data type (ADT) for metadata? Simple example:

@compiler >= 4

contract NFT =

  datatype metadata = Url(string) | Ipfs(string) | Internal(map(string,string))
  type state = map(int, metadata)

  entrypoint init(): state = {}

  stateful entrypoint mint(meta : metadata) = put(state{[Map.size(state)] = meta})
  entrypoint get_metadata(id : int): option(metadata) = Map.lookup(id, state)
marc0olo commented 3 years ago

just had some discussion with @omar-saadoun about that. the metadata topic is really interesting.

in the end we both came to the conclusion that it's definitely desired to have following possibilities:

the question is how to ideally reflect this in the contract. maybe we even want to have the possibility to add metadata "internal" in the contract (e.g. stored in a map) like mentioned above. @thepiwo any suggestions here? especially as we want to avoid having a custom URL for each NFT. it totally makes sense for the URI to introduce a baseUrl instead of having a unique URL for each NFT.

@omar-saadoun please edit / comment with additional feedback

thepiwo commented 3 years ago

@marc0olo I agree, using base_url makes sense as long as it is changeable by an administrator, each unique token id will stay the same. Putting additional data in ipfs per unique token makes sense to have optionally I think.

marc0olo commented 3 years ago

yeah. but when assuming we use a base_url my question is how would we best reflect this e.g. in the ADT (if we aim to use it like e.g. mentioned above).

should we use Url(bool) instead of Url(string) then? but if the type is Url why would I need the bool here then? I am just thinking out loud at the moment 😅 - wondering how it makes sense for the services/interfaces to consume this kind of data best from the contract

marc0olo commented 3 years ago

Summary Call between @arjanvaneersel and @marc0olo

How to deal with metadata

// on contract level define what metadata_type the contract uses
datatype metadata_type = URL | IPFS | OBJECT_ID | MAP

// on item level set the metadata for the metadata_type (only allow the correct one)
// for URL we don't need to provide anything 
datatype metadata = Url(string) | Ipfs(string) | ObjectId(string) | Internal(map(string,string))

Init function (deployment)

stateful entrypoint init(type: metadata_type, base_url: option(string)) = {
        metadata_type = type
        // check for and set base_url if metadata_type is URL
        ...                                  }

Mint function

stateful entrypoint mint(meta: option(metadata)) = {
       // check for correct type
       // for metadata_type URL we don't need to pass anything as the `base_url` + `id` forms the url
       //    => thus optional
       // for Ipfs, ObjectId and Internal metadata needs to be provided
       // increment id automatically
       ...
                                            }

Get metadata

entrypoint get_metadata(id : int): option(metadata) = Map.lookup(id, state)
omar-saadoun commented 3 years ago

Could you elaborate more about swappable @marc0olo @arjanvaneersel ?

marc0olo commented 3 years ago

Could you elaborate more about swappable @marc0olo @arjanvaneersel ?

Kind of like it's defined in the AEX-9 fungible token standard already, see https://github.com/aeternity/AEXs/blob/master/AEXS/aex-9.md#extension-swappable-swappable.

We could maybe also define different swap target chains and respective contract addresses like e.g. AETERNITY with ct_..., ETHEREUM with 0x..., POLYGON with 0x... (and others) so that consumers know the new chain and respective address to be used for the contract.

It's basically an extension that could be used if the developers want to consider a migration to a new chain and/or contract. At least from my understanding.

Maybe @thepiwo and @mradkov can elaborate a bit more on the thoughts about this for AEX-9 back then.

But we haven't put much thoughts into this right now. Not sure if it's that important. But definitely an interesting topic that might be useful for some people.

thepiwo commented 3 years ago

the goal of swappable is to provide a mechanism to swap AEX-9 to a different token standard in the future

arjanvaneersel commented 2 years ago

Proposal is updated. Please share your thoughts.

marc0olo commented 2 years ago

Hi @arjanvaneersel,

thanks for the update! I just had a very quick look and will dig in deeper during the day.

Following comments right now:

What do you guys think? cc @thepiwo @mradkov

arjanvaneersel commented 2 years ago

@marc0olo It's not a map with token holders, but a map with token data in meta_info. I remember from the discussion that most people, including you according your comment of the 14th of September, found it more logical to have this data in meta_info than outside of it, as it was before.

marc0olo commented 2 years ago

ahh okay, got it. I remember

I would propose to change the naming here then as follows and I still think the token_data is specific to each token and shouldn't be included in the general metainfo.

Currently

record meta_info = 
        { name: string
        , symbol: string 
        , base_url: option(string)
        , metadata : metadata_type
        , token_data: map(int, metadata)}

Proposal

record meta_info = 
        { name: string
        , symbol: string 
        , base_url: option(string)
        , type : metadata_type}

Note:

arjanvaneersel commented 2 years ago

@marc0olo I agree with you and what you describe is how I implemented it initially. Amended the proposal and removed token from meta_info. I don't know why I called type metadata in the first place. Must be a mistake. Changed that as well in the proposal.

marc0olo commented 2 years ago

one thing more I just observed when having a quick loog again. I think we currently miss the bool to indicate approval/revocation in the single Approval event:

thepiwo commented 2 years ago

@marc0olo can you elaborate why a metadata_type is needed if we use an ADT for metadata anyway?

thepiwo commented 2 years ago

@arjanvaneersel thanks for your work, I think the overall proposal looks good now. If we all agree that the current state of the proposal (excl. implementation) is good now I would like to make an in-depth review where I again check every type, throw and so on.

arjanvaneersel commented 2 years ago

@thepiwo The idea of metadata_type is that the type is determined at contract level instead of having different tokens with different metadata. The ADT on metadata level is mostly there due to the different kind of types that the metadata types require. Perhaps it's better to abstract metadata down to simply string or map, because I can imagine that how it's formulated now might be confusing. Any thoughts?

thepiwo commented 2 years ago

@arjanvaneersel yeah I understand, I think the ADT as metadata to hold content is good and can be extended by third parties. I am unsure if we want to introduce a restriction.

Pro (for restriction):

Con (against restriction):

marc0olo commented 2 years ago

@marc0olo can you elaborate why a metadata_type is needed if we use an ADT for metadata anyway?

as @arjanvaneersel already pointed out it is mainly for indicating to consumers (marketplaces, ...) what kind of metadata is being used for the NFT contract.

personally I think it's good this way but yeah, open for suggestions

  • maybe there is a use-case for different metadata types in same NFT

@arjanvaneersel and I discussed this and we couldn't think of a usecase where people would want different types of metadata. in general this would unnecessary complicate the logic on consumer side IMO.

  • checking restriction costs gas

not sure about the implications, but if we wanna support multiple metadata types via ADT I think we need this. also we are already enforcing the NFTReceiver check with this standard which is also sth that costs gas and might be unnecessary for some. though I also talked to @arjanvaneersel regarding this. both of us never used the "unsafe" transfer in Solidity NFT contracts. so I guess this is also fine here.

If we all agree that the current state of the proposal (excl. implementation) is good now I would like to make an in-depth review where I again check every type, throw and so on.

from my side the only thing that is open and got no answer is if we wanna provide another method for the batch_transfer extension that could be more flexible, see https://github.com/aeternity/AEXs/issues/141#issuecomment-964037787

marc0olo commented 2 years ago

to second that:

please react with 👍 (pro) or 👎 (against) to this comment :-)

thepiwo commented 2 years ago

I am indifferent to the restriction, either way can result in issues in the future.

marc0olo commented 2 years ago

question is how we can collect as many opinions as possible about this. IMO we are already very flexible with the ADT in general. this is way better than what ERC-721 provides.

I think if we cannot think of a specific usecase where I would want to mix up metadata types in one single contract we should go for the restriction.

so far I wasn't able to think of one.

thepiwo commented 2 years ago

For the batch_transfer topic we should also consider how implementation regarding that works, iteration over lists will result in out of gas issues. I would propose to keep the standard as clean as possible.

Maybe we can just make one function for batch_transfer that just takes a map(int, address) / list(int * address) to make any token to any address the default, which can still be used to send all tokens to the same address. Otherwise I fail to think of a use-case to batch-send multiple tokens to different receivers at once (when still having gas limitations in mind, so it can't be the default for automatic transfers, meaning remote-calling single transfers in a batch is effectively the same). So I don't think its worth to implement it and have it in the standard, over a simple batch send to single address, even for which I don't know the current use-cases in NFT world.

marc0olo commented 2 years ago

right. I think we could keep it like it is in the current proposal then for batch_transfer. though I also like the proposal to just take a map(int, address) / list(int * address) to have more flexibility. @thepiwo what do you think about gas costs implications if we would use this instead of the current proposal?

generally I can only think of airdrops or sth. like that right now if you want to send multiple NFTs to different addresses. in case of airdrops you'd need to consider out of gas issues anyway and then it's probably better to have a service triggering the distribution step by step. for smaller amounts such entrypoint could be useful anyway.

omar-saadoun commented 2 years ago

I understand we are supporting or willing to be as erc 721 similar as possible but what about considering to be half way with collections?

Probably it won't sense for a while to have another standar because of gas fees so low being already quite efficient but if we are considering a batch_transfer which would be great we should also consider batch_mint taking map(int, address) / list(int * address).

ie. If I'm doing some airdrop or launching a complete collection I would first use batch_mint instead of mint and then doing batch_transfer.

Side note for collections or multi-token support: We could say it can be supported through contract factory + NFT-AEX . Correct?

marc0olo commented 2 years ago

I understand we are supporting or willing to be as erc 721 similar as possible but what about considering to be half way with collections?

multiple collections in a contract are not considered in this proposal. we don't want and need to be compliant to ERC-721 here, but we want to keep it as simple as possible while providing flexibility e.g. in terms of metadata.

Side note for collections or multi-token support: We could say it can be supported through contract factory + NFT-AEX . Correct?

you could use a contract factory to achieve that, yes.

we should also consider batch_mint taking map(int, address) / list(int * address).

agree

ie. If I'm doing some airdrop or launching a complete collection I would first use batch_mint instead of mint and then doing batch_transfer.

yes, I think then in general map(int, address) / list(int * address) makes most sense to use then and be consistent there. and I agree that batch_mint should also be part of an extension then. maybe also batch_burn then.

I am wondering how to place that in the extensions. in theory we would need another 2 extensions then or 1 combined one:


we are coming in an area now where it starts to become more complex. but as these are all extensions I think this could be fine. question is if we should reconsider these batch_mint and batch_burn extensions at a later point of time or now directly.

thepiwo commented 2 years ago

@marc0olo if introduce list iteration in the standard, users will definitely run into out of gas issues at some point and will thus have to implement an alternative/special-case for this use-case anyway

marc0olo commented 2 years ago

@marc0olo if introduce list iteration in the standard, users will definitely run into out of gas issues at some point and will thus have to implement an alternative/special-case for this use-case anyway

so you propose to avoid it and not even including it as extension?

right now we would include it in an extension of the standard, yes. and yes, it would cause out of gas issues. but experienced devs could analyze and limit the amount of NFTs used in a batch action.

I mean we can also ditch the batch actions completely. also from the extensions. or we decide to introduce the extensions at a later stage if there is demand for it

ErkanKanden commented 2 years ago

Not an expert here, but if we have to decide between user friendliness/smoother experience or advanced functionalities like batch actions, I'd go for ease of use. This way we can get new developers to try out the new NFT standard more easily and not have them frustrate too much over out of gas issues

thepiwo commented 2 years ago

right now we would include it in an extension of the standard, yes. and yes, it would cause out of gas issues. but experienced devs could analyze and limit the amount of NFTs used in a batch action.

@marc0olo yes, but experiences devs can also build this using remote calls and a parent contract. Inexperienced devs will build some product that bricks itself.

thepiwo commented 2 years ago

same with the metadata, I am indifferent here, just analyzing and explaining the risks

marc0olo commented 2 years ago

@marc0olo yes, but experiences devs can also build this using remote calls and a parent contract. Inexperienced devs will build some product that bricks itself.

that's true, agree. so I would be fine with excluding batch transfers. and as already said I think we could introduce a respective extension at a later stage if there really is demand for it

same with the metadata, I am indifferent here, just analyzing and explaining the risks

here I kind of disagree. I don't see any risk and it's just much more convenient if it is restricted. still not sure if there exists a usecase where you would like to mix different metadata types in the same NFT contract

thepiwo commented 2 years ago

@ErkanKanden the problem is with this kind of ease-of-use it nice as long as it works, but when it doesn't any more shits on fire. E.g. I am an inexperienced dev, build an NFT marketplace contract, make use of batch transfers from my marketplace contract using the nft contract. I gain 1000 users, suddenly the marketplace is not working anymore. The inexperienced dev find out its because of too many transfers in batch, but then is to late to patch the marketplace contract as its already deployed without thinking of the possible risks using that external nft contract before, nfts stay locked in there forever. Of course a security review would have prevented that, but in any case, no one can use the feature of batch transfers from a contract without building a fallback, if you build a fallback anyway into your contract you have no benefit of using batch transfer in the first place. Only backend/wallet/trusted services can gain from this, others have to treat with lots of care.

thepiwo commented 2 years ago

Similar issues cost ethereum projects 100s millions in losses, this is not just something that might happen but probably never will. If someone knows about an issue in a third party contract like this it can be actively exploited by an attacker to provoke that bug.

thepiwo commented 2 years ago

With all of that I'm not saying we shouldn't have such feature, just treat anything that includes iteration with utmost care when proposing a standard, for people to rely on.

marc0olo commented 2 years ago

yes, the more I think about the batch thing the more I am on your page. we should avoid it in the standard.

thepiwo commented 2 years ago

When we avoid it we should make sure that batch actions will still be possible from a third party contract, and e.g. provide an example for that.

marc0olo commented 2 years ago

@arjanvaneersel du you agree to avoid iterations in the standard and providing examples for batch actions in third party contracts?

arjanvaneersel commented 2 years ago

@marc0olo I agree, let's not make batch transfers a part of the standard, but give some examples how one can do it in 3rd party contracts. I already incorporated your earlier suggestion. So I'll update the proposal by removing the batch actions and then @thepiwo should be good to go with his final review.

marc0olo commented 2 years ago

@arjanvaneersel also consider this comment please: https://github.com/aeternity/AEXs/issues/141#issuecomment-964907539

what's your take on the restriction discussion with defining the metadata type on contract level? do you see any usecase that requires not restricting it?

currently I personally am pro restriction, still