ERC-6551: Non-fungible Token Bound Accounts

When integrating the ERC6551Registry contract into other smart contracts, there is currently no way to securely validate that a passed address parameter is in fact the official ERC6551Registry address.

This is because ERC6551Registry in the reference implementation does not implement ERC165, the standard interface detection pattern. As a result, even though ERC6551Registry extends IERC6551Registry, a consuming contract cannot actually detect and prove the interface is properly implemented.

I can make the change and raise a PR for it, if you like.

Thanks for the comments @arrans!

I agree that this should be fleshed out more. There has been a lot of research done in the area of fraud prevention that should be highlighted in the proposal.

Since state is managed by the account and all operations that interact with tokens must be executed via the account, state commitments can ensure that no tokens have been transferred away from the account via direct transactions. The edge case here is approval-based transfers, which would not change account state.

However, account state can be deterministic based on operations executed from the account. This would allow commitments to future states that are only reachable if certain conditions are met (e.g. all active approvals have been revoked). Using deterministic state mitigates the shortcomings of the currently suggested fraud prevention schemes. We are currently considering how this should be written into the proposal, and would love your feedback.

This is a good point, and should be considered in a future proposal.

Since the Registry is a singleton contract whose address will be well-known and included in the proposal, contracts can trivially validate whether a given address matches the canonical Registry address. This address check also guarantees that the IERC6551Registry interface is properly implemented. As such, unlike other ERCs where multiple instances of a contract implementing a certain interface are expected, I don’t think ERC-165 interface detection on the Registry provides much benefit here.

That said, I’m not opposed to adding interface detection if there is a enough demand or a compelling argument for it.

1 Like

I’d like to point out a few considerations with the Create2 approach. Ensuring the same address across different blockchains involves adhering to a meticulous process which may not always be feasible. Imagine a scenario where a novel blockchain emerges in the future, and the nonce on that blockchain isn’t consistent with the wallet’s previous deployment nonce. Or consider wanting to leverage an optimized proxy size for new blockchains. There could be numerous reasons making it challenging to maintain the same address universally.

Now, let’s examine this from the perspective of a developer working with ERC6551. When coding the smart contract, hardcoding the address isn’t viable, as it would only be operational on the mainnet and not on staging or during development. Hence, the registry address would be input as a deployment parameter. But then arises the challenge: how does one ensure minimalistic yet effective validation of the address to prevent unintended inclusions? Currently, a feasible approach is implementing a try-catch mechanism and verifying the output of the ERC6551Registry.account function. However, in my opinion, a standardized support for the interface would streamline this process and enhance its robustness.

I don’t think this is an accurate characterization. The proposal specifies that the registry must be deployed using Nick’s factory in order to ensure the widest possible support across EVM chains. This factory exists on 31 chains according to BlockScan, and can be deployed to any EVM compatible chain using a pre-signed transaction. Additionally, because this factory is widely used, several chains that do not support pre-signed transactions have included it as a pre-deployed contract.

This proposal is dependent on the create2 opcode, which allows for deployments that are not tied to a given account nonce. Chains that do not support create2 will not be able to support ERC-6551 as a whole (as it requires EVM compatibility), regardless of the method used to deploy the registry contract.

If there are additional optimizations you would like to see included in the proxy bytecode I would love to discuss them and see if there is a way they could be incorporated into this proposal.

The registry can indeed be deployed to both testnets and local development environments at its canonical address. Instructions for how to do so should be documented, this would be a good addition to the reference implementation repo.

If the registry is deployed to staging / testnet environments, this is no longer an issue for developers.

I don’t think ERC-165 would be any more useful than an address equality check for this use case. ERC-165 makes a lot of sense for use cases where many contracts implement a common interface (such as the ERC-6551 accounts), but not as much sense for singleton contracts.

There are blockchains that enforce EIP155-like replay protection for security reasons. If that happens, Nick’s method doesn’t work because the chainId has to be part of the transaction. Did you try to deploy it on CELO or CELO Alfajores?

PS > I use CELO Alfajores as favorite testnet.

Nick’s factory already exists on Alfajores despite Celo’s lack of support for pre-signed transactions. As a result, the registry can be deployed without issue. Any other chains that enforce replay protection can include this factory as a pre-deployed contract as Celo has done.

1 Like

Then I don’t have other questions. Thanks

1 Like

Interesting! Paraphrasing to confirm that I understand: we can prove that it was impossible to perform a chain of actions that result in the assets being transferred therefore it is safe to assume that no fraud can be committed.

I think from a technical perspective this is a really elegant solution however I think that buyers still risk being cheated because it’s so much harder to reason about relative to the alternative of declaring transitively owned assets that the buyer expects. That said, I’m keen to see what you write up so please do share it.

1 Like

Just checking in after reviewing the last draft
Really good progress, as I was reading, was skeptical at first of the multi-implementation per NFT but I agree in the end that is it actually a great way to not constraint implementations. Nice!

I got few comments

  1. Feel like the Account standard should be a separate ERC.
  2. on that note, execution standards has been attempted before and I wonder why you chose to use a different spec than ERC725X (ERC-725: General data key/value store and execution) which define 2 functions:

