Closed eordano closed 6 years ago
As far as I understand it from the implementation, assetByIndex will constantly change (slightly) as users trade their assets around (since the last item in their asset array is moved around). Is there any real need for the assetByIndex function then since that ordering is not very useful? An indexOfAsset(assetId) function seems more necessary as the main way UI is going to display your assets is to use something like the following:
var assetIds = assetregistry.assetsOf(user);
var sortedAssets = new Array(assetIds.length);
for (var i = 0; i < assetIds.length; ++i) {
sortedAssets[assetregistry.indexOf(assetIds[i])] = assetId;
}
Edit: It may be that assetByIndex should be implemented to return the asset such that it's actual id in the _indexOfAsset map == index (which it doesn't in your current implementation). It could be that assetByIndex needs to be implemented more like below:
function assetByIndex(address holder, uint256 index) external view returns (uint256) {
uint size = _assetsOf[holder].length;
require(index < size);
for (uint i = 0; i < size; i++) {
uint256 asset = _assetOf[holder][i];
if (_indexOfAsset[asset] == index) {
return asset;
}
}
assert(1 == 0);
}
I don't think create() (and possibly update()) should necessarily be part of a standard ERC821 token. They seem optional, and for many assets, their creation probably won't be a publicly accessible function and updating might not be relevant for that contract.
Edit: Thinking about it more, I think there is a potential to front-run the create() function too. If a malicious user sees a create call being made pending, they could create that same asset for themselves first. It would make managing these assets difficult to have to try and ignore maliciously added assetIds.
Thanks @ImAllInNow!
1) Regarding assetByIndex
. The idea behind this is to have that accessed from a smart contract. Use case: access from another contract for migrating all assets in batch mode to a new owner. allAssets
might consume too much gas, or kill the evm stack
2) You're right about create
. Front-running makes it a no-go. I'll keep them in the standard implementation as internal
.
Regarding update
and the metadata: is there really a case where no errata would be published? For my quick checklist:
update
is good for appending new information (change of location, status, news)update
is great for amendment on card rulesupdate
is used for errata or fixing typosupdate
is used to store the data for the landThe rule of the owner being the one authorized to make the changes is policy. You're right, it should not be part of the standard (same as before, I'll keep the mechanism as internal so the implementor can decide on the policy)
Last note on assetsOf
: there's a problem of concurrency, it would be nice to have a function that atomically returns all assetIds and all the associated data (otherwise if the order changes between fetches the UI will display erroneous information).
I'll add a note that UIs should list the items in sorted order, or keep their own information based on the transfer logs or user preferences.
@eordano You're right about update. I was getting it confused with updating the assetId rather than the data associated with the assetId.
For those coming straight to ERC821 @eordano has written a distinguishable asset manifesto: https://blog.decentraland.org/the-non-fungibles-revolution-of-2018-304270525b05
My original thinking for (unique) assets was given existing ERC20 support in wallets was to create assets that are ERC20 compatible, rather than get wallets to support (unique) assets but if we can get enough support for a unique assets standard for wallets to implement, then all the better.
Cost For me reducing the cost of creation and send are hugely important. So want to think about how the standard impacts the implementation and hence the cost.
ERC821 meta data (Similar to my comments on ERC721) For assets to be displayed in wallets we ideally want a name, logo and possibly a symbol representing the assets in ERC21 registry. MetaMask uses name and logo with optional symbol in their metadata. https://github.com/MetaMask/eth-contract-metadata Ideally the logo would be non-optional, though there will likely be use cases where assets won’t want to be displayed in wallets
CUD CUD access may depend on the implementation. E.g.: Create may only be called by the contract owner. Update may only be called by the contract owner or the asset owner depending on what is in data. Destroy may only be called by the asset owner.
Send Does the standard need two send functions?
Thanks for the comments, @abcoathup! Please note that I've updated the body of the proposal to include as much detail as I thought necessary
1) Cost: I can run some benchmarks tomorrow and give you some estimate
2) Should we add a logo
? Should there be some kind of specification, perhaps following iOS or Android's standard for logo sizes?
3) CUD: Updated to reflect your thoughts. Note:
Our implementation tries to follow the UNIX principle of providing mechanisms, not policy. That's why it contains functions to create, update, and destroy tokens, but they are marked as internal.
4) Does it save a significant amount of gas? I think not, except perhaps for calls between contracts?
@eordano rather than description
for the DAR, I was thinking this should be data
(metaData) in the same format as the data
for the NFT and include a logo.
Suggest reaching out to wallets and dApp browsers (e.g. the likes of MetaMask, Toshi, Status.im and Cipher Browser) for input on logo specifications.
Is there a need for both holderOf
and 'safeHolderOf' when there is an exists
?
Should all functions that accept an assetId
require that it exists so there is consistent behavior?
safeHolderOf
is a cheaper way of calling exists
and holderOf
.
Discussion comes from #721 throwing on ownerOf
if the asset doesn't exist. I think this prevented exists
from being calculated.
My take on this is that both are valid use cases... that you might want to save some gas by just calling safeHolderOf
, but that there might be some useful way of checking holderOf
and calculating exists
yourself. For example, when used as an external function and you want to do only one roundtrip.
The other functions that accept an assetId are not relevantly modified by this behavior, except maybe on the case of assetData
, but it might be expected that safeHolderOf
would be called first, or the logic implemented "client"-side.
Thx for the great idea. I think that you have to describe how #821 is different/better from the #721.
As long as #721 was added before, it is your goal to clearly describe why you decided to add new non-fungible token standard. Thx again.
I'll expand later, but for the time being:
1) If I have 100 NFTs, calling approve
100 times with the same to
value (a decentralized exchange probably) is very inefficient, specially given that it's usually used that way (see Etherdelta, 0x)
2) Lack of exists
function in #721 -- There's no way to do a "try-catch" for ownerOf
from a contract.
3) Lack of transferAndCall
functionality -- More than 4 million USD have been lost according to https://github.com/Dexaran/ERC223-token-standard. It's critical that we have a way to not loose tokens for sending them to a contract that doesn't handle it.
4) Possibility to log more information in the Transfer
event, following #777
Lots of good thinking has gone into this. Decentraland brings a perspective to NFTs that we don't have at CryptoKitties. Thanks for bringing this to the community, @eordano!
I'm confused as to why you think this needs to be an whole new standard, tho. I don't know about anyone else, but I don't consider ERC-721 as finalized, and I'm not sure it's appropriate to have a whole new standard that is largely the same.
I appreciate that I've been a bad steward of ERC-721 for the last few weeks, and I can see how you might be frustrated by the lack of forward progress, but I would rather we find a way to move forward with one standard, rather than two. I mean, it's kind of the point of standards! 😅
Hey Dieter, I thought this was a better way to express the wholeness of the changes I was proposing (as you see, they are quite a lot).
The three things were I think we've got completely different approaches are:
authorizeOperator
instead of individually approve
-ing every assettransfer
to prevent tokens from getting lost to contracts that can't handle themUsing
authorizeOperator
instead of individuallyapprove
-ing every asset
I like this! I haven't had a chance to integrate it into the 721 draft, however.
The need for transfer to prevent tokens from getting lost to contracts that can't handle them
Unfortunately, we do disagree on this. I think that preventing user errors is a porcelain problem, not a plumbing problem. It's not the job of the filesystem to decide if the file you've named .jpg
is actually compliant with the JPEG standard. (That's admittedly an exaggerated analogy, but I don't think Ethereum will ever see mass adoption if we continue to assume that users will be directly interacting with smart contracts!)
Upgradability through #820
EIP-820 is obviously very new, and looks pretty smart. So far as I can tell, it doesn't dictate anything about how an interface is structured, though, so it should be possible to use it for any standard past or future (even good ol' ERC-20).
I agree with @dete that moving towards one NFT standard is preferable to multiple standards especially when this concept is new and has not been specifically supported by wallets/exchanges yet (that I know of).
Definitely some good improvements suggested in the interface as described here. I like the use of an authorized operator and the idea of storing an index mapping to allow for a users assets to be stored as an array for easy retrieval through MetaMask without timing out when there are large numbers of assets (I recently noticed I can't retrieve all CryptoKitties for a user anymore :( ).
I think deciding to prevent user transfer errors can be left up to the contract itself. It is a slight extra cost to make this check so if a contract wants to add to the gas cost of transfers to protect their users, that's great, but I don't think it necessarily needs to be mandated in the standard.
I think that preventing user errors is a porcelain problem, not a plumbing problem.
I think regardless whether the UI should prevent the user from doing this (I think it should), it's still valuable to have another contract be notified with only requiring 1 transaction (from a plumbing perspective).
Have you considered implementing something for lienholders?
Let's say you have a plot of LAND that you are using as collateral for a loan, e.g. a mortgage. What if there was a way to allow the lender contract to transfer ownership of a plot of LAND without the land owner being able to revoke this access?
I expect lienholders (new word for me!) to be a strong usecase for NFTs, especially as we move to a tokenized future. Think loans collateralized by fine art (or, hell, cryptokitties). Same with, say, propy houses.
It'd be possible to create that functionality with a "wrapper" contract (like an escrow), but that's not entirely ideal.
Could either be an optional extension to this EIP (nice) or another EIP (long process).
I suggest that every ERC821 asset registry must register the "IAssetRegistry" interface with ERC820, that way contracts can automatically discern if a given NFT registry conforms to this standard.
An example of how that would be used: https://github.com/hyperfekt/ethereum-auctions/blob/master/NFTAuction.sol#L29
What is the operatorData argument of transfer()
for? The description of "the operatorData argument" is a self-referential description, and says nothing about what data goes there.
I'm kind of assuming it's just a blob of information that people can use when they need to communicate something not defined by the spec, but I feel like it needs more clarification.
I feel like there's still room for an approve()
call.
Calling recipient.onAssetReceived()
eliminates part of the need for approve()
in that you an send an asset and invoke the recipient in the same call, rather than needing to call approve()
, invoke a separate contract and have it execute transferFrom()
.
Having authorizeOperator()
addresses another part of the need for approve()
, for the scenario where there may be a considerable period of time between authorization and transfer (such as waiting for an order to be filled on an exchange). But I feel authorizing another account to transfer all of your registered assets, much like authorizing a contract to transfer an unlimited number of your ERC20 tokens, is a bit of an anti-pattern. It saves gas, which is obviously not insignificant, but it also means that if the security of authorized operator is compromised all assets are subject to transfer, not just the ones you intended that operator to transfer on your behalf.
In light of the numerous security compromises we've already seen (the DAO hack, Parity Multisig Wallets Rounds 1 and 2) building a standard on the assumption that contracts will always work as intended seems short sighted. An approve()
method would allow people to be more conservative, only risking assets they anticipate transferring in the event that the operator contract gets compromised. That would come at a higher gas cost for the authorizer, but if they are confident in the contract they are authorizing, they would still have the option to call authorizeOperator()
.
@AusIV the operatorData
is there for protocols like 0x where you'd like to log some information about why the transaction was successful, for better auditability. For example, you could have the market making order be the operatorData, and the userData
would be whatever arguments you want to pass on to the contract receiving the asset.
I think you're right about allowing approve
as well as approveAll
. I was thinking that one could just have a "filter" contract that can take care of this granularity, to save the complexity of implementing that to the DAR, but now that I think of it again, I'm not convinced. What do you think?
@hyperfekt I think the correct way to go about this is that the DAR should register the ERC821
interface (an empty interface, just using it for the name) as a way of signaling that it implements the standard... what do you think? In any case, I added the isERC821()
method because of feedback from potential users that they want to have an easier way to check for compatibility.
I just updated the post with the following changes:
Historical versions can be seen at: https://github.com/decentraland/erc821/blob/master/ERC821.md
@eordano What makes you propose an empty interface? I might be confused what you mean by this - shouldn't every registry announcing ERC821 support offer the full interface?
One thing that I'm a bit concerned with is the balanceOf alias - this could lead users who are not aware of the non-fungible nature to accidentally transfer random NFTs with id <= assetCount instead of that number of tokens, could it not?
Also I fear that allowing to detect support via a method seriously hampers upgradeability in the spirit of ERC #820, where an old NFT registry can have the new methods added. This of course only works if it's default or required to only use the ERC821 methods with the implementer ERC820 returns (maybe this condition should be part of ERC820?). Please tell me if I've misunderstood how ERC820 upgradeability is supposed to work. /cc @jbaylina
What are you thinking in terms of a filter contract? Would that be something the user could specify that would be called to determine whether a given operator was allowed to move a given asset? That might work, but I'd hope to see some kind of pattern established for what it would look like. One thing to keep in mind is that people implementing DARs are expected to have some level of competency in solidity, (or perhaps other EVM languages) while people merely holding assets on them very likely don't. I would avoid putting onus on the asset holders to manage other contracts.
The other factor is that there will be template DARs that will likely already implement a lot of the complexity of handling approvals, and so long as those templates are well implemented I think most DARs will implement it correctly. With filters and operator contracts, on the other hand, it's likely that each one will be unique, making it more likely that some will be implemented badly and compromise the assets of users that depend on them.
@hyperfekt People implementing DARs would be organizations creating new kinds of digital assets. The DARs don't have to register themselves as IAssetHolder (in fact, they probably shouldn't -- that's why 223 initially started, token contracts usually are not meant to hold tokens). But they might choose to register the "erc821" interface and point the resolution to the same contract.
@AusIV The fact of the matter is that the approve
function is used with ~infinite values in practice. We built an application with non-infinite approve values and it was a pain to handle that. Wallets would be the implementators of the filtering functionality --supposedly experts as well.
To summarize, on one side:
approveAll
is all that's neededOn the other:
Three more comments against single approves:
transferAndCall
-like functionality reduces the need for approve
@eordano I think you might have misunderstood me. I was saying registries should register themselves as implementing "IAssetRegistry", not "IAssetHolder".
I'd like to support the argument for 'filter contracts'. Take a look at #777 where a perfectly analogous discussion is happening right now. This is a very important feature that would greatly increase flexibility, reduce the trust necessary in other contracts and simplify the development of many applications. It would also allow people who need it to implement single item approve on top.
Oh I see @hyperfekt sorry for the confusion! Thanks for the correction. I'll take a look at the discussion!
I'm not opposed to allowing unlimited operators, but I'd like to see an option to just authorize specific items. I think the answer of "hold different assets in different accounts" may be limiting, depending on the nature of the DAR. It's not hard to imagine asset types where holding assets in the same account might offer some advantage over holding them in separate ones.
Some ERC20 tokens have adopted the convention that if you set an allowance of MAX_UINT256, they won't subtract out transferFroms, making the allowance literally infinite, not just effectively unlimited since it's very unlikely that an account would ever transfer more than MAX_UINT256. I would advocate making this pattern official for ERC821. If operators have to be approved on a per-asset basis, we could have a reserved assetId (I would propose MAX_UINT256) which could grant the operator access to all assets in the DAR.
This would add a bit of complexity in that the DAR would have to track approvals on a per-asset basis, but it wouldn't require completely different structures for tracking individual asset approvals and complete account approvals, and users who prefer to save gas by authorizing the operator for all assets in one go could do so.
Assuming approvals are stored in a mapping (address => mapping (address => mapping (uint => boolean))) approvals
, checking an approval for operator
trying to move asset
on behalf of owner
could be done with:
bool allowed = approvals[owner][operator][MAX_UINT256] || approvals[owner][operator][asset]
I feel this pattern would allow people to be more conservative with their authorizations without adding too much complexity to DARs, while still allowing people who want to save gas to do so by approving the operator for all of their assets.
This may work better as a separate EIP but I wanted to bring this up because I don't see it anywhere else.
I want to use NFTs to facilitate DAO voting. In executing a proposal the DAO will call balanceOf on the Shareholder ERC-20 to tally the votes. The way balanceOf was implemented in the original 721 spec was a disadvantage. Instead of counting each NFT as 1 vote, it would be better to count them based on its weight or value within the DAR.
For my specific use case, we make an NFT representing a donation. To make the donation you purchase the NFT. The NFT is effectively a certificate/receipt. I want to weigh each of the NFTs according to their price and then have that weight count as votes in a DAO.
If balanceOf instead returned the total weight or value of a holders NFTs then the DAR would be a drop-in replacement for, or sit beside in an array, ERC-20 contracts in DAO voting.
In my case, we use an ICO to raise funds for charity and let the token holders vote in the DAO to choose charities. In time, the number of NFTs increase from the donations that are made the number of votes coming from the DAR surpass the number of votes from the ERC-20 contract.
This allows us to 'bootstrap' the organization with the original ICO donors, but then the users who are ultimately served by the DAO inherit the DAO.
For now, I've been tracking this attribute as 'voteWeight' and maintaining it in the _transfer function. It's a simple change this way, but then you have to add extra logic to the DAO to account for the separate vote source logic. Effectively duplicate code because of naming convention.
Hey @AusIV I just updated the text to add approve
as in ERC721. As a result, I think that except for requiring contracts to implement onAssetReceived
to receive tokens, or functions like takeOwnership
or transferFrom
(that can be emulated with transfer
), any ERC821 can be treated like an ERC721.
cc: @dete @hyperfekt @Shrugs
@tenthirtyone I just updated the text to make it more ERC-20 look-alike. In particular, I added balanceOf
. Let me know of any other questions. I think you probably need another weight contract, as the use case would assume that each NFT has some value (for our LAND that has some sense, but dealing with valuation changes is something that most likely won't apply to all DARs)
@AusIV with regards to multiple authorizations, the solution you proposed adds a lot of complexity and I'd leave it for a filtering contract. Simple ERC-721-like authorization to "transferFrom" once (as it's used in the wild) looks more adequate to me for simplicity.
@eordano - Can you please elaborate on filtering contracts, or provide references on the topic? It's not clear to me how a filtering contract can restrict operator access after a user has granted an operator unlimited access to their NFTs.
@AusIV no longer necessary as I added an approve
, but I meant something like authorizing a contract like this one to be an operator:
contract Filter is Ownable {
address actualOperator;
ERC821 dar;
mapping(address => bool) authorized;
function transfer(address to, uint asset) {
require(authorized[asset]);
require(msg.sender == actualOperator);
dar.transfer(to, asset);
}
function authorize(uint asset, bool _authorized) onlyOwner {
authorized[asset] = _authorized;
}
}
where you are the owner and you have authorized this contract to be an operator. Yes, it can move any token, but the code is simple enough to be considered pretty safe to be allowed operation of all your assets.
0x is considering organising a working group to discuss and push forward the adoption of an NFT standard. I understand that it is fairly short notice, are there any interested parties who are also attending ETHDenver who would like to join such a discussion?
If there is enough interest we can carve out a time during ETHDenver and invite those who are interested or have contributed to the various proposals.
Please contact me at jacob@0xproject.com if you're interested. If this meeting eventuates an agenda and meeting notes will be made available.
Interested @Jacob. Sent an email On Mon, Feb 12, 2018 at 16:23 Jacob Evans notifications@github.com wrote:
0x is considering organising a working group to discuss and push forward the adoption of an NFT standard. I understand that it is fairly short notice, are there any interested parties who are also attending ETHDenver who would like to join such a discussion?
If there is enough interest we can carve out a time during ETHDenver and invite those who are interested or have contributed to the various proposals.
Please contact me at jacob@0xproject.com if you're interested. If this meeting eventuates an agenda and meeting notes will be made available.
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/ethereum/EIPs/issues/821#issuecomment-365083496, or mute the thread https://github.com/notifications/unsubscribe-auth/AAYfb_GJ9rec8_OT0phr3dI23NeFX-Mfks5tULnbgaJpZM4RUdR9 .
The only issue I have with this PR is the naming. Distinguishable Asset Registry isn't terrible, but I think it makes sense to drop NFT here and now.
I could say to people, "This DAR tracks NFTs and their owners," but then the next words out of my mouth are always going to be "distinguishable asset registry and non-fungible token". If I don't lose their attention right then and there, I'm going to have to spend the next minute or two explaining wtf I'm talking about.
But if I could say, "This asset registry contract tracks assets and their owners," they stand a reasonable chance of following the conversation without having to feel stupid. Instead of tuning out or asking me to explain what those words mean, they're going to ask me what I'm doing with it that's so interesting.
While Asset is definitely better than NFT, it's not specific enough. That's why the spec calls it a distinguishable asset registry. This is one area where I feel like #841 really shines. Deed nails the readability problem. It's short enough already to resist shortening by default, it pluralizes well, it's speakable, and it's implicitly non-fungible by definition.
Non-fungible token takes 18 characters and 1-2 explanations (1 to define fungible, 1 to explain what an NFT does) to do what deed does in just four letters and zero explanation. Namely, they both accurately describe a record of ownership of some unique asset.
Examples using "NFT":
Omega is going to sell 500 limited edition watches for the 2020 Olympic Games, as they have for many Games before. But these watches will be a little extra special. Instead of issuing paper certificates of authenticity, Omega will be the first watchmaker to issue what are known as NFTs, or non-fungible tokens. These digital tokens will live on the Ethereum blockchain and hold the watch's serial number and a link to a printable certificate. Our very own John Doe has the story...
CryptoPonies launches today! Our ponies are defined by the NFT metadata. The name on the NFT is actually the pony's unique genetic code, while the description lists its breed and parents. The URL will take you to your pony's battle stats and show you its progress on The Path of Skulls. Come check us out!
Examples using "Deed":
Omega is going to sell 500 limited edition watches for the 2020 Olympic Games, as they have for many Games before. But these watches will be a little extra special. Instead of issuing paper certificates of authenticity, Omega will be the first watchmaker to issue blockchain deeds. These deeds will live on the Ethereum blockchain and hold the watch's serial number and a link to a printable certificate. Our very own John Doe has the story...
CryptoPonies launches today! Our ponies are defined by the metadata in their deeds. The name on the deed is actually the pony's unique genetic code, while the description lists its breed and parents. The URL will take you to your pony's battle stats and show you its progress on The Path of Skulls. Come check us out!
NFT might be a household term in our circles right now and we might all be crypto- or finance-savvy devs, but the next wave of adopters won't be. I think we'd do the community a favor by making it easier to bring them (and their investors) on board. Deed Registry and Deeds do that better than Distinguishable Asset Registry and Non-fungible Tokens can.
If #821 moved to friendlier naming, I'm not sure there would be any gain in adopting #841 over this.
@inhumantsar the reality is that NFT is sticky. Deed doesn't have that catchy-ness.
I came up with "DAR" to avoid the issue of having to differentiate between "NFT" and "the NFT contract" (something that happens a lot with ERC20s). It actually stuck with some other people I have discussed this standard with.
Organizational inertia is a terrible excuse to maintain any practice though. If everyone just stuck to whatever came first, we wouldn't be discussing this vs. #841.
I'll agree that "Deed" isn't the sexiest word for engineers to use but engineers aren't the only people we need this to catch on with. We can use a term other than Deed, but intentionally moving toward jargon and opaque acronyms is anti-noob, which is anti-adoption.
To drive mass-market adoption, it will help immensely to name it with enthusiastic laypeople in mind. They're the ones who are going to be talking to the media and their friends about the things we build. If they have to spend 5-10 minutes explaining every other acronym that falls out of their face, fewer people are going to listen.
edit: For the record, I prefer Asset over Deed, but "Distinguishable" launches it into jargon land.
@inhumantsar This EIP works at the API level, so I guess it's just for developers. I think deed is less specific. You can approach the whole community to adopt using "deed" at the UX level, and even write a proposal for that, but I feel confident in ruling "enduser-friendly naming" out of scope. Not so much with other naming considerations that I suggested, like using holder
instead of owner
(as in the actual "owner" living outside of the blockchain and holder
is the address that supposedly represents him) or operator
to refer to someone who has been approved to move assets on behalf of the holder
address.
I guess it's just for developers
I disagree. If you take a look at the press that CryptoKitties got, you can clearly see that the high-level naming we choose, like the title of the spec, is not just for developers. In fact, it's probably the only thing from the spec that the general population is going to be exposed to.
holder
and owner
, by contrast, will only ever be exposed to people writing contracts or code that interacts with them.
[edit] quoted the wrong sentence, also words
Non-Divisibility & Multiple Ownership I believe is core to serving a Non-Fungible Token.
Whether this is incorporated in this standard, or, possibly the #874 conversation pertaining to Weighted NFTs.
Use Case: The use case where I am encountering the need, is for a National Land Registry whereby a NFT / Title has the ability to contain many owners, and the future potential of using ownership in equity.
The problem of Non-divisibility can be seen to be a limitation within the Dharma Protocol whitepaper, we see this as a limitation as well.
Current State: Right now this can be circumvented with a proxy contract
Non-Fungibility and scarcity tend to align pretty nicely for those assets of higher value. I strongly believe that the future of these tokens, will more and more require a proxy contract to facilitate Non-Divisibility & Multiple Ownership if the current state of is implemented.
Therefore, to reduce gas with less transactions / calls, increase adoption, and increase security with standardization / best practices for transferring value / delegating trust in a standardized way, I think we should discuss for #821.
@eordano, @tenthirtyone Thoughts?
I'll Note: If anyone has encountered the best way to handle the governance of N number of owners in a decentralized way please point me in the direction. Transferring ownership with multiple owners requires coordination, which I believe to be a custom function depending on the NFT token. Considerations are: voting on chain with some level of quorum consensus (but becomes expensive), or possibly voting / agreeing off chain with state channels, with on-chain settlement (only a research area now).
Summary
A Distinguishable Assets Registry (DAR for short) is a contract that tracks ownership of, and information about a set of assets that are distinguishable from each other, and are commonly refferred to as "NFTs", for "Non Fungible Tokens".
See https://github.com/decentraland/erc821 for a reference implementation.
See the "Revisions" section for a history of this ERC.
Abstract
Tracking the ownership of physical or digital distinguishable items on a blockchain has a broad range of applications, from virtual collectibles to physical art pieces. This proposal aims at standardizing a way to reference distinguishable assets along with the foreseeable required operations and interfaces for effective management of those assets on a blockchain.
Introduction
The number of virtual collectibles tracked on the Ethereum blockchain is rapidly growing, creating a demand for a more robust standard for distinguishable digital assets. This proposal suggests improvements to the vocabulary used to refer to such assets, and attempts to provide a solid and future-proof reference implementation of the basic functionality needed. This EIP also proposes better naming conventions and vocabulary for the different components of the NFT economy: the assets, the NFTs (representations of those assets on the blockchain), the DARs (the contracts registering such assets), distinguishing ownership from holding addresses, and more.
See also: ERC #721, ERC #20, ERC #223, and ERC #777.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
Specification
A non-fungible token (NFT) is a distinguishable asset that has a unique representation as a register in a smart contract. This proposal specifies such contracts, referenced as Distinguishable Asset Registries, or DARs. NFTs MAY also be reffered to as "deeds".
DARs can be identified by the blockchain in which they were deployed, and the 160-bit address of the contract instance. NFTs are identified by an ID, a 256 bit number, which MAY correspond to some cryptographic hash of the non-fungible's natural key.
NFTs SHOULD be referenced by a URI that follows this schema:
The NFT's ID SHOULD have a hint that helps to decode it. For instance, if the encoding of the number is in hexadecimal, the NFT’s ID SHOULD start with
0x
. The DAR's address SHOULD follow the checksum by casing as proposed on #55.Some common names for Ethereum blockchains are:
ethereum
,livenet
, ormainnet
ropsten
,testnet
kovan
rinkeby
Some examples of NFT URIs follow:
Every NFT MUST have a
owner
address associated with it. NFTs associated with the null address are assumed non-existent or destroyed.An owner MAY assign one or multiple
operator
addresses. These addresses will be able to transfer any asset of the owner.DARs MUST trigger
Transfer
events every time a NFT'sowner
changes. This might happen under three circumstances:from
value of theTransfer
event MUST be the zero address.to
value of theTransfer
event MUST be the zero address.Transfer
events SHALL NOT simultaneously have a zero value in both thefrom
andto
fields (this means, the sameTransfer
event can't both create and destroy a token).Associated Metadata
Metadata associated with each asset is out of scope for this standard.
DAR global methods
totalSupply():uint256
Return the total amount of assets under this DAR. This method SHALL NOT throw.
supportsInterface(bytes8 interfaceId):bool
This method returns
true
if theinterfaceId
is a supported interface (165, corresponding to 0x01ffc9a7, or 821, corresponding to 0x959d7abb). This method SHALL NOT throw.NFT getter methods
exists(uint256 assetId):bool
This method returns a boolean value,
true
if the asset identified with the givenassetId
exists under this DAR. This method SHALL NOT throw.ownerOf(uint256 assetId):address
This method returns the
address
of the owner of the NFT. This method SHALL NOT throw. If theassetId
does not exist, the return value MUST be the null address.Owner-centric getter methods
balanceOf(address owner):uint256
This method returns the amount of NFTs held by the
owner
address under this DAR. This method MUST not throw.assetByIndex(address owner, uint256 index):uint256
This method returns the ID of the
index
th NFT held by theowner
address under this DAR, when all the IDs of the NFTs held by such address are stored as an array.This method MUST throw if
assetCount(owner) >= index
. This method MUST throw ifindex >= 2^128
.The DAR MAY change the order assigned to any NFT held by a particular address.
This method is expected to be used by other contracts to iterate through an
owner
's assets. Contracts implementing such iterations SHOULD be aware of race conditions that might occur, if this iteration happens over multiple transactions.assetsOf(address owner):uint256[]
This method returns an array of IDs of the NFTs held by
owner
. This method SHALL NOT throw.Operator getters
isAuthorizedBy(address operator, address owner):bool
This method returns
true
ifowner
has called the methodauthorize
with parameters(operator, true)
and has not calledauthorize(operator, false)
afterwards. This method returnsfalse
otherwise.This method MUST return
true
ifoperator == owner
.This method SHALL NOT throw.
isApprovedFor(address operator, uint256 assetId):bool
This method returns
true
ifowner
has called the methodapprove
with parameters(operator, assetId)
and has not called it with a different operator value afterwards. This method returnsfalse
otherwise.This method MUST return
true
ifoperator == owner
.This method SHALL NOT throw.
Transfers
transfer(address to, uint256 assetId, bytes userData, bytes operatorData)
Transfers holding of the NFT referenced by
assetId
fromownerOf(assetId)
to the addressto
.to
SHALL NOT be the zero address. Ifto
is the zero address, the call MUST throw.to
SHALL NOT beownerOf(assetId)
. If this condition is met, the call MUST throw.isAuthorizedBy(msg.sender, ownerOf(assetId))
MUST return true as a precondition.This means that the
msg.sender
MUST beownerOf(assetId)
or an authorized operator.If the NFT referenced by
assetId
does not exist, then the call MUST throw.If there was any single authorized operator (see the
approve()
method below), this authorization MUST be cleared.If the call doesn't throw, it triggers the event
Transfer
with the following parameters:ownerOf(assetId)
before the callto
argumentassetId
argumentmsg.sender
userData
argumentoperatorData
argumentIf
to
is a contract's address, this call MUST verify that the contract can receive the tokens. In order to do so, the method MUST do a #165 check for support to handle tokens to the target contract. The implementation for the receiver is as follows:If the
supportsInterface
method call fails, the transfer must be reversed and this call MUST throw. Otherwise, the methodonAssetReceived
MUST be invoked with the corresponding information, after the asset has been transferred.transfer(address to, uint256 assetId, bytes userData)
Shorthand method that MUST be equivalent to calling
transfer(to, assetId, userData, EMPTY_BYTES)
.transfer(address to, uint256 assetId)
Shorthand method that MUST be equivalent to calling
transfer(to, assetId, EMPTY_BYTES, EMPTY_BYTES)
.transferFrom(address from, address to, uint256 assetId, bytes userData, bytes operatorData)
Transfers holding of the NFT referenced by
assetId
fromownerOf(assetId)
to the addressto
, ifffrom == ownerOf(assetId)
.After checking that the asset is owned by the
from
address, this method MUST behave exactly as callingtransfer(to, assetId, EMPTY_BYTES, EMPTY_BYTES)
.transferFrom(address from, address to, uint256 assetId, bytes userData)
Shorthand method that MUST be equivalent to calling
transferFrom(to, assetId, userData, EMPTY_BYTES)
.transferFrom(address from, address to, uint256 assetId)
Shorthand method that MUST be equivalent to calling
transferFrom(to, assetId, EMPTY_BYTES, EMPTY_BYTES)
.Authorization
authorize(address operator, bool authorized)
If
authorized
istrue
, allowsoperator
totransfer
any NFT held bymsg.sender
.This method MUST throw if
operator
is the zero address. This method MUST throw ifauthorized
is true andoperator
is already authorized by the sender. This method MUST throw ifauthorized
is false andoperator
is unauthorized.This method MUST throw if
msg.sender == operator
.This method MUST trigger an
AuthorizeOperator
event if it doesn't throw.approve(address operator, uint256 assetId)
Allow
operator
totransfer
an asset without delegating full access to all assets.Only the
owner
of an asset can call this method. Otherwise, the call MUST fail.Only one address can receive
approval
at the same time. Clearing approval for a transfer can be done by setting theoperator
value to the zero address.This method MUST trigger an
Approve
event if it doesn't throw.Events
Interfaces
Implementation
https://github.com/decentraland/erc821
Revisions
transferFrom
operator
from public facing interfacesholder
->owner
to use the most common nameapprove
to approve individual assets to be transferredmsg.sender
being the managerERC820
andIAssetHolder
interface checkIAssetOwner
forIAssetHolder
to reflect that another contract might hold tokens for onetransfer
textdecimals
for more ERC20 compatibilityonAssetReceived(...)
is implemented.isERC821
flag to detect support.holder
toowner
.transfer
:to
MUST NOT beholderOf(assetId)
safeAssetData
methodtransfer
: it MUST throw if the asset does not exist.Copyright
Copyright and related rights waived via CC0.