ERC-6551: Non-fungible Token Bound Accounts

Would love to hear more about the use case you’re working on!

I don’t think an interface check at the registry level is necessary. Implementations are welcome to check for these interfaces, but I don’t think the proposal should require implementations to do so.

I’m fascinated by ERC-6551.

I’ve been working on a very similar vision at Locksmith Wallet (locksmithwallet.com). Quick shill to please read my white paper if you are interested in this ERC.

It uses a specific ERC-1155 contract that enables account owners to mint their own private collection of NFTs. Major departures from the base ERC-6551 vision is:

  1. Multiple NFTs per Account: Each Account can have its own collection of NFTs, with permissions. Can enable multi-sig schemes as well.
  2. Multiple Storage Locations: Store your assets in an on-chain vault, inside of a liquidity pool, or somewhere else, and the account can keep track and maintain security and permissions.
  3. Account owners can mint, burn, or “soulbind” tokens to specific addresses to embed in contracts or prevent phishing scams, signature risk, or re-selling/collateralization.

I’ve looked at the spec and I feel like I can make the VirtualKeyAddress and PostOffice contracts compatible with the IERC6551Account, as well as the Registry. In this way, I can offer my users a ERC-6551 compatible experience even though the project existed before the ERC.

A couple of questions I have (hopefully did not miss the details above):

  1. Do I have to integrate with a specific registry, or is having my own considered to be ERC-6551 compatible enough?

  2. Today, I assume that the EOA simply holds the NFT, and all of the assets (except gas, looking at you EIP-3074) are all on-chain. Does ERC-6551 prevent 4337 set-ups, and/or does it also require an EOA and gas (I imagine it would?)

  3. Does the custom-made ERC-1155 for Locksmith (And all of its admin capabilities) somehow proclude it from being ERC-6551 compliant? It’s not entirely clear to me.

  4. Why does IAccount return an address for the owner field? I would expect it to return an address and token ID, because isn’t the account owner an NFT holder, not a specific address? What design decision went into that choice?

Thanks for this awesome ERC and looking forward to contributing to the 6551 ecosystem going forward!

1 Like

I am working on a vault system that add extra-protection to the owning NFT (if not already deployed) and the owned assets. Both ERC6551 and ERC3652 work for me. Basically the Vault deploys an utility NFT that is owned by the Vault and that will have a bound account. The advantage of this approach is that as long as the user trusts the vault, they can let the Vault managing the assets, but at any moment the account can be “ejected” transferring the corresponded utility NFT to the owner of the OwningNFT.
In another variant, the contract that owns the bound account is not an NFT.

I agree. Also because applying IERC721 to the address and calling ownerOf would do the same.

@scotthconner awesome to have you here, thanks for the comments and questions!

  1. One of the goals of 6551 is to have a single registry that can handle all token bound account implementations. This has the benefit of giving the ecosystem a single entry point to query for addresses, and ensures that every address is deterministic so that it can receive assets prior to deployment. If there’s something about the existing registry that makes it incompatible with your use case, I’d love to better understand the issue and explore some potential solutions with you.
  2. Any account (EOA or smart contract) can own the NFT, and is granted execution permissions. Token bound accounts can be 4337 compatible (allowing them to pay their own gas fees) and/or be directly callable (allowing the NFT owner to pay gas).
  3. There are a few paths toward ERC-1155 support that are outlined earlier in this thread. It depends a little bit on how Locksmith 1155 tokens are structured (the biggest being whether they have a single owner per token ID or multiple). With a custom account implementation contract 1155 tokens can be handled in most cases.
  4. This is to enable (partial) support for EIP-173 and (full) support for EIP-5313, which are used to determine contract ownership in some applications. Since returning token information from the owner function would break compatibility with these types of applications, an additional token method was included to query the this data. In the case of ERC-1155 tokens with multiple token owners the owner method is not useful. Changes to this function have been heavily requested, and we’re planning to change it to explicitly support use cases without a single canonical owner.

Thanks Jay.

I imagine the singleton registry is to facilitate the discovery of the account address so DApps have a clear way of interacting with the account. Is that right? If so, have we considered putting discovery on the NFT contract itself? I realize some of the goal is to make it compatible with all existing NFT contracts, so a registrar() interface wouldn’t be backwards compatible, but it would allow you to send someone an NFT and immediately be able to discover all the on-chain metadata required to interact with the account without succumbing to a centralized registry.

