Minimalistic transferable interface

Ya, just responded to this as well. There’s many on-chain attribute props that can trigger different behavior/computation.

I would say that an EIP that allows for storage of a prop is more effective than one specifically around transferability. Where do you draw the line between what is/isn’t generalizable enough to be a completely separate method and EIP?

This doesn’t seem like one of them.

Btw, I can see this use case, but the ones from before around it being useful for marketplaces/etc. aren’t really for the reasons I mentioned.

The use case that this EIP specifically enables is dynamic non-revert behavior changes based on transferability. Just want to be clear as to what this actually enables.

1 Like

Looking at alternative solution you proposed.

The front end part: Yes sure the pre-check would work, but it is important to take FE developer’s UX in mind, and not just end-user’s UX. As a client-side developer, I would rather have a ready made method on the contract that tells me if token is transferable with a simple function call that is descriptive and clear, and I am fine that this comes at a slightly increased gas cost for the sake of clearer integration (both for me and other people who come across this ERC) VS doing this check with gasEstimate, which is very un-intuitive.

From the contract-to-contract checks:
Just the revert is not enough for so many possible use cases, I agree that the immediate and most obvious benefits here VS just expecting a revert, is relevant on custom implementations and combining this EIP with other EIPs and gaming/trading or marketplace contracts

  • Marketplace contract skipping royatlies distribution for nested (ERC-6059) non transferable children when parent is sold.
  • Applying discounts and buffs if the token or it’s children (ERC-6059) are non transferable.
  • etc

Using on-chain attributes as you suggested can solve some cases where a new behaviour/computation should be triggered. But we can’t deny that there are plethora of such ideas and use-cases out there specifically that require non-transferability and conditional logic based on transferability, so having people implement their own solutions for both instead of providing them with a clear method to do it is exactly why the ERC is a good idea. At the end of the day if this was just blocking transferability then this would be pointless, this EIP is specifically for those who want to prevent transferability and react to it from other contracts (games, reputation systems, achievement registries etc) while having a clear way to check transferability status from a UI.

Right now the most easy to imagine use cases are very well paired with Nestable tokens (ERC-6059) and other custom contracts (gaming, reputation), but the bonus that the same standard works with just simple ERC-721 and thus will automatically just work on existing dapps, and these more advanced ERCs and custom implementation, outweighs the minimal gas saving of just having everyone to modify safeTransfer in their own way and apply custom solutions to react to it from other contracts

2 Likes

