ERC-6551: Non-fungible Token Bound Accounts

Since token bound accounts are deployed as ERC-1167 proxies, users can easily verify the implementation for any token bound account. The implementation contract can be verified once, and the account instance contract can be verified by introspecting the proxy to determine the implementation address. On Etherscan, this can be done by clicking “is this a proxy?” on the token bound account contract.

Additionally, because token bound account addresses are deterministic, you can verify that an account is using a trusted implementation by computing the address for the account with that implementation and checking that it matches the address being used. This can be done prior to the deployment of the account contract.

1 Like

I mean that it’s unrealistic for average users to use Etherscan to verify each contract. But probably this is a UX issue, rather than TBA itself. I’ll consider how to improve the UX.

I wonder if there is any news about the new proposed name.

@sullof For sure! Here are a few of the options we’ve been considering if the owner() function is to be re-named:

  • executor()
  • operator()
  • signer()
  • controller()
  • custodian()
  • steward()
  • manager()
  • admin()

The goal of these names is to reduce the emphasis on the holder of the token as the canonical owner of the account, since the token itself is the owner. The holder simply has temporary execution permissions.

However, as discussed earlier in this thread, the concept of a canonical controller makes it difficult for this pattern to be applied to tokens that lack a canonical owner. Additionally, there is currently no way to determine whether a given address has execution permissions on an account without assuming that the executor must be the owner. This assumption will likely be broken in practice quite quickly. As a result, it might make sense to replace owner() with a new method that can be queried to determine whether a given address has permissions on the account.

Here are a few options that are being considered:

  • is...(address) returns (bool) for any of the above names (e.g. isExecutor).
  • isValidSigner(address) returns (bool)
  • isAuthorized(address) returns (bool)
  • controlledBy(address) returns (bool)
  • managedBy(address) returns (bool)

Another option is to add a function that can be queried to determine a caller’s ability to execute a certain function on the account. This could look like:

  • isAuthorized(bytes4 sig, address caller) returns (bool)
  • canExecute(bytes4, address) returns (bool)
  • hasPermission(bytes4, address) returns (bool)

This could be even further extended to accept the full calldata bytecode as a parameter instead of just the method signature. Something like:

  • canExecute(bytes calldata data, address caller) returns (bool)

Additionally, each of the above functions which return a boolean could return a magic value instead, allowing for further extension of functionality at the account implementation level.

I don’t want to stray too far into mandating specific access control patterns here (e.g. role-based permissions) as I think those are out of scope for the proposal. However, access control is a central part of this proposal and I think some minimal pattern should included in the account interface.

Part of the challenge here is determining a term that can be used in place of owner to properly captures the distinction between the token (which “owns” the account) and the token holder (which “uses” the account).

I’m leaning towards the term signer, as I think it fits well with existing smart contract account terminology (e.g. “signer on a gnosis safe”). It’s also similar to the verbiage used in EIP-1271.

I would propose that the owner function be replaced by the function:

  • isValidSigner(address) returns (bytes4)

The proposal would specify that the token holder should be regarded as the default signer and that all valid signers should also have execution rights.

Would love any and all feedback on these potential interfaces :slight_smile:

4 Likes

Thanks so much for the update.

I agree.
signer makes a lot of sense in the context of the account.

To keep the same approach of owner(), maybe you can go with signer() instead of the more verbose isValidSigner(). I am not sure if signer() creates any conflicts with existing EIPs, but I don’t recall any general EIP where that was used.

I understand the connection salt has with an account’s address and the idea of leveraging this for authentication is really cool, but then wouldn’t it make more sense to keep this private as some kind of key?

Also, since you mentioned that some smart contracts may use salt as authentication, not all of them, then this should not be a requirement (i.e. SHALL) but rather an option (i.e. MAY) for the implementer of the proposal. But we have to acknowledge that including or excluding the salt (or any data) from the bytecode changes the address computation using CREATE2.

So, I’m wondering, does the standard allow for some flexibility regarding the appended data and what can be introspected from the bytecode? I predict the answer is no, so let’s try to answer this follow-up question: Can we introspect arbitrary data in some other way?

I’m asking this because the account bytecode doesn’t actually know that implementation contains a smart contract account, as far as the ERC-1167 proxy is concerned, it is an arbitrary address. Therefore, I’m exploring the possibility of other token-bound approaches, or even a more universal NFT-owned smart contracts proposal, where the ability to introspect arbitrary data from the proxy bytecode is crucial, as well as its compatibility with TBAs.

1 Like

While you can use the value of salt as input data to the authentication scheme of the account (e.g. for storing the threshold balance of ERC-1155 tokens required to use the account), the primary behavior of salt is to allow multiple account instances for the same account implementation per token. It doesn’t make sense to keep this data private, as it’s not a key. Storing the salt data allows for the account implementation to access it’s value (which enables it to be used within the authentication logic) and allows the account to re-compute its own address locally (which is useful for proving account authenticity when calling into other contracts). In short, salt isn’t designed to store private data.

salt is a required input value for all token bound accounts as it affects the account address computation. Users that don’t wish to customize the salt can default to a salt value of zero. Because salt is not intended to be used for authentication and is required for all accounts, I don’t think it makes sense to change the language here.

