EIP-7779 Interoperable delegated accounts

No call decision · Account abstraction · EIP status Draft · created 2024-10-02

Defines a standard interface for looking up account information and storage bases and for cleaning up before leaving, so that an EOA delegated to a smart account via EIP-7702 can re-delegate without storage collisions when it moves wallets.

Why it is needed

Once EIP-7702 applies, an existing EOA (externally owned account) can delegate itself to a smart account implementation and use features such as batch execution and gas sponsorship. The problem is that the delegated account uses storage tied to its own address. If the user switches wallets and re-delegates to a different implementation, the storage used by the previous wallet stays in place, and if the new implementation uses the same slots, a collision occurs that can lead to unexpected behavior such as account lockup or security vulnerabilities. Until now an EOA worked with only a cryptographic key pair and moved freely between wallets, so moving wallets is already familiar to users. To preserve this freedom after delegation, a common convention is needed so that an account can check in a standard way which storage slots it has used and clean them up before moving.

What changes

Today

After EIP-7779

Who is affected

In depth

accountId(): query the account namespace

A view function that returns the account's namespace as a string. The namespace can include the account version, the account name, the wallet vendor name, and so on, and the original example is "v0.1.2.7702Account.WalletProjectA". The standard does not standardize the format of the namespace itself. As a usage example, it cites a wallet that shows a prompt such as "Move from Wallet A to Wallet B?" during redelegation and gets the Wallet A information from this function. The IInteroperableDelegatedAccount interface, which contains accountId() and accountStorageBases(), MUST be implemented to comply with the standard.

accountStorageBases(): query the storage base history

Returns the list of base storage slots the account has used. To reduce collisions, slot computation MUST use keccak256(), and the preimage of the hash (name, version, or a combination) is left to the account. The list is stored as a bytes32[] at the slot keccak(keccak('InteroperableDelegatedAccount.ERC.Storage')-1) & ~0xff. Because it is an append-only array, a newly delegated account must add its own base without overwriting existing values. The addition happens during account initialization and can be skipped if an identical entry already exists. An account that uses an external storage contract returns a value made by repeating the ERC number at the front and appending the storage contract address. If the address is 0xAAAA...AAAA, the value is 0x777977797779777977797779AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA. If a colliding base is found, the account can run additional on-chain or off-chain verification and then decide whether to delegate, and if the collision point holds a suspicious value aimed at the user (such as a shadow signer), the wallet can revert the delegation.

onRedelegation(): cleanup hook before redelegation

A function called before delegation is switched to a different implementation. It is contained in the IRedelegableDelegatedAccount interface, and implementing it is optional (MAY). A new wallet that is taking over an already delegated EOA can trigger the call, and the existing account runs wallet-specific preparation logic, such as returning the necessary storage to its pre-initialization state. It must verify through msg.sender that the caller is the legitimate owner, and an account that uses its own verification method can also handle this as a self-call. The standard does not enforce specific behavior and does not require storage to be cleared completely. Some accounts use many data types that are hard to empty, such as mappings, so the function is designed as a best-effort function that leaves the state as clean as possible for the new wallet to use.

Design rationale: verifying storage without relying on hash probability

Some argue that if each wallet uses a different base slot, the collision probability is low enough because of the nature of hashing, so no check is needed, but this standard takes the opposite position. Just as developers in the diamond pattern (ERC-2535) verify a facet's storage directly instead of relying only on hash probability, the standard holds that it is better to scan the account's storage before delegation, at least the region the new implementation will use. So accountStorageBases() is designed to return the full history of storage slots the account has used, not only the base of the current implementation, and this history serves as the material for the pre-delegation storage scan.

Security considerations

Calls to onRedelegation() must have a security mechanism that properly authenticates the owner. The function also must not reset replay protection (prevention of reuse) values, because that can create vulnerabilities such as signature replay attacks. After storage is cleared, the initialization function must be protected so that an arbitrary party cannot call it again. Also, because this standard is an ERC, enforcing the rules does not guarantee that real implementations follow them. Some accounts may pretend to support the standard, so it is recommended to verify that an implementation is a known safe one, and where needed the authenticity of the information can be checked off-chain.

Status: Draft, discussed on a call only

This proposal is in Draft status, so the interface and detailed rules may change. On the core developer side it was discussed in AMA #14 on 08.29, but no decision was made. Because it is an ERC, it is not included through a hard fork. It becomes established as wallets and account implementations adopt it.

Original EIP · ← EIPs