A standardized event to attribute ownership for ERC721 NFTs. Currently, platforms assume the wallet submitting the deploy transactions is the author/creator, but that is not always true. This EIP seeks to standardize the way we validate and attribute authorship.
today we lean on getters to identify the creator - tokenCreator(uint256 tokenId)
or owner
/admin. originally we were hoping to see the former, or something similar, become widely adopted. but as we open up, weād be vulnerable to spoofing since the contract could return or emit any address.
this seems like a good approach to attribution without needing to trust the factory or underlying implementations. itās unfortunate that weād need to include a second prompt when creating collections, to get their 712 signature - so would need to talk it over with the product team to see if we have appetite for this before we integrate.
EIP-1271 support is a good call out to include (mentioned in offline discussion). some other thoughts on this eip:
- The spec suggests this is only for creation time. Seems worthwhile to support changes too - the contract could just emit the same event again after any additional checks (such as approval from the original owner). Creators occasionally migrate to a more secure wallet, it may be nice to reaffirm their latest address
- If itās a collab, should we emit once per author? Seems like that should be okay - but makes me wonder if something more should be included for the account migration scenario to indicate the original is invalidā¦
- Some of the fields seem redundant - e.g. why
address token
in the struct when thatās already in the domain? why emittoken
andverifyingContract
when they are both the emitting contract (which is returned with the log from RPCs)? or could you clarify how this could be emitted by a contract other than the collection itself (or do we reject events where these fields do not match)?
- The spec suggests this is only for creation time. Seems worthwhile to support changes too - the contract could just emit the same event again after any additional checks (such as approval from the original owner). Creators occasionally migrate to a more secure wallet, it may be nice to reaffirm their latest address
My intuition is that managing āauthorsā post-deployment is most likely cumbersome and possibly unnecessary, given that the author is not the same as the āadmin/ownerā of the NFT. A way this could be used is, say, if a creator has a multisig but usually manages things with an EOA, the author can be the multisig, but the EOA can have all the permissions to make changes on the NFT.
One way it could make sense to have post-deployment authorship management is if there were different authors for each tokenId
. The EIP, for now, is meant to attribute authorship for all tokens in the contract to the same author.
- If itās a collab, should we emit once per author? Seems like that should be okay - but makes me wonder if something more should be included for the account migration scenario to indicate the original is invalidā¦
This is a very valid point. Multiple authors will require multiple signatures for the same message and multiple events. I believe this could work out of the box with the same definition; we can extend it to include multiple events with the same parameters but different signatures. Iāll think through the details and post an update.
- Some of the fields seem redundant - e.g. why
address token
in the struct when thatās already in the domain? why emittoken
andverifyingContract
when they are both the emitting contract (which is returned with the log from RPCs)? or could you clarify how this could be emitted by a contract other than the collection itself (or do we reject events where these fields do not match)?
Requiring the token
parameter in the struct ensures that the signature is only used once and prevents spoofing. Otherwise, an attacker could redeploy a token with the exact same parameters, generating a different NFT contract that they control.
There are two approaches to signature verification: through a factory and through the token itself. However, after your comment, I think we should simplify this to require the token to perform the signature verification; that way, we can remove verifyingContract
from the event.
I just found this EIP proposal after submitting the post here last week and opening up EIP-7050 today.
I had originally searched here for provenance
and creator
and a handful of other keywords but this didnāt come up
Looks like weāre both approaching the same desired destination via different paths
today we lean on getters to identify the creator -
tokenCreator(uint256 tokenId)
orowner
/admin. originally we were hoping to see the former, or something similar, become widely adopted. but as we open up, weād be vulnerable to spoofing since the contract could return or emit any address.
This is our approach in ERC-7050. We allow the contract owner to set who the creator of a token is, and then require the creator address to call verifyTokenProvenance(tokenId)
. Only if the contract owner has set their address as the creator of tokenId
will the contract state be updated so that a call to provenanceTokenInfo(tokenId)
returns (creatorAddress, isVerified)
(where isVerified
will be true
).
We expect this can be extended to handle both batches of tokenIds and to cover an entire contract as well.
Weāve taken this approach as the goal is to call a contract function. ie: as a Dapp developer I want to call provenanceTokenInfo
in my code so itās easy to list one or more NFTs with explicit creator info.
- Shall we team up?
- Is there a good reason to have events AND the equivalent of
tokenCreator()
as different EIPS? - Have we overlooked something that makes our approach in ERC-7050 a poor tool for this job?
Here are a few thoughts on why I donāt necessarily think we need to have setter/getters for authors:
- When it comes to provenance, in the most practical sense, I donāt believe the āauthorā necessarily changes. The author is the creator of the piece. I understand getters/setters for the provenance of specific tokens, thatās what 721 is, but even then, thereās no record-keeping of the chain of ownership other than through emitted events (the same approach ERC-7051 takes).
- The current specification for ERC-7050 is easily spoofable, thereās no method for verifying a certain
wallet-x
is the true creator of a token, one can easily create a getter method that returnswallet-x.eth
as the creator. To truly verify thatwallet-x.eth
is the creator, you would need to inspect all transactions submitted to the contract and verify that indeedwallet-x.eth
calledverifyTokenProvenance
. One needs to trust the NFT contract is not behaving maliciously. ERC-7051 uses signatures to validate ownership, it makes for a better UX (the creator doesnāt need to own any ETH), since the transaction can be submitted and anyone can verify the authorship. Thatās why the signature is emitted in the event. - Storing signatures on-chain is expensive, and to have a getter/setter that can be truly verified, weād need to store signatures onchain and return them along with the provenance-token-info (more like ERC-5375 in terms of interface, the specification stores data offchain). Something I donāt think itās truly necessary. Curious why the need to be able to query for token provenance offchain? I see a potential argument to be able to do it onchain (say from a different contract) for composibility or redirecting funds, but I think a better approach would be to use some sort of splitter contract. But when it comes to querying it from offchain, we could just look at the events emitted previously. ERC-7015 doesnāt specify change of āauthorshipā as per my first point above.
- I can see the need for multiple authors, ERC-7015 does specify a way to attribute authorship to multiple wallets, however, it doesnāt specify
tokenIds
. This is something Iām still considering but I think it would make a good addition. I will follow up with some changes regarding this.
Since posting the proposal for ERC-7050, weāve also discovered ERC-5375 which is similar in goal. How does this proposal differ and how is it similar to the proposed ERC-7015 discussed here?
Agreed that authorship does not change.
As far as I know, we currently donāt have explicit getters/setters for the provenance of explicit tokens and that is part of what the proposed ERC-7050 aims to do.
The chain of ownership is outside the scope of what the proposed ERC-7050 aims to cover.
I assume you mean ERC-7015 here
In the current draft proposal, only the owner of the contract can set who the author is, and only the author can successfully call verifyTokenProvenance
. I donāt follow how this is spoofable.
If the author is the only one that can successfully call verifyTokenProvenance
why is this needed?
I assume you mean ERC-7015 here
An author proving their authorship without owning ETH is a fantastic UX
If I communicated this need I think it was in error. I donāt believe I did.
100% multiple authors is important.
I agree that we need a standard for determining the creator of a given NFT!
Iām curious why the nomenclature of author
? creator
seems to be a widely used and understood term in the NFT space already, and is a broadly understood concept. It also doesnāt collide with the concepts of āminterā of the NFT, ādeployerā of the NFT contract, or owner of the NFT. Also, the EIP itself defines the author as āthe creator of an NFT
ā, so it seems like creator
might be the simplest and most appropriate term to use here.
I agree here, and believe adding a getter would be helpful in making this EIP more widely adopted. I agree this getter could easily be spoofed.
Iām having trouble understanding how the spoofing of the getter would be functionally different from the spoofing of the emitted events already included in the EIP. For both the EIPās existing events and a potential getter, wouldnāt the party interacting with the NFT contract ultimately have to trust and/or verify that the creatorās signatures are being verified properly by reading the source code of the contract itself?
I would propose itās easier to keep this idea simple and only allow for āoneā author. I believe allowing for multiple introduces lots of surface area for complexity (if you have multiple, how can you be sure youāve queried all of them, is there a hierarchy amongst them, etc). By allowing for only one account to be the author, you allow the proposed EIP to be simple and also push the responsibility of explaining the complexities of a multi-author situation to the author themselves. For example, a single author could actually be a smart contract, which itself further elaborates on the multiple authors, etc.
We have seen similar issues play out with ERC-721ās ownerOf
, which, by only allowing for one owner, pushed the complexities of collective ownership, fractionalization, etc. to the owner. Ultimately, I think this contributed to the success of ERC-721.
Yeah, creator sounds better. Iām good with using that!
So two things; first, ERC-7050 doesnāt specify signatures, so thereās no way to verify a creator has given consent without inspecting that the creator submitted a transaction that was intended to do what the ERC specifies. Second, for ERC-7015, it is a requirement for validating attribution offchain that the event is emitted from the token
contract and matches the same token
address that was signed by the creator. So even if I take a signature that belongs to someone else and emit it from a new contract that I deployed in an attempt to spoof creator attribution, I would have to figure out a way to deploy a contract with my own malicious logic at the exact same address as the token
signed. In practice, since the token
signed has to be generated before the signature, there are two ways to generate a token
address:
- Using a factory contract that will deploy ERC721s with
create2
- Using an EOA and with the nonce predicting the next address that will be deployed.
In the first case, it is impossible with create2
to deploy a token contract with a different implementation
than the one used to generate the token
address and still get the same token
address. This also assumes that the attacker has access to specifying which implementation to use within the factory to deploy the contract. The key thing to understand is that weāre using the implementation
logic as an input to generate a token
address ā so thereās a guarantee of what the intention of the creator was. For all intents and purposes, spoofing this way is impossible.
In the second case, there is no way to encode or use the implementation
logic as input into the token
address since the EOA can deploy any logic and still get the same token
address. However, this attack vector assumes the attacker has somehow acquired access to the EOA that will deploy the contract ā something we cannot prevent with the ERC specification.
Both approaches suffer from creators signing messages unintentionally, but we cannot prevent this, and this is also specified in Security Considerations section. I can add these considerations to the specification and also recommend using a factory and create2
as the preferred method of implementation.
The spec currently supports multiple creators; it only starts adding complexity when you think of which token ids specific creators created since thereās really no way to specify multiple creators for a token id; hence I agree with you that we can push that complexity into the accounts. And it would simplify other aspects. For example, with the current support for multiple creators, if, say, a new token id is added, and we want to signal that thereās an additional creator, weād have to support emitting the event on a transaction after deployment. In which case, if youāre looking for all the creators of the NFT, youād have to search all transactions for AuthorAttribution
(soon CreatorAttribution
) events instead of just the deployment transaction.
I am still unconvinced we need to add getters to this ERC and much less setters since that creates more complexities. The purpose of the ERC is to signal the correct creator in a simple and verifiable way during the deployment transaction. The currently proposed alternatives do not provide a verifiable way of proving who the creator is because they lack signatures. They also hinge on the idea of a contract owner, which is merely a convention and not a standard, and even then, it is still easily spoofable.
Edit: one caveat is that changing the byte code and still get the same address could be done with metamorphic contracts, but as I mentioned above the attacker must also acquire access to the factory that deploys the contracts. This is most likely to happen if the attacker gets the creator to provide a signature directly to them rather than stealing a signature that was meant to be used by a different system. We canāt protect against creators signing unintended payloads from untrusted sources.
Is the scope of this EIP around things happening up until the ERC721 contract has been deployed and not related to things after that? ie: Emit certain events on contract deployment. Not emit events after contract deployment.
Also, in EIP-750, although the NFT extension is designed so that itās only possible for the person(s) or machine(s) which set the metadata of each token to set the address which is the creator, and then only possible for that creator address to accept authorship, it is becoming clear that signatures can be useful. Weāll explore some good ways to do that.
Thanks for putting together this great proposal. Definitely helps address the problem of most places using the tx.origin as the ācreatorā instead of the contract creator, which becomes an issue when a creator wants to sign a message to create a token that someone else executes.
have some feedback/thoughts.
Isnāt the token
parameter the same as the verifyingContract
param that is part of the domainSeparator in EIP712? When would it be different? Even if they deployed the token again, it would have a different address and the signature would be invalid if the contract address changes regardless of whether or not you had a token
parameter because the verifying contract address would change in the domain separator.
Now regarding the signed message with the format:
bytes32 public constant TYPEHASH =
keccak256(
"CreatorAttribution(string name,string symbol,bytes32 salt,address token)"
);
This removes flexibility in implementing this EIP as it forces the contract to have its signature for token creation in a specific format with a specific name.
Signatures for token creation serve the following purpose:
- validate that the token creation parameters are correct, and match what the creator intended
- validate that the creator signed the message.
So having the name CreatorAttribution
as the domain name implies that the purpose of the signature is to attribute the token to the creator, but really it has more than that purpose, also to validate that the token creation params are correct and to ultimately create the token.
Iād like towards a name of TokenCreation
and Iād rename salt
to params
.
Also, another approach would allow the structHash (abi encoded domain + arguments) to be made in any eip712 compatible format, and leaving it up to the developer of the contract to ensure that they follow that format and that the message signed contains all necessary parameters, include the entire structHash. This would give developers more flexibility to build the arguments to their signature the way they want and allow the existing signatures in their contracts to work with this standard. Youād still get the benefits that:
- signer of the message signed the message for the verifying (token) contract on the correct chain.
- all data in the event can be used to verify the signature was signed by the creator.
Something like this:
pragma solidity 0.8.19;
import "openzeppelin-contracts/contracts/utils/cryptography/EIP712.sol";
import "openzeppelin-contracts/contracts/interfaces/IERC1271.sol";
abstract contract ERC7015 is EIP712 {
error Invalid_Signature();
event CreatorAttribution(string name, string symbol, bytes32 structHash, string domainName, string version, address creator, bytes signature);
constructor() EIP712("ERC7015", "1") {}
function _validateSignature(bytes32 structHash, address creator, bytes memory signature) internal {
if (!_isValid(structHash, creator, signature)) revert Invalid_Signature();
emit CreatorAttribution(name, symbol, structHash, "ERC7015", "1", creator, signature);
}
function _isValid(bytes32 structHash, address signer, bytes memory signature) internal view returns (bool) {
require(signer != address(0), "cannot validate");
bytes32 digest = _hashTypedDataV4(structHash);
address recoveredSigner = ECDSA.recover(digest, signature);
return recoveredSigner == signer;
}
}
Hey Dan, thanks these are all very good suggestions.
Youāre right. The token
parameter is encoded into the domainSeparator
; an earlier iteration of the EIP had the signature validation performed in a factory contract, so I included the token
parameter. When we moved the signature verification to the token to accommodate direct deployments (no need for factories), it became unnecessary to include the token
parameter in the event. I will update the EIP to reflect this.
Cool with this; renaming makes sense to me.
This is interesting; if we were to take this route, I wonder if you think it makes sense to remove the name
and symbol
parameters from the CreatorAttribution
event and expand the EIP to support ERC-1155 NFTs as well. Given that those two parameters are not top-level params on 1155, I first decided to restrict this EIP to 721s, but if weāre being more flexible with the signed hash, it could also support 1155s. However, at that point, the struct has just one parameter, bytes32 params
ā which feels a bit off since itās no different from signing a single message hash and obscuring things a lot, making it easier for users to sign unintended messages. At the same time, we should try to make it as general as possible for wider adoption. Do you have any thoughts on this?
And one more thing Iām wondering, I think it makes sense to also remove creator
from the emitted event and just let indexers assume that the recovered signer is the creator. Having the parameter in the event would only leave open the possibility of a mismatch between the emitted creator and the recovered signer, but in the end, what matters is the recovered signer.
Some thoughts from me.
IMO ERC-1155 should be included and if weāre including it, then the standard should be more generic than NFT use cases. Ultimately what this is accomplishing is a standard for attributing a contract creator, where in the NFT case all tokens minted on the contract are defined to be created by the contract creator.
I donāt really see a reason this needs to be NFT-specific.
Still contemplating it, but I think it might make sense to allow this attribution to be dynamic and updatable by the contract owner. This means supporting setters/getters and not restricting it to be set via the constructor.
I think getters would make it easier for wider adoption, i.e. Ownable. For example, supplying the relevant data via creatorMetadata()
and leaving it up to the client to verify. I think the trade-off on ease-of-use vs cost is worth it.
Similarly a use case I can think of for setters is to onboard wallet-less creators. First collector deploys the contract and mints, and later on the creator claims ownership of the contract and sets their new wallet as the creator, something weāve been exploring with EIP-6981.
Collabs are important imo and the current approach makes sense to keep it light. Might be worth explicitly calling this out in the EIP. If we wanted to add getters then I think spec should be changed to support arbitrary number of creators. (In general this is even more important if weāre switching to a more generic standard).
Suggestions:
- Instead of a
name
andsymbol
, just have a singlemessage
. - Rename
salt
toparams
, or alternatively allow any EIP-712structHash
that incorporates thetoken
address.
emit CreatorAttribution(message, structHash, "ERC7015", "1", creator, signature);
The recent update is great. I would suggest a few minor tweaks:
As @Vectorized, mentioned, Iād rename params
to structHash
to align more with what the EIP-712
standard calls it and what devs are used seeing, and also what the openzeppelin contract calls the argument.
I would also show a more concrete example that looks more like what a real contract would look like: taking explicit args (such as token id, name, etc), building the structHash
by hashing a const DOMAIN
+ those params, then calling _hashTypedDataV4
with those params. It would be nice to show how this would work with both erc721 + erc1155, so to show an erc721 and erc1155 example would be great.
Regarding:
This is interesting; if we were to take this route, I wonder if you think it makes sense to remove the
name
andsymbol
parameters from theCreatorAttribution
event and expand the EIP to support ERC-1155 NFTs as well. Given that those two parameters are not top-level params on 1155, I first decided to restrict this EIP to 721s, but if weāre being more flexible with the signed hash, it could also support 1155s. However, at that point, the struct has just one parameter,bytes32 params
ā which feels a bit off since itās no different from signing a single message hash and obscuring things a lot, making it easier for users to sign unintended messages. At the same time, we should try to make it as general as possible for wider adoption. Do you have any thoughts on this?
Yeah I can see the issue with that. One possible approach would be to have two separate events, one for erc1155 and one for erc721 with erc1155/erc721 specific fields.
However I still see the issue that sometimes the signed message needs to contain more than the name and symbol; for example if there are not auto-incrementing ids, then the token id needs to be included. Or if there are pricing settings (i.e. how much this token costs) you need to include that in the signature.
I suppose this could be accomplished with:
params = hash(tokenId, price)
structHash = hash(DOMAIN, name, symbol, params)
I think both approaches are fine, but whatever the final approach is Iād try to include support for erc1155 which has exploded in popularity recently with the rise of open editions
On further thought it actually may make sense to include the contract address in the event; this would allow the signature to be created against other contracts than the token contract itself;
Letās say for example, you have a factory contract that is granted permission to create tokens on an erc721 or erc1155 contract on behalf of a creator; the creator would sign a message that the factory contract would validate. In this case; youād want to have some contextual awareness of the address of the factory in the event;
If the factory was to emit the TokenCreation
event, would that still be recognized? Or would the approach to support this to be to have the TokenCreation event emitted from the contract, but add the validating contract (in this case the factoryās) address to the event?
This was the original design, and moving the validation logic to the token contract was made so the standard could also be supported when deploying directly through a contract creation transaction. I want to keep it that way, and I think it makes the most sense that the token contract also emits the TokenCreation. What are some of the reasons youād rather have the signature be made for the factory contract?
Has there been any discussion into making this queryable on-chain as well? I could see this being extremely helpful for marketplaces when trying to determine the difference between a primary and secondary sale, as one example. There are probably other use cases for on-chainness (if thatās a word).
Excited about this proposalās ability to improve DX when it comes to facilitating token-based information exchange.
I am disappointed that there is no ability to specify creator attribution at the tokenId
level. This is relevant for app-level shared token contracts and small-group/collective owned contracts, where the ability to group all activity under one contract while still enabling tokenId-level attribution would be a nice improvement.
I think the simplest way to enable this without making edits to the existing implementation would be to allow for the emission of additional CreatorAttribution
events post contract deployment that include hashed tokenId(s) in the structHash
. It could be assumed that if only one event is emitted during the token contracts lifecycle that all tokenIds should be attributed to the creator
specified in the original event, while follow up events could signal overrides for whatever tokenIds are included in the their respective structHashs
.
Not sure how close this is to finalization but think this could be a reasonable fix (doesnt change anything at the impl level besides getting indexers onboard + providing an additional example) if others are interested. I for one am!