A few points here:

  1. This EIP does not do anything to prevent actual token transferability or provide additional UX support that can’t already be done. (your FE UX statement isn’t sufficient for me, as you can make it ‘easy’ by wrapping it in a lib. And tbh, all marketplaces should be checking transferability anyways (as ppl already have a variety of mechanics to block transfers → see Operator Filters, that are not token specific, but context specific). So, even if someone was trying to adjust their UX for general tokens to handle non-transferability, this EIP isn’t sufficient.

  2. On the examples provided point, those cases all still fall under the “non-revert behavior changes based on transferability” cases I mentioned. You could call it “mechanicModifier” if you wanted to, and for any game system that actually wants to do complex on chain dynamic behavior similar to what you describe, it’s likely that this one attribute isn’t sufficient. So I still see this as fairly limited/not generalizable enough for the purposes of those examples.

In general, this EIP, in my opinion, is far too narrow. It seems to be designed for ERC-6059 (which I have critiques of as well), but if the reason is around dynamic on-chain behavior, it’s still severely limiting as it doesn’t take into account other transaction context. Example, the whole Opensea Operator Filter (which I don’t agree with but is a good example of what I mean) does not work here.

Anyways, I can see what you’re trying to put forth, but I feel that there are fundamental flaws with this approach. There’s not much else I can add, so I’ll leave it at that.

Last note:
If the proposal was for a view function equivalent of transferFrom (like checkTransferFrom/checkSafeTransferFrom) with the exact same signature as their counterparts, I could buy that, as it would address the context issues I mentioned.

2 Likes

Great discussion above. I started this thread with a specific use case in mind and did not expect a long conversation on it.

Thus said, I actually like the checkTransferFrom/checkSafeTransferFrom using the same signature, and I suggested something similar above in the discussion because it covers most scenarios — since (in our specific use case) the transferability does not depend on the tokenId but on who you are trying to transfer it to.

I would make a point, that the interface function isNonTransferable(uint256 tokenId) like suggested (ERC-6454: Minimalistic Non-Transferable NFTs) is elegant due to its simplicity (hence flexibility).

IMO it should stay an interface only, NOT a guide on how it shall be implemented.

isNonTransferable(tokenId) is - besides NFT marketplaces - particularily interesting for DeFi, when an NFT shall be used as security e.g. for loans, lending etc. It can only serve as a security, as long as it cannot be transferred (unconditionally).
A side benefit is imho that the implementing contract becomes easy to read/review, as it is easy to grasp the contracts behavior, when isNonTransferable(tokenId) can trivially be used in ERC721’s _beforeTokenTransfer(tokenId), enforcing compliance with the “external” view on the transferability.

For disclosure, we drafted an EIP, where we need a similar mechanism and would rather prefer referencing an already existing standard interface rather than proposing another competitor method. In our case for example the required events of EIP-5192

3 Likes

Well said!

We are also in contact with several projects that have this as the only blocker before launch. They want to have ERC-6454 standardized before launching, so they don’t run into any incompatibility issues if the interface changes.

I suggest we proceed with the EIP process so as to not block the interested parties and finalize the proposal.

We see the current implementation as universal and as minimal as it gets. This seems beneficial as it can be the core building block of many solutions and implementations.
I think that if you have a collection-wide use case based on the receiver, you can add custom logic on top of this to extend the interface specified in the proposal :thinking:

2 Likes

As I mentioned on the Final status change pull request, I think there are a couple active questions being discussed here, so I am hesitant to move this proposal to final.

My understanding of the open questions:

  • I think this deserves an explicit mention in the Security Considerations section. Are there any potential problems that could arise if isNonTransferable doesn’t accurately report? Returning false but preventing transfers just seems like a minor annoyance, but what about returning true and allowing transfers?
  • I disagree on this point. The existing API can only tell you if a particular transfer (with an exact source and destination) will fail, not if all transfers will fail. That makes a significant difference in UI design.
  • This is an interesting approach, and I think it merits exploration. With this interface, is it possible to also check more general questions like: is token 0x3 transferable; can address 0x1234...5555 transfer any tokens; can address 0x5555...1111 receive token 0x5 regardless of who would’ve sent it?

Do you also have contact with projects on the flip-side? For example wallets, or NFT Marketplaces?

I ask because the EIP process doesn’t enforce any exclusivity. Even if we finalize ERC-6454 as it stands today, if wallets/NFT marketplaces implement a different standard, your contracts will still be incompatible.

I think that’s the flaw with this spec: assuming transferability is solely bound to the token level, which is really limiting.

example: incorrectly determines transferability capability for tokens using marketplace blockers. The proposed spec should take into account receiver/sender addresses. Token level is insufficient in a general sense. Note that transferability constraints outside of tokenId only are extremely desirable (e.g. restricting token transfers outside a set of specific authorized engagers for those trying to create a more controlled token transfer ecosystem).

Tokens that would fail for ALL would fail for a dummy recipient address anyways, so you could pass in a dummy 0xdead address as the ‘fail for all’ check (or define some standard checker address).

IMO, the spec would be ok if:

  1. It specified an event based system (rather than the current view method)
  2. It specified a specific ‘to’ address as a way to check if a transfer can occur at all at a token level
  3. ‘ok’ if we create view methods which mirror the transfer methods.

A dummy address wouldn’t work for tokens limited to a certain owner set. Imagine a token which can only be owned by one of seven addresses, but can be transferred freely between those seven. isNonTransferable would correctly return false, but testing random addresses with transfer would nearly always claim the token is not transferable.

Thanks for your time @SamWilsn!

I think this deserves an explicit mention in the Security Considerations section. Are there any potential problems that could arise if isNonTransferable doesn’t accurately report? Returning false but preventing transfers just seems like a minor annoyance, but what about returning true and allowing transfers?

I feel you’re taking maybe about malicious implementations. If that’s the case, it is as much of a problem here as it is on any ERC. I could easily produce and ERC721 compliant collection with a behind door that allows me to transfer NFTs to my self. If you’re using this for instance in Defi to secure an NFT as collateral, you must trust the implementation of the ERC6454 on that specific contract as much as you must trust its implementation of ERC721. We could add a note on that, but to me it feels obvious, unless I missed your point.

The existing API can only tell you if a particular transfer (with an exact source and destination) will fail, not if all transfers will fail. That makes a significant difference in UI design.

I think this has been his stronger argument, but we are on your same position as you, we see clear value to this EIP from UI/UX point of view.

This is an interesting approach, and I think it merits exploration. With this interface, is it possible to also check more general questions like: is token 0x3 transferable; can address 0x1234...5555 transfer any tokens; can address 0x5555...1111 receive token 0x5 regardless of who would’ve sent it?

I agree it would be an interesting approach. It would require at least the from and to arguments on the view, but this only applies for a specific use case and it would make this proposal no longer minimal. It could be work for a separate ERC.

Do you also have contact with projects on the flip-side? For example wallets, or NFT Marketplaces?
I ask because the EIP process doesn’t enforce any exclusivity. Even if we finalize ERC-6454 as it stands today, if wallets/NFT marketplaces implement a different standard, your contracts will still be incompatible.

We have a marketplace which uses this interface to display non transferability, and few gaming projects working with it already. This implementation comes from a non EVM blockhain where this simple approach was widely used for over a year. There are also a few projects using this ERC on EVM for reputational avatars, in some case together with ERC6059. @tbergmueller also mentioned they’re working on a use case on Defi. We’re also in talks with some wallet teams about supporting these and other ERCs we have proposed.

We’re aware that other alternatives may come in the future, there is even an ERC now which is similar but forces you to emit event on change of state, we already explained how this is limiting for many use cases. However we will be vigilant to point new comers to this ERC if it solves their use cases. Making an ERC go mainstream is no easy task but we think a minimal interface for this use case has the highest chances.

2 Likes

But, isNonTransferrable is incorrect here, since it would still return false which is technically incorrect since it’s not actually transferrable for anything outside those 7. I’m not following how the proposal even addresses that case or what the desired UX behavior is.

In your specific example, the UX would show it’s transferrable, even though it’s not (say it’s a marketplace UX, it would think you could buy the token when in fact, you can’t).

Yep, I’m talking about malicious implementations. I haven’t thought much about it, so I’m a perfect canary for obviousness: I have no idea what would happen if an NFT marketplace displayed a token with a lying isNonTransferable implementation. Is there anything special that apps/wallets displaying these tokens need to consider?

I don’t think it’s much different from a lying 20 or 721. You can’t enforce a honest implementation of those either, and if a backdoor is discovered they would quickly loose all value.

One thing to consider is that this value may need to be refreshed often, but it can vary a lot from case to case. So I’m thinking the most reasonable request to marketplaces/wallets is to have an option to refresh the value any time, seems worth adding this note.

Right. I’d just like to see a note in security considerations about two things: what might happen if isNonTransferable lies, and how apps can mitigate that risk.

2 Likes

Thank you for the suggestion; we updated the section accordingly. :smile:

@tbergmueller also mentioned they’re working on a use case on Defi. We’re also in talks with some wallet teams about supporting these and other ERCs we have proposed.

To clarify, we do not intend to use the isNonTransferable() to implement our EIP. But in our protocol, when an NFT is e.g. collateral/security for a loan, transfers are typically blocked for a specific period of time or until some contractual obligations / conditions are met. We saw/see issues with tokens according to our protocol, when people try to sell on market places.

The following has been tried with ERC-5484 , which essentially always throws in _beforeTokenTransfer() on OpenSea and Rarible (testnet implementations);

If transfer of an ERC721 is blocked temporarily or permanently, the owner can still go ahead and list the token. Often, a owner of a token (which is currently non-transferable) would even pay gas to approve e.g. OpenSea, then list the token for selling. When a buyer comes along and completes signing the purchase, only then would OpenSea says “whoops, I cannot transfer for unknown reasons”.

This is not only annoying, but both, the seller (who pays gas) and often likely also the buyer (who may have sold some other assets to have liquidity (and paid gas+fees+ royalty, … )) spent money on a transaction, that cannot be carried out. Not through the failed transaction, but through other transactions working towards the failing transaction.

To avoid those situations, it would be great if we can propose a standard . Naturally, marketplaces would then need to implement that standard. This is in general likely, as Rarible evidentially already recognizes throwing behavior, as the error says “Soulbound tokens cannot be transfered”.

Towards the question, of how the interface should look like, I can also see a isNonTransferable(from, to, tokenId), similar to safeTransfer(from, to, tokenId). If tokens cannot be transferred in general, those implementations should simply ignore then the from and to and implement logic just on a tokenId-level, essentially achieving the same as isNonTransferable(tokenId)

That interface only answers the question: “can I make a specific transfer?”. ERC-6454’s interface answers a slightly different question: “can any tokens be transferred?”.

Ideally we’d want an interface that can say if you can transfer any combination of: fixed from / any from, fixed to / any to, fixed tokenId / any tokenId.

Yep, it’s 2 different questions. We see the value of both and are actually thinking on creating a draft for the from/to version. This one is meant to be minimal.