Currently the appended data is determined by the registry and cannot be customized. This is to ensure that the bytecode for any token bound account is easily computable without relying on external data sources.

Using a custom implementation allows you to store any data you wish to using smart contract storage. This allows for easy querying of data. However, smart contract storage behind a proxy cannot be constant or immutable.

Correct, although querying whether a given implementation is a smart contract is trivial by extracting the implementation address from the account bytecode and querying it’s code length.

I’d love to hear more about your use case here! What arbitrary data are you hoping to append to the account bytecode? I’d be open to including an optional extraData parameter in the account creation function that allows for additional arbitrary data to be embedded in the account contract if there is sufficient interest. The tradeoff would be making deterministic calculation of token bound account addresses more difficult for applications integrating with ERC-6551.

2 Likes

Let’s take a token bound ERC20 for example, where the NFT gives access to owner methods (e.g. configuration, minting, etc.). In this case the implementation contract would need to introspect name and symbol that would be passed to the registry at the moment of deployment.

Let’s imagine an auction contract where the fee per transaction is set at deployment and cannot be changed, whoever owns the bound NFT will receive the revenue from fees. In this case, the implementation contract should be able to introspect the fee from the proxy bytecode.

Essentially, I believe we should be able to introspect any type of immutable data configurable at deployment, which in a traditional scenario would represent Solidity immutable variables.

Btw, by “introspect” I mean performing Bytecode.codeAt(address(this), start, end) as in the reference implementation, where address(this) would return the EIP-1167 proxy address in a delegatecall, hence we get the proxy bytecode.

That being said, I still believe salt is not a requirement (but an option) for the proxy bytecode, it could be part of extraData in the context of this proposal. On the other hand, having chainId, tokenId, tokenContract is a must.

1 Like

I see. This use case is pretty far outside the scope of this proposal, but should be easy to accomplish without any proposal changes by separating the account and ERC20 contracts. You can create an ERC20 contract and set an existing token bound account address as the owner. That way the ownership of the ERC20 contract is permanently bound to the NFT’s account, but neither the token bound account or the ERC20 contract need to be aware of each other. This also removes the need to append additional data to the token bound account bytecode within the proposal.

This could also be easily accomplished with an external auction contract that uses immutable storage. There is no need to include ERC20 or auction logic inside of a token bound account.

salt is required as a parameter in order for the registry to have the ability to create multiple accounts per instance/NFT combination.

1 Like

I see, then that is a reasonable cause to include it in the bytecode.

You’re right, I failed to see the bigger picture here. Essentially, we can introduce token bound functionality to any smart contract by making certain interactions to be performed via a token bound account.

Still, I wonder if any other folks can find use-cases where the extraData would make sense.

1 Like

Hi @Jay,
Thankyou for this wonderful new ERC and all your replies.

I’m considering implementing this ERC in a project I’m starting, but I’m worried that the interface could change. Is there some expected finalization date? Forgive my lack of experience with the process. Thankyou.

I came here intending to write this same comment.
A project I’m working on could really benefit from TBAs and I’m eager to use 6551, but I’m not familiar enough with the ERC process to know where this proposal stands.
What’s it waiting for? Is it “safe” to implement it today?

The current version of this EIP is in “Draft” status. As the tooltip on the “draft” status on that page indicates, EIPs in draft form might still have significant changes made before finalizing, so it’s not recommended to rely on its current form just yet. The EIP process itself is laid out in EIP-1, which indicates the next step for this EIP is the original author flagging it as ready for peer review, which moves it to the “Review” stage.

1 Like

My options!

Just posting a link to a competing EIP idea isn’t all that helpful in furthering discussion. If you think the proposal in this thread can be improved in specific ways, can you call out what those specifically are? The EIP idea you’re promoting is for very different use-cases (cross-chain interactions) and therefore I don’t really see how it adds “options” to this proposal?

There is a discussion going on about the name of the function to get the owner of the bound account. That must be solved before move from Draft to another state. That, of course, is a problem for everyone is implementing this EIP.

@jay I am assuming that you are going with isValidSigner. Any news about it? I haven’t seen any PR on the repo.

I am mainly concerned about usage scenarios. For example, whether this standard can collect assets in multiple chains. As I said in the article, I have an nft on eth. Can I use this to hold other nfts on polygan? ? And can this nft be held by multiple parties and jointly promoted?

The goal of ERC-6551 is to give each ERC-721 token a token-bound account with full Ether account functionality, which makes NFTs no longer just static assets, but with more utility and functionality, opening up a world of new possibilities. The realization of this concept may lead to a variety of new applications and innovations that will allow NFT to play an even greater role in the digital asset and blockchain space.

ERC-6551 does not cover those two scenarios. However it does serve as a base logic layer that both those ideas could be built on top of as separate EIPs/applications.

ERC6551 specifies a way to associate smart-contract wallets to any NFT. That works only on the chain where the account is deployed. To manage assets on different chains you should use a bridge like Wormhole, but that is out of the scope of this proposal. If you like to explore that, you can consider using GitHub - ndujaLabs/wormhole-tunnel

About the second question, you can do that letting a multi-signature wallet hold the NFT that holds the bound-account.