function execute(uint256 operationType, address target, uint256 value, bytes memory data) external payable returns(bytes memory)

function executeBatch(uint256[] memory operationsType, address[] memory targets, uint256[] memory values, bytes[] memory datas) external payable returns(bytes[] memory)

It is really similar than the one specified in 6551 except for

  • it has STATICCALL
  • it uses 256 bit to represent operation type
  • it support batch operation in its interface
  • have associated events
2 Likes

@wighawag thanks, that means a lot! I definitely agree that there should be a separate smart account execution interface standard, and that this will likely become standardized in the future. Unfortunately there is no strong standard that this proposal can adopt right now, which is why an optional, minimal execution interface has been included in the spec. In my opinion this strikes a balance between having a standard interface across 6551 accounts right now while also allowing future standardization to be adopted without changes to the proposal.

Unfortunately, because ERC-725 has been in draft stage for a long time and does not have a clear path towards finalization, including it as a dependency would mean that ERC-6551 could not become finalized before ERC-725 does.

There are also several developer experience papercuts that exist in ERC-725X that have been adjusted in the recommended ERC-6551 execution interface:

  • using uint8 to represent operation types allows implementations to represent the value of operation as either a uint8 or an enum in their codebase without affecting the execution function selector
  • ERC-725X’s representation of arguments to executeBatch as arrays rather than as a single array of structs introduces additional calldata overhead (and causes two of the parameter names to be grammatically incorrect)
  • ERC-725X’s interpretation of operationType values is different than the equivalent values in most common smart contract accounts (e.g. Safe, Kernel), where an operation value of 1 usually represents DELEGATECALL

If ERC-725 were to be changed to fix these issues and championed towards finalization, or another proposal to standardize the execution interfaces were to gain steam, I would fully support externalizing the account execution interface.

Would love to hear @frozeman’s thoughts on this.

1 Like

@jay, I’ve been implementing the Cruna protocol and have used the ERC6551Registry to deploy an NFT’s manager for each tokenID in a Cruna Vault. This experience led me to realize that the applicability of ERC6551 might extend far beyond merely linking wallets to an NFT. In fact, it seems capable of associating any relevant entity with a tokenId.

However, there’s a challenge I’ve encountered: contracts bound using ERC6551 may not feasibly implement the IERC6551Account interface due to the constraints posed by the receive function. Additionally, such contracts might not require signer validation functionality. Their primary need is access to the token’s data.

To address this, I propose an amendment to the protocol. I suggest introducing a new intermediary interface, IERC6551Bond, which could look something like this:

interface IERC6551Bound {
    function token() external view 
    returns (uint256 chainId, address tokenContract, uint256 tokenId);
}

This interface would specifically define a contract as bonded to an NFT and facilitate access to the NFT’s data.

In an ideal scenario, IERC6551Account would be an extension of IERC6551Bond, perhaps as follows:

interface IERC6551Account is IERC6551Bound {

    receive() external payable;

    function state() external view returns (uint256);

    function isValidSigner(address signer, bytes calldata context)
        external
        view
        returns (bytes4 magicValue);
}

What do you think?

PS > If you like the idea, I can raise a PR in the erc6551/reference implementation and in the ERC itself.

PS2 > I think that IERC6551Account’s interfaceId should remain the same since the selectors ar XOR-ed and the interface contains the same selectors.

PS3 > I just realized that it would be also better if in the case of a simple bond, the registry emits a different event — it looks unlikely to pass. Maybe I have to make a new proposal…

Could you expand on the constraints you’re referring to here? Implementing an empty receive function or one that always reverts is fine. Similarly isValidSignature and isValidSigner can always return invalid. It seems like this should cover most use cases.

Right now, the registry emits an event telling the world that an account has been created.
The way for a third party to know if that is actually a TBA is to verify if it supports the IERC6551Account and IERC6551Executable interfaces. In our use of it, since we do not support it, the user knows that, despite the emitted event, that is not an account.
It would be better, IMO, if the standard supports a recognizable interface that tells the world what that specific contract is.

In our discussions with various projects regarding the Cruna protocol and its capability to produce secure NFTs linked to TBAs, a predominant concern has emerged. Many projects are apprehensive about the current limitation in trading assets contained within a TBA on standard marketplaces. This concern primarily revolves around the technical challenge of recognizing the actual owner of an asset that is, in essence, held by a TBA, and subsequently authorizing that asset for sale.

I am aware that there are ongoing dialogues with marketplace operators to address this issue and establish a system for accurately identifying the rightful owner of such assets for transactional purposes. However, I am keen to understand the current progress in these discussions. As it stands, this hurdle appears to be the main obstacle preventing the widespread adoption of the ERC6551 standard. Any updates or insights on the status of these negotiations would be greatly beneficial.

The standard has the function:

function token()
        external
        view
        returns (uint256 chainId, address tokenContract, uint256 tokenId);

So, I assume marketplaces should be able to identify the rightful owner by checking owner of the corresponding NFT.
Then, what is the technical challenge of recognising the actual owner of an asset that is held by a TBA ?