My implementation has a “PostOffice” contract which holds a registry of virtual account contract addresses mapped to individual ERC-1155 token IDs. In this way, I have my own unified account registry, that my application uses to facilitate many things under the hood for virtual accounts, and the UI. To be fully compatible, I wouldn’t necessarily want to adopt another registry, and it might require some indirection for me to properly integrate. I’ve also made the design decision to require deployment before usage, versus counterfactual addresses, so the creation workflow is a bit different. I could change this, but for simplicity I went with what I did.

In either case, I have my own registry. I imagine others will to. For me, I also have my own custom ERC-1155. This enables an account owner to create a fully managed collection of NFTs that have a parent/child relationship. In this way, any root key holder for a “Trust” as it is called can freely mint, copy, or burn any NFT in their collection, or create new ones. Because the analogy is each NFT is a “key”, they operate like physical keys would and so there could be multiple holders of a particular key/permission/account. So a virtual account could be actually owned, at a contract level, by an NFT, and anyone holding it is considered an owner, even if the trust owner has minted multiple copies of it.

Architecturally - everything else is very similar. You have an NFT contract (KeyVault.sol), a virtual account (VirtualKeyAddress.sol), and a registry (PostOffice.sol). There are other moving pieces but those likely aren’t relevant to the ERC.

My main interest in this ERC are two fold:

  1. The mission to prove that NFT’s are more than JPEGs is gaining steam and folks are seeing the prospects of token gating increasingly complex contract functionality!
  2. I’m actively wondering if this will solve my impending dapp integration problem. I want users to be able to easily interact with DApps, without those Apps necessarily doing a direct integration with Locksmith (as using the NFT keys as session keys allow for things like no-approval swaps. Will DApps be able to support 6551 accounts and thus transparently support projects like Locksmith if I add adapters for the interface?

While implementing ERC-6551, I was thinking of possible security issues.
Let’s say that someone creates a malicious NFT that when the user executes a safeTransferFrom with a delegated call attacks the executer. Since to transfer something from the bound account to another wallet, we should trigger executeCall in the ERC6551Account smart contract, the malicious NFT could make any kind of damage, including destroy the bound account calling a self-destruct.
Am I wrong? If not, I guess we must wait for the Duncan hard-fork before using it in production.

2 Likes

@sullof I think any calls coming out of the NFT contract from inside _beforeTokenTransfer or safeTransferFrom would have the NFT contract itself as the message sender.

Assuming authentication for account actions requires that the NFT is held by the message sender, the NFT contract itself might not be able to attack the tx.origin or the account itself. At least, that is the way I’m thinking about it.

To your point though, being able to use any NFT opens up a bunch of re-entrancy opportunities and undefined behavior. For instance, an ERC721 could be fully conformant on the interface, but allows the NFT contract owner to mint their own copies of the NFT or otherwise steal any virtual accounts attached to it.

From a security perspective, this is why at least for my smart wallet project I’ve enshrined a specific NFT contract that acts as the permissions for the virtual account. The same security holes plague generic ERC-20 coins and thus the token contract itself is a critical component of any on-chain account’s security model.

In this way, using an ERC-6551 account as conceived would mean that:

  1. DYOR on which NFTs are considered secure to use for ERC-6551.
  2. Making the wrong choice could compromise the entire account.
  3. The threat model for the account is the entire NFT contract, the ERC-6551 implementation, and all subcomponents, combined.

@sullof In general, smart contract accounts should not execute delgatecall against untrusted contracts as that is a security risk. This is true for 6551 accounts as well as any other type of smart contract account. In the case of malicious NFTs held by a 6551 account, the account would use call instead of delegatecall to execute safeTransferFrom. There is little to no security risk in doing so, as the malicious contract would have no special permissions on the 6551 account to perform reentrancy attacks.

@scotthconner I don’t think there are reentrancy risks here, but you are correct that 6551 accounts are only as secure as the underlying NFT. If the underlying NFT contract allows the creator to arbitrarily change the owner of a given NFT, then the account bound to that NFT could be compromised. This is by design, and is a necessary trade off in order to allow every NFT to have it’s own account.

2 Likes

Thanks for the anwers.

1 Like

Hi all,

I’m excited about this new EIP!
I’m a little bit confused about a use-case:

Let’s say I want to implement a game where NFTs holders can buy accessories like hats, glasses, etc.
When an NFT holder buys an accessory, the character of the NFT should change to wear that accessory.
Do I still need to change the metadata of the NFT for that to point to the correct image?

Sorry if it’s a stupid question, but I could not figure out if this EIP could help in this use case.

Hi @lughino! This is something you can definitely build on top of ERC-6551, but it’s outside of the scope of the proposal. You would need to setup your metadata server to query the contents of the token bound account and modify the artwork of based on the contents.

This is a genuinely brilliant EIP proposal! I especially love the fact that it aims to support all existing NFTs.

Imagine a situation where an account holding 1M ETH is linked to an NFT. A malicious owner could collude with miners to sell the NFT on OpenSea. When the NFT is sold, the miner could prioritize a transaction to transfer the 1M ETH elsewhere before the atomic swap occurs.

Can EIP-5646 help here? The idea is to have a lockable token-bound account with an incremental nonce. Whenever the account is locked, the nonce will increase and update the EIP-5646s getStateFingerprint function. Marketplace/lending protocols can use the fingerprint in their offers. Of course, it would be ideal to propagate the information into the NFT of the account, which can be solved by implementing an auxiliary contract computing the fingerprint for the original NFT. Even without the auxiliary contract, this could be a semi-standard way to solve the issue of front-running withdrawals.

I used similar “account ownership via token” approach in a bundler. You can check it out here TokenBundler/src at develop · PWNFinance/TokenBundler · GitHub

1 Like

Hi @jay !
Thanks for coming back to me so quickly :slight_smile:

That makes sense! I was under the impression that I could do it in IPFS, am I wrong?

You can implement the ERC6551Account the way you like, which gives you a lot of flexibility. In the end, if your user’s profile changes based on what they have in their wallet, if the wallet is a standard EOA, like MetaMask, or a bound account, it changes little. The logic to handle it will be outside the wallet. Think of ERC6551 as a different way of generating wallets. What people put in the wallet, and how this content is managed is out of the scope of this EIP.
Thus said, if the profile starts with an NFT owned by the user, then, IMO, adopting ERC6551 is better than ask user to use their MetaMask

1 Like

Having an ERC721-style NFT with metadata that changes is possible, and depending on how you do it could use IPFS as the storage for the metadata file, but it could also be generated on-chain. All of that is out-of-scope for this specific EIP. If you want to start a separate thread on your questions about making that sort of “dynamic NFT” in general, I’d be glad to give a more thorough answer (I’ve helped develop several of those sort of assets).

1 Like

Thanks @MidnightLightning !
I just created a new thread.

I am using the reference implementation of ERC-6551 in the Cruna Protocol, but I also played with ERC-3652 and the second produces much smaller proxies.
The ERC6551AccountProxy generates a bytecode of 964 bytes, while the ERC3652Proxy generates a bytecode of only 322 bytes, saving a significant amount of money on expensive chains like Ethereum.

It would be great if the two projects could merge and I would like to know what @jay and @k06a think of it. Since I will most likely merge the two approaches to optimize Cruna’s code, I would be happy to help if you decide to merge.

@sullof I’m not sure that these numbers are correct, since proxies deployed by the ERC-6551 registry are only 141 bytes in length (45 bytes for the ERC-1167 proxy and 96 bytes for the static data appended to the bytecode). You can see an example of the deployed proxy bytecode here.

With that said, @k06a’s contribution to this proposal would be welcomed.

Uhm, the bytecode I get compiling reference/ERC6551AccountProxy.sol at main · erc6551/reference · GitHub is that long. I guess I am making some error. Thanks for pointing out to that. I will try to figure out what is wrong in my calculations.

@sullof ah I see! That contract is part of an example upgradable account implementation. In this case, ERC6551AccountProxy is a singleton - only one copy of the contract needs to be deployed. The proxies created by the registry will point to the ERC6551AccountProxy contract as the implementation address. This means that the cost for creating the ERC6551AccountProxy contract only needs to be paid once instead of every time an account is created via the registry.

In the case of ERC-3652, it seems like the 322 byte proxy is deployed each time an account is created. This means that ERC-6551 account proxies will cost ~1/2 as much gas to deploy.

1 Like