Minimalistic transferable interface

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.

ERC-6454’s interface answers a slightly different question: “can any tokens be transferred?”

We may have different interpretations of the word “any”, but just to be clear, the current EIP-6454 suggests isNonTransferable(uint256 tokenId). So it is an interface to determine whether a specific token can be transferred, i.e. the same contract may have isNonTransferable(123) == true and isNonTransferable(456)==false.

~~

@stoicdev0 Instead of another draft doing a similar thing, it would be great if the community can agree on one interface? The more similar interfaces we have, the less likely it will become that any of those will get mass-adaption. So maybe @stoicdev0 and @sullof , you could find some common ground and e.g. adapt this EIP?

1 Like

The ability to pass from to to check if token is transferable is great for a particular use cases, but in the case of something like ERC-6059 where a token is nested in another token, it doesn’t always make sense. I want to be able to display token’s transferability/soulbound state regardless of what account owns it or who it is being transferred to. Let’s say there is an in-game marketplace which sells a “gauntlet” item NFT that has a cursed soulbound “gem” NFT nested into it, I want to be able to clearly display that it’s soulbound into the gauntlet so when the user browses the marketplace (even without a connected account), he will know that he won’t be able to get rid of the cursed gem. Passing from and to fields every time to check this is impractical, especially before we know the to address. So while there are use cases where from/to checks are useful, I would prefer it to be a separate method or a separate EIP

Even without Nestability, I may hold reputational NFTs that I’d like to see either on the marketplace or some other dApp. On a marketplace these shouldn’t even have the option to be sold as they will fail. All this is again independent from the from and to.

@SamWilsn would it make sense to have both versions in the same EIP?
Something like ERC6454a for the minimal version with tokenId only, and ERC6454b for the version with from, to and tokenId?

If possible, what do the rest of you think about it?

1 Like

There isn’t a clear-cut answer unfortunately. I tend to recommend splitting into separate proposals if:

  • For independent interfaces/ideas: each idea can stand on its own; or
  • For dependant interfaces/ideas: it’s more likely that the dependant proposal won’t be implemented.

A good example of the latter point is the ERC721Metadata interface. It’s more likely that tokens will implement it than not implement it, so I think it was the correct choice to include it in ERC-721.

If ERC6454a and ERC6454b can be useful separately, then that’s a great indicator that they belong in their own proposal(s).

Here, however, it sounds like if you’re going to implement one interface, you’re likely going to implement both, so one proposal is probably good.

Based on the discussion, we are considering adding another method to our proposal. So the updated proposal would have a method that accepts tokenId and a method that accepts tokenId, from, and to parameters. With the addition of the new method, it also makes sense to rename both to isTransferable.

Our current suggestion is to use the following:

function isTransferable (uint256 tokenId) external view returns (bool)

function isTransferable (uint256 tokenId, address from, address to) external view returns (bool)
2 Likes

@ThunderDeliverer given your two overloaded methods, would it make sense to either require or recommend the following implementation:

isTransferable (uint256 tokenId, address from, address to) { 
      return isTransferable(tokenId) && fromToBasedConditions;
} 

By the way, we just did the PR for the EIP I mentioned earlier (discussion in Draft ERC xxxx: Asset-Bound Non-Fungible Tokens), I already referenced this ERC there and will adapt our draft accordingly, as soon as this is becomes a final EIP.
Of course, happy to receive (critical) comments on our proposal.

2 Likes

This recommendation makes sense, but I’m unsure whether we can specify it within the specification. This might be implementation instructions, so I think it’s best to ask @SamWilsn if it’s allowed. :thinking:

Congratulations! I will make sure to check it out! :smile:

We have updated the specification of the proposal to include two methods now. Please review it and let us know how you feel about the revised proposal. :smile:

1 Like

The “proper” way to encode that in the spec would be something like:

The three argument isTransferable MUST return false if the one argument isTransferable would return false for the same tokenId.

1 Like

Your comment exposes a possible problem with the current specification with two functions.

In fact, if isTransferable(uint tokenId) has priority, why should we have the second function?

You would assume that the two are consistent, which may be impossible or would remove sense.
For example:

isTransferable(1) => false
isTransferable(from, to, 1) => false because the above returns false, right?

isTransferable(1) => true
isTransferable(from, to, 1) => true because the above returns true, right?

So, why should we have the isTransferable(from, to, 1) function? This seems to confirm the initial proposal and make isTransferable(from, to, 1) useless.

I see two reasonable cases:

  1. There is only one Transferable() function and the second one is abolished.

  2. There are two function, one returning a state, and the other a boolean, like

enum Transferable {
  YES,
  NO,
  MAYBE
}

function isTransferable(uint tokenId) external returns (Transferable);

function isTransferable(address from, address to, uint tokenId) external returns (bool);

In this case, if isTransferable(1) returns MAYBE it means that the result depend on from and/or to and the caller can make a second request.

What do you think?

The way I worded it didn’t require true if true, just false if false. If isTransferable(tokenId) returns true then isTransferable(address from, address to, uint tokenId) can return either.