Title: ERC_6900__Modular_Smart_Contract_Accounts_and_Plug
Category: ERCs

Author: adamegyed
Date: 2023-04-19T14:16:07.860Z

A proposal for modular smart contract accounts and account plugins, which allow for composable logic. This proposal is compliant with ERC-4337, and builds on the existing work from ERC-2535 when defining interfaces for updating and querying modular function implementations.


Ethereum Improvement Proposals


ERC-6900: Modular Smart Contract Accounts
Interfaces for smart contract accounts and modules, optionally supporting upgradability and introspection





https://github.com/ethereum/EIPs/pull/6900
Please note, this standard is still in the draft state, and will likely be significantly amended with the input of the community.
A software standard is only as useful as its ability to coordinate the development of different parties. We hope this standard can coordinate plugin developers with account developers to unlock the full potential of account abstraction.

Author: fmc
Date: 2023-04-19T15:00:24.268Z

Thanks @adamegyed for initiating this EIP!
Having a standard for AA-enabled wallets is crucial, and it is impossible without having standard for the wallets themselves.
I’ve been working on the architecture of what you call Validation Plugins for the last several days.
I have a couple of questions:

When the function validateUserOp is called on an MSCA by the EntryPoint, it MUST find the user operation validator defined for the selector in userOp.callData



Where is it expected for MSCA to look for the operation validator address? do you suggest having some ‘selector=>validator’ registry?


If we’re using userOp.callData, to look for the validator address, we won’t be able to forward signature validation flow for the EIP1271 flow, as isValidSignature only operates with hash and signature.


What do you think of adding the validator address to the signature?
let signatureWithValidatorAddress = ethers.utils.defaultAbiCoder.encode(
        ["bytes", "address"], 
        [signature, validatorAddress]
      );

and then decode it at validateUserOp or _validateSignature.
Like this:
(bytes memory moduleSignature, address authorizationModule) = abi
            .decode(userOp.signature, (bytes, address));

        if (address(modules[authorizationModule]) != address(0)) {
            return
                IModule(authorizationModule).validateSignature(
                    userOp,
                    userOpHash,
                    moduleSignature
                );
        } else {
            revert WrongAuthorizationModule(authorizationModule);
        }

it requires no registry as it expects a standard validation method interface.
In the case above it is
validateSignature(UserOperation, bytes32, bytes);

and it is compatible with isValidSignature like
function isValidSignature(
        bytes32 _dataHash,
        bytes memory _signature
    ) public view override returns (bytes4) {
        (bytes memory moduleSignature, address authorizationModule) = abi
            .decode(_signature, (bytes, address));
        if (address(modules[authorizationModule]) != address(0)) {
            return
                ISignatureValidator(authorizationModule).isValidSignature(
                    _dataHash,
                    moduleSignature
                );
        } else {
            revert WrongAuthorizationModule(authorizationModule);
        }
    }

That will allow MSCAs to verify signed messages for dapps with validation plugins.
This example above is for passing the signature validation flow to the plug-in (module) , however it is possible to validate not only the signature but the whole userOp (for the recurring payments and similar use cases) in the similar way

Author: jamesmccomish
Date: 2023-04-19T16:53:03.403Z

Is the idea that all non updatePlugins or validateUserOp functions are handled in a fallback?
Or would it be useful to have an execute function which handles routing to the appropriate plugin? Since we build on Safe there is already a fallback handler which could be edited, but it may be an unnecessary call.

Author: trinity-0111
Date: 2023-04-19T17:24:41.614Z

@fmc thanks for the feedback.


Where is it expected for MSCA to look for the operation validator address? do you suggest having some ‘selector=>validator’ registry?


MSCA must be able to find all functions (validation, execution, hooks) and their relationships in some form. For any incoming execution request, MSCA will need to look up the associated functions. Depends on the account implementation, a registry can be useful. That’s what I did in the sample implementation (coming soon).


If we’re using userOp.callData, to look for the validator address, we won’t be able to forward signature validation flow for the EIP1271 flow, as isValidSignature only operates with hash and signature.


Are you treating validateUserOp as an execution function and forwarding data that way? It is doable, but can be very messy.
For validateUserOp, the recommendation is that the account implements it directly, can calls to validation plugins to do the actual validation. Accounts passes  userOp, userOpHash, and userOpValidator Plugin implementations can safely expect them as params.
Whenever you implement userOpValidator, make sure either implement an runtimeValidator that handles calls not directly from EntryPoint or disable that call path altogether.

Author: trinity-0111
Date: 2023-04-19T17:31:01.980Z

Thanks @jamesmccomish for the feedback!

Is the idea that all non updatePlugins or validateUserOp functions are handled in a fallback?

updatePlugins can certainly be handled in a fallback.
As for validateUserOp, as mentioned in the above comment, it is recommended to be handled on the account directly, though it is doable in the fallback (can be very messy).

Or would it be useful to have an execute function which handles routing to the appropriate plugin? Since we build on Safe there is already a fallback handler which could be edited, but it may be an unnecessary call.

The routing to appropriate plugins is expected to happen in the account level before the 4 steps starts.
What you may need to think with build on Safe are:

How can you add global hooks for execution functions?
How can you add custom validation and hooks for execution functions?

Author: fmc
Date: 2023-04-19T17:39:22.422Z

Are you treating validateUserOp as an execution function and forwarding data that way? It is doable, but can be very messy.

Nope, validateUserOp is not an execution function.
What I’m trying to say, is that if we have a module that validates signatures via some alternative algorithm (say passkey module), we may want to sign messages to verify them via isValidSignature via same alternative algorithm.
In this case, we will need isValidSignature on the MSCA to understand where to forward this signature validation flow to, and it only can take it from signature.
I realize, that we can have different flows, like

For the AA flow we get the operation validator plug-in address from the callData
For the EIP-1271 flow we take get the operation validator plug-in address from the signature

but it can be confusing.

Author: rmeissner
Date: 2023-04-19T18:22:49.738Z

Happy to see that discussions around this topic start. In my opinion it would have been nicer if the ERC would not enforce a specific proxy (e.g. currently the Diamond Proxy). The choice of a proxy depends on your use case and therefore a standard a standard that is agnostic to the proxy would be more widely applicable.
I would be interested in more details how hooks are designed. For example the Safe currently has a similar concept called Guards and it would be interesting to see the differences.

What do you think of adding the validator address to the signature?

Imo it should not be up to the signer, but the account what validator should be used. But this depends on the setup of the account (i.e. is there 1 address that fully controls the account, than there is no issue with this approach).
There is been some work around this also in conjunction with the Safe and the approach there was to enforce EIP-712 and then route the signing validation based on the domain separator. This allows high security guarantees. An WIP example can be seen here: safe-contracts/contracts/handler/extensible/SignatureVerifierMuxer.sol at merged-efh-sigmuxer · rndlabs/safe-contracts · GitHub

Author: fmc
Date: 2023-04-20T11:55:13.750Z

Totally agree with not enforcing a specific proxy pattern!

Imo it should not be up to the signer, but the account what validator should be used.

Yep, ideally it is the account that should decide, what validator to use.
However, if we consider, some SDK/dApp as a part of the account, it can decide what module address to append to the signature based on how this signature (or any other validation information) has been obtained from the signer.
It can be cheaper and easier than performing this search on-chain.

There is been some work around this also in conjunction with the Safe and the approach there was to enforce EIP-712 and then route the signing validation based on the domain separator.

Thanks for the link

Author: fmc
Date: 2023-04-20T12:45:27.174Z

When the function validateUserOp is called on an MSCA by the EntryPoint , it MUST find the user operation validator defined for the selector in userOp.callData

Also, does this mean that there is only one operation validator at a time defined for a given method of the Smart Account?
Let’s say, there is a method execute(address dest, uint256 value, bytes calldata func) that just calls an external smart contract on behalf of MSCA
Does it mean, that for all the calls with execute.selector in userOp.calldata there will be the same operation validator?

Author: jamesmccomish
Date: 2023-04-20T15:49:08.810Z

From how I see it, you could have different validators for the same executionSelector.
The ExecutionUpdate struct allows for an array of AssociatedFuntions, so based on some conditions in validateUserOp you could select which implAddress/implSelector you want.
I guess it depends on how the plugin information is stored… but like you asked above, I think I’ll go for some mappings in the MSCA. Could be worth standardising that storage into some IPluginManager interface so dapps could easily check if particular plugins are enabled

Author: kopykat
Date: 2023-04-20T16:29:28.917Z

While the rhinestone team has been initiating open discourse about modularising ERC-4337 with multiple teams working in the space and we have been looking to push the space forward in a positive sum way, we feel that this EIP does not aim to start a conversation about this topic but is rather a way for the authors to further their own reputations.
The main reason we have for thinking this is that the EIP is clearly very heavily inspired by the work we did during the ETHDenver hackathon, yet fails to attribute or even mention our project (GitHub - kopy-kat/ethdenver-aa: Account Abstraction Project for ETHDenver). During the hackathon we built the (to our knowledge) first implementation of ERC-4337 using the Diamond Proxy Standard.
It is obvious that this EIP takes inspiration from our code for several reasons, most notably because it exactly follows our peculiar naming convention exactly and repeats many of the errors and security vulnerabilities present in our proof of concept.
Unlike Safe, who call their extensions modules, or ERC-2535, who call them facets, this EIP has chosen to call them plugins, naming that we hadn’t seen in solidity before we chose it. Further, while the extensions are called plugins, the functions that call them are called hooks, a name inspired by frontend libraries and that is completely absent in other solidity contracts today. Further, our names for the specific hooks, such as preExecution and postExecution have equally been retained exactly by the authors.
Unlike ERC-2535, which stores function selectors in a mapping, this EIP has chosen to use arrays, something that we had done during the hackathon but is, on second thought, a very suboptimal implementation. Perhaps most interestingly, during the hackathon we were unable to finish testing our implementations of the hooks, so they are left blank on the public GitHub repo. It seems that rather than trying to figure out our intentions and planned implementation of these hooks, the authors just left them out of the EIP, perhaps hoping for others to tell them how to implement these.
Since we have been working on improving the code we had written during ETHDenver over the last month, there are many design choices that we have now revisited in order to make a modular implementation of ERC-4337 using diamond Proxies viable to be used in production. As stated above, we are happy to share our progress in public in order to propel the space forward and have already done so with select teams. However, because we are not yet confident in the security guarantees of our code, we have for now kept most of it internal and are planning on releasing it, together with detailed explanations of our design choices and extensive testing, at a later date.
Due to the numerous “coincidental” similarities to our code, examples of which were given above, but the clear lack of attribution or even mention of the rhinestone project, we feel like this EIP does not aim to create and improve public discourse around modular ERC-4337 in a positive-sun way and hence we will abstain from commenting and pointing out the numerous flaws that we have since discovered in our original code, most of which have been repeated in this EIP.

Author: noam
Date: 2023-04-24T01:06:04.639Z

We want to quickly address the above comment, which we’ve attempted to mediate directly, in a public comment and then move towards focusing discussions on technical criteria and improvements.

Our motivation for creating this EIP is not to “further our reputations” and indeed an EIP is not a vehicle to do that. We see contract accounts as an absolute requirement to get 1B people on crypto rails, and to that extent we’re pushing things forward where we can, including standards, open source software, and developer products. Standards specifically streamline the ecosystem, maximize developer leverage, minimize fragmentation and developer lock-in, and allow more cohesion in moving the space forward.
We were actually entirely unaware of Rhinestone when publishing. We reached out to engage the Rhinestone team as soon as a community member flagged the similarities. It’s actually cool to see convergence in design here. That generally means as a collective group we’re trending in the right direction. That said, similarities are a large step from copies - we demonstrated live to the Rhinestone team the history of how we landed at this conclusion after months of iteration. Nomenclature like “plugins” and “preExecution” are not unique to either this standard or Rhinestone’s implementation and are canonical and intuitive terms to capture the intended meaning.
The reason we moved our internal discussions into a public forum is to engage in the discourse and discussions, such as the ones from @rmeissner, @fmc, and @jamesmccomish, that forces us to either justify our design decisions or incorporate feedback and adapt the design. To that point, all decisions in the standard now have been prototyped and battle tested internally, and we have not seen counter arguments to standard specifications - including when we presented these “error laden” decisions to the Rhinestone team.
We’d been hoping to reach a resolution directly with the Rhinestone team, but unfortunately the asks they were making were not appropriate for this EIP or any other. Specifically, the two asks are:

a) Calling out Rhinestone in the EIP abstract. EIPs are not a vehicle for products or teams to gain relative standing or distribution. This is a conscious decision to maintain neutrality in standards for an open decentralized platform, and to that extent Alchemy is not mentioned a single time in the EIP, and we reference examples we researched in design where appropriate in the context of the specification.
b) Adding a Rhinestone author to the EIP authorset. We plan on collaboratively defining a set of criteria for expanding the author set that we can apply equally to everyone. We’ll share those in the TG facilitating these discussions next week, but can’t create special cases. That’s not to say Rhinestone doesn’t or won’t eventually qualify based on these criteria but we need a system that is uniform and applies equally to everyone in the community based on contributions and body of work. Working on parallel implementations that result in similarities to the standard likely don’t suffice and aren’t sufficient constraints on growing the authorset responsibly.
Taking a step back here: the goal is to provide an open standard that streamlines development and maximizes outcomes for the ecosystem. The goal here is not to assume individual ownership of this standard, use it to promote teams or products, or otherwise bias it towards a particular entity. To make sure that we’re able to do this, we need to enforce an equal process for each stakeholder and a conscious mitigation of any brand and product associations.The TL;DR of this situation:

We unequivocally did not copy work, and demonstrated our methodology to the Rhinestone team.
To that point this work is now in the public domain and should be owned by the ecosystem.
This is not a zero sum game, and the most outsized outcome will be a result of everyone working together, agreeing on scope, charter, and process, and pushing this forward in a timely manner.
This is not a vehicle for products or teams to gain standing or distribution.

Moving forward, let’s keep the discussions on Eth Magicians and similar public forums focused on technical discussions. There’s an open Telegram group as well to facilitate higher bandwidth conversation, per suggestion from the Rhinestone team. We’re always happy and available to chat with any teams directly on other matters.

Author: kopykat
Date: 2023-04-24T11:20:10.769Z

I fully agree with your point that this discussion should be reserved for comments on this EIP, so I think it makes sense to leave it at this. However, we also don’t want any misrepresentations to hang around so I just wanted to briefly clarify a few points:
Primarily, the asks that you mention were in fact not our asks, but your suggestion on the call with myself, you (Noam), Adam and Fangting (all from the Alchemy team) on Friday April 21. After discussing your proposal to add me as co-author and referencing our code from ETHDenver in exchange for removing my comment above, my team agreed to this and informed you over the weekend. After this you changed your mind, said this was no longer an option and followed up with this comment.
My understanding was that our discussions were centred around collaboration and clearing the air between us, not promoting rhinestone, so this comment is disappointing.
We are committed to building modular AA in the open and ensuring it is valuable to all in the ecosystem (as seen in the telegram conversations Telegram: Join Group Chat). However, as I pointed out on the call, we do not think that it is beneficial for the ecosystem to try to enshrine a specific implementation of modular AA as a standard, but we should rather aim to standardise interfaces for modules and how they interact with smart accounts, allowing for a diverse set of implementations to exist. We have initiated discussion about this in the group chat and will move some of the arguments into this forum should that be useful in the future.

Author: mudgen
Date: 2023-04-26T13:35:13.967Z

I looked at the standard and in general I like it and I think it is a great idea. In order to leverage the EIP-2535 Diamonds community and its tooling, interoperability and documentation I think it would be great, if possible, to make the smart contract accounts compliant with EIP-2535 Diamonds.
Making it compliant with EIP-2535 Diamonds may be easier than first considered.
The upgrade function diamondCut specified in EIP-2535 Diamonds is optional so smart contract accounts would not have to implement that and the standard allows other custom or standardized upgrade functions to be used like updatePlugins.
The EIP-2535 Diamonds standard only requires that the DiamondCut event be emitted and the four IDiamondLoupe read-only functions be implemented. The standard requires these for interoperability and interfacing with tooling and other software, for example louper.dev. A great article on EIP-2535 Diamond compliance is here: Compliance with EIP-2535 Diamonds Standard
It seems possible to me that a smart contract account could implement the IPluginLoupe interface and IPluginUpdate interface other interfaces defined by the smart contract account EIP as well as implement the IDiamondLoupe interface and emit the DiamondCut event when adding/replacing/removing functions.
I would love for EIP-2535 Diamonds and this smart contract accounts EIP to work together.

Author: cejay
Date: 2023-04-30T14:08:10.707Z

“EIP: Modular Smart Contract Accounts and Plugins” is generally feasible!
But I think there are several points that may need attention:


add/remove Hook plugins may need to be divided into two parts:
For example: 1. add plugin → 2. wait for a security time delay (e.g. 48 hours) → 3. user confirms that the plugin has been added.At least the implementation in soulwallet is like this, this is mainly for security reasons, for full context see the previous discussion with the author of ‘EIP-2535 Diamonds’ mudgen at here . So for us only PluginAdd function is not enough, at least a PluginPreAdd similar process is needed, and this process for monitoring purposes, will also emit event


validateUserOp because it will be called at high frequency, so we need to consider the ‘gas efficiency’, in our consideration for the time being will be validateUserOp logic fixed in the contract (rather than through the plugin way to achieve), later if user need to upgrade the logic of validateUserOp, the user can update the ‘proxy contract’->‘logic contract’ address pointer(lower gas).


I think MSCA implicitly aims to create a set of standards that any standard-compliant SCA can use any standard-compatible frontEnd (users don’t need to rely on ‘ONE’ frontEnd only), while the signature assembly and verification process often differs from one SCA to another (gas efficiency first｜stability first｜code readability first… etc.), I’m thinking that this area could be a major challenge for MSCA implementation.

Author: fmc
Date: 2023-05-08T12:20:08.023Z

cejay:

validateUserOp because it will be called at high frequency, so we need to consider the ‘gas efficiency’, in our consideration for the time being will be validateUserOp logic fixed in the contract (rather than through the plugin way to achieve), later if user need to upgrade the logic of validateUserOp, the user can update the ‘proxy contract’->‘logic contract’ address pointer(lower gas).


Can you elaborate on this topic please, and/or drop code examples from Soul Wallet repo?

Author: adamegyed
Date: 2023-05-10T21:28:15.064Z

Hi everyone, I’m happy to check in on this thread and provide an update on this proposal’s progress. There is now an updated and merged draft of the proposal on the main EIP site:
https://eips.ethereum.org/EIPS/eip-6900
The current draft is a response to the helpful and engaged dialogue happening here and on the related telegram channel. The feedback so far has pointed to some concerns about the the multi-facet proxy approach and interfaces, particularly in the context of where storage can feasibly reside. We’ve revised the EIP to be more explicitly agnostic in its approach to proxies and interfaces — this version allows for (but no longer requires) ERC-2535 style ‘delegatecall’ operations as the basis for execution.
This change has had a number of implications for security, interoperability, plugin management, and other issues that we’re currently exploring through research and development. In particular, we’re currently working on a library to address some of the storage limitations for plugins when using call. (In more detail, this library would aid in storing bulk data within an account’s associated storage, allowing for “nested” mappings and dynamic arrays).  We are also working on benchmarking some of the implementation options allowed by the new spec, and on developing a reference implementation. We look forward to sharing the results of that work in the coming weeks.
In the meantime, we’d be very grateful for your perspectives on some key unanswered questions that we’re also working on. First, interfaces: the changes we’ve made to IPluginUpdate.sol and IPluginLoupe.sol have made them more flexible, but with some tradeoffs in complexity. Are there implications for this approach that we haven’t yet discussed? We would appreciate your feedback in either implementing an account or writing plugins for modular accounts.
Second, changing the interaction flow from accounts to plugins to use call instead of delegatecall requires both standardizing execution and explicitly allowing for multiple validator functions on the same execution function. These are additional changes to the spec that we’re considering, while also weighing the costs of added complexity. Thank you to @fmc for the suggestion on self-identifying signatures, that strategy (or a similar one in calldata) can provide the flexibility needed to adopt these changes. We would appreciate any additional insight into what baseline features execute function(s) should have.
Also, at this time I’d like to introduce @JasonW to the ERC-6900 author team. He will be helping with research and organization, making sure this standard is the best it can be and enabling community members to become contributors.

Author: fmc
Date: 2023-05-11T11:19:52.252Z

Thanks, Adam, glad to hear my suggestion has been helpful.
Here are the links to the wip implementation of this strategy: Smart Account and sample EOA Ownership Module following Associated Storage requirements.
Excited to hear about the library that addresses storage limitations for plugins when using call! Let me know if you need any assistance with r&d of it

Author: cejay
Date: 2023-05-15T14:17:36.211Z

fmc:

Can you elaborate on this topic please, and/or drop code examples from Soul Wallet repo?


must of discussion at the link above. In terms of adding and removing plugins, we think it’s slightly different. We think it’s more important for the user to have the wallet address ‘forever’, but plugins pose a security risk, for example, if a hacker steals your private key, can the hacker modify the storage by adding a plugin ( This would make the social recovery function unavailable), and we are trying to make the social recovery always available if the user is concerned

Author: adamegyed
Date: 2023-05-26T16:06:40.971Z

Hi everyone - thank you everyone who has continued to work with us on this proposal. We wanted to give an update to bring everyone up to speed on some of the design decisions we’re considering as a result of these conversations, including open areas we’re still working through.
We’re still in the process of formalizing the wording around this proposed change, and would like to get feedback and and bring the community into the decision process around these changes. The current draft is viewable here:



GitHub



Comparing comparison-base...spec-update-1 · alchemyplatform/EIPs
The Ethereum Improvement Proposal repository. Contribute to alchemyplatform/EIPs development by creating an account on GitHub.





We’re still actively seeking collaboration and co-authorship in order to make this as useful as possible for the ecosystem. Please reach out if you want to work together on this proposal!
Proposed updates to the spec
The biggest change we’ve been testing is the idea of updating all function invocations, including execution functions, validation functions, and hooks, to use call instead of delegatecall. This isolates storage and execution code to be per-plugin, preventing accidental or malicious storage overrides across plugins and fundamentally changes the plugin trust model.

To preserve the capabilities of modular accounts to make arbitrary external calls, we introduce two standardized execution functions to ERC-6900, grouped together in a new interface IStandardExecutor. The two functions are execute and executeBatch, taking the same names as the functions from ERC-4337’s SimpleAccount but with new parameters.
These functions become necessary to provide through the standard when moving from delegatecall to call, because otherwise every single new contract interaction target would need to be added as a new plugin.
The IPlugin interface (with the method pluginStorageRoot()) would be removed, due to the changes in storage management no longer necessitating this.

We propose consolidating global hooks and regular hooks into one concept called “hook groups”. Hook groups have each hook type as an optional field, and accounts must maintain an association between execution functions and which hook groups apply for the execution function. This addresses a comment from @dror on how postExecHooks can take in data parsed from a preExecHook to reduce the number of calldata copies.
We propose a new hook type, pre-validation hooks, that run before either a user op validator or a runtime validator. The intended use case for these hooks is permission checking and similar pre-transaction checks that are not related to signature validation itself.

With the new storage model, it is possible to limit the blast radius of unverified validation and execution plugins using pre-validation hooks. These hooks can scope which external contracts or what parameters a given function is using.
The code implementations and storage of these two contracts are independent, which allows for independent security assessments to be valid. E.g. a formally verified permissions plugin can limit an unverified validation plugin.

Plugin Loupe functions have been scoped down to reduce implementation overhead. We are actively looking for feedback in this area, as it affects the implementation process for off-chain entities.
Tradeoffs we’re considering
The current plugin update function (updatePlugins in IPluginUpdate) allows for “slim” updates that only specify the fields that actually need to be set. This is done by using only the minimum number of array fields needed to express the desired plugin configuration, specifying function types using the defined enums and omitting any function types that aren’t used.


This allows, for example, a function that only has a user op validator and an execution function to not need to specify anything for other fields, like hook groups or a runtime validator. This reduces calldata size when the other fields are not needed.


However, this requires all function references to be casted as bytes24. This is what Solidity internally uses as the ABI-encoded type, but requires casting back to a function (or unpacking into address + selector) to perform the call. It might be possible to change the interface to take in function types in the struct (i.e. declare a struct as follows), but doing so would require defining new structs for each function type:
struct UserOpValidatorUpdate {
    PluginAction action;
    function(UserOperation calldata, bytes32) external validatorFunction;
}



With hooks passing data from preExec to postExec, it now requires a pairwise association with across each, to be able to track which preExecHook returned data should be sent to which postExecHook. However, with the introduction of preValidate hooks, it is unclear exactly what the data flows should look like, if any.

The group association is still valuable with the new hook types, as some preValidation hooks will want to defer state updates until execution. For example, preValidate hook that enforces an ETH spend limit will not want to perform the state update for tallying up ETH used by a key during the validateUserOp step. That should be avoided because in the case that execution reverts, the validation step will not be reverted, resulting in the spend limit usage increasing without an actual spend. For this reason, a preHook that performs the state update should be used.

Specifying the intended validator in the calldata of the standard execute functions limits the custom validation routing to just those two functions. This reduces the account-internal storage requirements (how the mappings and/or arrays look) for ERC-6900 implementing accounts. As an alternative, @fmc has suggested specifying the validator in the signature. We’re leaning towards not taking this direction to limit the scope of required validator storage in the modular account, but if there’s interest and a compelling reason to move it into the signature field, we can make that change.
PluginStorageLib
We’re still hard at work on testing and securing a reference implementation. In the meantime, we’ve realized there are some common components that many plugin developers will need to create when developing using the new call model that they did not need to do under the previous delegatecall model, specifically around designing storage layouts that abide by the account-associated storage rules of ERC-4337.  To aid in developing these plugins, we’re sharing the first version of PluginStorageLib - a solidity library that presents account associated storage as one giant (bytes32 => bytes) mapping, allowing for both nested mappings and dynamic arrays in associated storage. This is intended for singleton plugin contracts to have storage per-account.


gist.github.com


https://gist.github.com/adam-alchemy/9a2cdde12ece0467c88fcb4e897d124b







This is an unaudited library intended only as a starting point for plugin development. DO NOT USE THIS IN A PRODUCTION ENVIRONMENT. It requires custom serialization and deserialization for data stored, and care when designing key derivation schemes (i.e. protecting from manufactured key collisions).
We hope this can help explain and potentially alleviate some of the tradeoffs of switching from delegatecall to call, and look forward to feedback on its implementation and usage.
Conclusion
These proposed changes to ERC-6900 need to involve community input. Please feel free to leave comments and let us know what you think!

Author: fmc
Date: 2023-05-26T17:30:33.377Z

Very happy to hear about the pivot from delegatecall to call.
Can’t wait get my hands dirty with the Plugin Storage lib!
Meanwhile, msged you on Telegram regarding contributing to the proposal.

Author: in2xx
Date: 2023-05-31T08:56:55.526Z

Thanks @adamegyed for worth proposition!
This standardization of modular architecture is a very important proposition, given the variety of wallet providers that will enter the wallet market in the future.
BACKGROUND
We’re building a borrow/lend application that requests to prohibit execution transfer() and related modules, setApprovalForAll(), approve() and burn() methods to borrower’s wallet.
Those prohibit logic was implement into Safe’s guard contract so that borrower doesn’t be required to deploy each app-specific contract wallets but they are only required to apply guard to their wallet.

At ETHGlobal Istanbul, Pinky team also built an application which implement prohibited logic into guard contract.
If the each application provide own “app-specific” wallet, Users has to deploy each wallet that they want to use.

Following above, I could say that, from application developer side of view, this topic is much important for usability and account based identity contexts.
I draw a shape of composability like below (from this tweet).
image1920×1345 112 KB
STATUS QUO
The safe’s guard can be bypassed by module logic and this issue was flagged couple years ago and Safe V2 will implement “global guard” (details are unknown).
https://github.com/safe-global/safe-contracts/issues/335



Safe Community Forum – 8 Feb 22




Safe Contract v2




Safe Development





Contracts




Abstract The current version of the Safe contracts (1.3.0) is stable, but is not following all standards. Therefore I would propose to start planing a new version of the Safe contract. The general goal is to make the contracts more explicit and...

Reading time: 3 mins 🕑
Likes: 31 ❤






IMO, the reason why they didn’t support it at V1 that the Safe designed their architecture for organizations who is motivated to manage treasury by Multi-Sig so module would be setup by member of HQ, not setup by outside application.
OPINION
So, standing to above problem, I think the global preHook also need to implement logic below:

With Opt-In, global preHook enables applications to prohibit transactions to wallet user.
User can’t disable global preHook without application permission.
*In case of our application, if borrower will return NFT from their wallet, wallet user could disable global preHook.

When updatePlugins is called with PluginAction.REPLACE or PluginAction.REMOVE, the calls MUST be validated by preHook and postHook.
   function updatePlugins(PluginUpdate[] memory pluginUpdates, address init, bytes calldata callData) external;
   function updateGlobalPlugins(GlobalPluginUpdate[] memory globalPluginUpdates, address init, bytes calldata callData)
       external;

Author: yoavw
Date: 2023-06-19T01:54:03.624Z

I’d like to discuss two issues and suggest a model that might solve them:
1. The use of DELEGATECALL.
While the latest version of the ERC doesn’t require DELEGATECALL, at least one operation still does:

The function updatePlugins takes in arrays of execution updates, hook updates, and hook group updates to perform. It also takes in an optional initialization function. The function MUST perform the update operation sequentially, then, if the address provided in init is not address(0) , MUST execute init with the calldata callData through a delegatecall .

Using DELEGATECALL for init is a risky default. We may need DELEGATECALL for some use cases but CALL should also be supported and used by default.  Therefore an ExecutionMode should be specified for init.
2. The need to enforce permissions in the pre-validation hooks, based on calldata alone.
It is hard to enforce permissions in pre-validation functions if the subsequent function runs in the context of the account itself.  Real enforcement must happen inline.  An alternative is to run the functions themselves outside the account context and provide callbacks that enforce permissions.
Permissions model
Plugins have unlimited power, especially if they use DELEGATECALL.  Permissions can’t really be enforced in pre-validation if the plugin operates in the context of the account itself.
This unstructured access This makes auditing and formal verification of plugin challenging.  This might limit the growth of the plugins ecosystem.
I’d like to propose a permissions system loosely modeled after Android’s:
The plugin declares its requested permissions, as well as the components it has:

Initialization function and how it should be called (CALL/DELEGATECALL)
Hooks it needs to use and how they should be called (CALL/DELEGATECALL)
Execution functions it adds to the account.

It may also declare custom permissions defined by the plugin itself, which other plugins may request in order to interact with this plugin in certain ways.
This definition is similar to AndroidManifest.xml which every Android app includes.  It should be returned by a pure function by the plugin, e.g. getManifest(), and the wallet would communicate it to the user before accepting it.  The account will also use it for setting things up when adding/updating the plugin.
Here’s what the permissions might look like:
The default
By default (if the plugin doesn’t ask for any permissions), no DELEGATECALL, no special permissions in any account callbacks, the plugin is not trusted by the account beyond just calling the requested functions (CALL only).  Some plugins may be able to work without any permissions.
For example, a spending-limit guard could be implemented using just a pre-execution hook which checks the current balance and returns it as a context, and a post-execution hook that reverts if the balance change is over the limit.  Users will be able to add it with little risk because it won’t ask for any scary permissions.
DELEGATECALL permission
Requiring ongoing DELEGATECALL is the riskiest permission and should be avoided unless there is no other way to achieve the same functionality.  This is equivalent to Android’s SYSTEM permission and would be used only by the most trusted plugins.
It might make sense for account devs to prevent users from adding such a plugin unless they explicitly whitelisted it, to prevent users from getting scammed.  This is how Android handles apps that require SYSTEM - they must be signed by the manufacturer or by Google.
DELEGATECALL permission only during init
DELEGATECALL only during init is a bit safer since it’s easier to verify what it does, so it is considered a separate permission. But should also be avoided when possible.  It might make sense to also treat it as a SYSTEM permission that requires whitelisting.
Specific storage permission
Specific-slot permissions can be given through setter/getter functions in the account, allowing plugins to access only certain slots.  Similarly, the account may provide permissioned setters for certain mappings, arrays and structs.  Specific-storage permission could allow a plugin to manage certain aspects of the account without giving up full control.
For example, a plugin that adds social recovery capability would need access to manage guardians and to set the account’s control key, but it doesn’t need access to manage modules or to transact on behalf of the account.  It would ask for a PERMISSION.SET_KEY permission that allows it to call a callback that sets the key, and that’s it.
Per-plugin storage
A per-plugin getter/setter can be used for the plugin storage itself, providing unrestricted access to a mapping indexed by msg.sender.  Each plugin has implicit permission to its own storage.
If the plugin is the only one that uses this mapping, it may be redundant since the plugin could use account-associated-storage in its own storage to store its data, but it will be useful for cross-plugin storage since it will be stored in the account itself and managed by the permissions system.  Plugin1 may maintain a mapping in the account and define a PERMISSION.Plugin1.storage_read.  Plugin2 will request that permission and be able to access that mapping as well.  It enables splitting plugins to multiple modules with different permissions.
In the social recovery plugin example, there may be a complex module that manages the guardians and offers permissioned read access to the guardians.  And then a very simple module that verifies guardian signatures according to the stored configuration, and sets the key.  The complex module has no permissions (but declares a custom permission), and the simple module requests two permissions: PERMISSION.SET_KEY and PERMISSION.Plugin1.storage_read.
Execution from plugin
PERMISSION.execute_from_plugin, enabling calls to an executeFromPlugin function, similar to Safe’s executeFromModule.  It allows the plugin to perform specific operations on behalf of the account without modifying its storage.  E.g. send eth or transfer an ERC20 token.
Inside the account’s executeFromPlugin it is possible to enforce granular permissions for each plugin, e.g. the plugin can only transact with a specified contract.  A plugin that create pre-execution hooks may declare custom permission such as PERMISSION.transact_with_contract_X and get called from executeFromPlugin before execution, where it’ll be able to check if the calling plugin has the permission to transact with contract X (or with all contracts).  And a plugin that adds an execution function would only need to ask for the permission to transact with the contract it needs.
For example, a game plugin might ask for permission to transact with the game contract (and no other permissions).  Users will be able to add it without worrying that it might steal assets.
Custom permissions
The account (or any of the plugins) may add custom permissions, similarly to how Android apps can create custom permissions.  These permissions are not a part of the ERC but they are defined in the context of the account, the wallet knows how to interpret them to the user, and plugins may request these permissions when available.
Popular custom permissions may be standardized later if they are used by multiple account implementations and plugins.
PluginManifest
The model above requires the plugin to expose a structure, similar to AndroidManifest.xml to define the plugin’s required permissions, hooks with their execution modes, execution functions, etc.  It should also have a protocol version to allow future upgrade, and a list of optional capabilities, e.g. declare that it only supports an account that implements a certain ERC beyond just 6900.
The manifest may also specify dependencies on other plugins.  E.g. if PluginA adds a new execution function and PluginB uses it, then PluginB’s manifest would require PluginA.  The wallet will detect this and install them together as needed (with the user’s approval).
The plugin’s getManifest() will return the manifest, RLP-encoded.
The update function could be replaced with an installation function that calls the plugin’s getManifest().  It’ll then install the plugin (unless it requires capabilities or a version that the account doesn’t have), grant the needed permissions, configure hooks, etc.
The wallet will first show the user the manifest and explain the meaning of the requested permissions.  The user will be asked to sign a installPlugin(pluginAddress,manifestHash) transaction.  The manifestHash is included to prevent the plugin from baiting and switching the requested permissions.
Example - social recovery plugin
Manager plugin doesn’t ask for permissions, only declares a permission and a few execution functions. All of them require the validation function ownerOnly which validates the owner’s key.
<manifest plugin="socialrecovery.manager" version:"1">
  <permission name="PERMISSION.socialrecovery.READ">
  <execution name="addGuardian(address)" validation="ownerOnly">
  <execution name="removeGuardian(address)" validation="ownerOnly">
  <execution name="setRecoveryPolicy(PolicyStruct)" validation="ownerOnly">
</manifest>

Recovery plugin asks for the SET_KEY permission.  It also adds a validation function that validates the guardian signatures (so it needs the socialrecovery.READ permission defined by the manager module).  And an execution function that sets the key, validated by the guardians validation function
<manifest plugin="socialrecovery.recovery" version:"1">
  <uses-permission name="PERMISSION.SET_KEY">
  <uses-permission name="PERMISSION.socialrecovery.READ">
  <validation name="validateGuardians">
  <execution name="setKeyByGuardians(bytes[])" validation="validateGuardians">
</manifest>

We’re not really going to use the XML format on-chain.  It’ll be a more efficient RLP representation of this information.  We should also optimize this model to better suit the ERC’s need.  In this post I deliberately followed the Android model to show how it could work, but we can deviate from it when it makes sense.
Misc
Aside from the permission model and installation method, I’d like to propose another minor change:
In Calls to execution functions, If no corresponding plugin is found, the MSCA MUST revert. makes it incompatible with Safe fallback modules.  Maybe we could consider supporting a “default plugin” with an associated validation function.  Fallback modules built for Safe could then be compatible with this ERC with little or no changes.

Author: cejay
Date: 2023-06-21T02:55:10.801Z

Yes, I completely agree with what Yoav said:


For modular wallets, plugin is not enough. This is the solution we implemented (a detailed document will be released by David in a few days):

Plugin is a collection of hooks.
Because the Pre-UserOpValidate hook, plugins can only access associated storage. To address the issue of dynamic storage for plugins, we introduced Plugin storage (plugins can read and write the wallet’s storage, but each plugin has its own separate storage).
2. There is a significant use case: 2FA. We introduced out of signature data in the Pre-UserOpValidate hook to address this. Even if the data are outside the signature, data security can still be ensured easily (【1】 【2】). To avoid conflicts when different plugins parse guardData, the wallet must parse ExtData and pass it to the respective plugin based on its needs.



Module is an external contract authorized to execute a specific list of functions.


As Yoav mentioned, a permissions model needs to be defined. In our implementation, we require the Permission list to be pure [module] pure [plugin]. However, this cannot solve the problem (as it is actually invoked using staticcall on chain) unless the contract source code is audited to ensure “pure” functions.


Yoav suggest can use sstore2, and Dror suggest can use Bundler.UserOpValidate.simulate. All reasonable. However , taking into account security and simplicity of implementation, I suggest using an Request -> Approval Model:


Plugins have a permission request manifest. When users add a plugin, they need to input the corresponding permission manifest code. This ensures that all necessary permissions are granted, considering that if only certain hooks are allowed, the plugin may not function properly.


Modules have a permission request manifest. When users add a module, they need to input a list of permissions (bytes4[]) that the user allows. This allows customization of permissions based on user requirements.






I suggest removing ExecutionMode.DELEGATECALL because it poses unresolved security risks that cannot be mitigated.

In our current implementation, only the upgradeTo(address) function ( in UpgradeManager.sol) can access delegatecall, but this function is accessible to any authorized module:

Users have the freedom to choose their wallet implementation (with a 48-hour waiting period).
In case of any big issues, we can add our new version of the implementation to the whitelist, allowing users manual to upgrade immediately.
[context: by default, users trust our whitelist, and only modules in the whitelist can be added immediately. Other modules need to wait for 48 hours to take effect.]







Looking forward to the author’s reply.

Author: adamegyed
Date: 2023-06-27T21:37:01.478Z

Hi @in2xx !
To answer your question directly, updatePlugins and other functions defined directly on the account (as opposed to defiined via plugin execution functions) still run hooks. It is possible to design, under the current architecture, a custom validation function and/or preExec hook to enforce that type of check. On a standards-compliant account, there should be no way to bypass hooks.

Author: adamegyed
Date: 2023-06-27T22:17:31.482Z

Hi @yoavw and @cejay,
Thank you for your detailed feedback. We think the plugin manifest - some form of a self-descriptive view function that describes what functions a plugin provides and how they can be added to the account - makes a lot of sense. For the execution-related permissions, we’re generally leaning towards removing mandatory delegatecall usage entirely from the specification, so accounts that implement such features do so at their own risk.
For the “exec from plugin” workflow, we hear you and realize that runtime validators are not a good enough solution on their own. So, we’ll take a look at how we can adapt the spec and the way it represents associations to support this use case. Hopefully, sharing the reference implementation we’ve been working on can provide clarity in this area too.
Let’s wait on the the account storage API for now, and see how plugins can rely on existing tools to perform cross-plugin reads and writes. These include functions defined in the account fallback, and on the upcoming “exec from plugin” function, which can allow specific plugins to view and edit the data of other plugins. If we see the direct shared storage become a critical feature in plugin development, we can choose to add it to this spec. In the meantime, because the account storage API is defined as an interface on the account, it can be represented as a “required capability” of a plugin that needs it in its plugin manifest.
Hi @in2xx,
To answer your question directly, updatePlugins and other functions defined directly on the account (as opposed to via execution functions) still run hooks. It is possible to design, under the current architecture, custom validation and execution functions that enforce that type of check. On a standards-compliant account, there should be no way to bypass hooks.
There’s also a small batch of ERC changes that we’re getting ready to merge soon. Here’s what’s included and what’s planned for future work:

ERC Changelog:

Removed the execution mode enum  from IStandardExecutor. Now, it is only expected to use call.

Non-standard native account functions that use other call modes, such as delegatecall, are not prohibited by the spec.


Changed plugin init operation to an array of call operations instead of a delegatecall.
All plugin function signatures have been collected in one spec section for clarity.
Pre user op validation functions now return a packed time bound, which is intersected with the validator-returned value.
Specified behavior for natively-defined functions on the account.
Specified operation order for updatePlugins (pending plugin function updates do not apply until after execution completes - i.e. after all postExec hooks return and the call frame is complete.
Updated rationale section, fixed a typo, and updated the two call paths diagram.


Areas we’re still working on

How to use a plugin manifest to replace the declaration of individual functions within updatePlugins.

This would also replace the current usage of hook groups.


How to expose and control the ordering of hooks by the user.
How to specify default fallback functions via the plugin update interface
How to provide msg.sender and value as a meta-context to execution functions.
Possible hook consolidation, especially as these type declarations are growing to be similar.

PR: Update ERC-6900: Spec update 2 by adam-alchemy · Pull Request #7247 · ethereum/EIPs · GitHub
Thank you all for your feedback!

Author: fmc
Date: 2023-07-03T08:49:01.221Z

yoavw:

A per-plugin getter/setter can be used for the plugin storage itself, providing unrestricted access to a mapping indexed by msg.sender. Each plugin has implicit permission to its own storage.


I agree, that this is the only way for plugins to access MSCA’s storage that makes sense.
The purpose of Smart Accounts is to “store” and manage assets.
Since SA is a wallet, all operations made by SA should be managing the assets, and for this, there’s no need of using the SA’s storage.
IMO, Modular Smart Account should only use its own storage to store information about modules. And do it in the safest way to avoid situations, when SA can be upgraded to use another logic that can work in an unexpected way with an existing storage.
Thus, if as Adam mentioned we see a strong need for a direct shared storage, mapping indexed by module address could be the most straightforward and long-term oriented way of implementing it.

Author: mattstam
Date: 2023-07-06T02:55:12.705Z

Overall looks excellent! This is very comprehensive, so much sothat it might be beneficial to consider a phased approach or provide more detailed guidance and support to facilitate its integration amongst the community.
Maybe this EIP should just focus on the hooks, then once those are finalized, more EIPs can build on top to eventually reach the current scope of this design. I think even just the hooks have a lot of nuance, and reminds me of iptables. Ensuring those are well thought through is critical.

Author: jayk
Date: 2023-08-23T19:19:36.682Z

Hi all, we’re excited to share some updates to the proposal taking into account feedback provided thus far.
Most notable are changes to the way plugins are configured on the account and the introduction of an interface for plugins. Updates related to the plugin manifest that enable plugin installation and removal are largely a product of suggestions and feedback by @yoavw, who will be joining as a coauthor to this proposal. As a result of these updates, the former IPluginUpdate interface and the updatePlugins function have been removed.
The full changelog, along with areas we’re still thinking through can be found below.
ERC Changelog:

Updated shared components diagram to more accurately depict modular functions and their relationship to plugins.
Added “native function” definition under Terms.
Added the IPlugin interface that describes the functions a plugin must implement.

Plugins must implement ERC-165 and support this interface.
Associated functions in the interface take a uint8 function identifier to support multiple implementations of the same type of associated function.


Removed the FunctionReference custom type, as function identifiers do not represent function selectors.
Removed the hook group concept and its associated types.
Replaced the IPluginUpdate interface with the IPluginManager interface.

The function updatePlugins has been replaced by the functions installPlugin and uninstallPlugin.


Updated the IPluginLoupe interface to reflect the above changes.
Added the IPluginExecutor interface, which introduces new functions executeFromPlugin and executeFromPluginExternal that allow plugins to interact with execution functions on the modular account as well as external contracts.
Standard execution functions and executeFromPluginExternal should not allow calls to contracts that implement the IPlugin interface to maintain the integrity of account and plugin configuration.
Added a section describing the plugin manifest, and the expected behavior for installPlugin and uninstallPlugin.
Added a section describing the expected behavior for calls made from plugins.
Style updates and typo fixes.

Areas we’re still working on

We’re actively working on introducing a permissions model similar to what @yoavw has described in an earlier comment. We hope to release an update on this in the next couple of weeks.
Using ERC-165 is one way to enforce data integrity on the account and associated data within plugins, but presents additional overhead during standard execution. We’re still exploring ways we can improve this flow.
The way dependencies are requested by the plugin manifest and provided during installation prevents having to make immutable dependency references in the manifest, and offers flexibility for the user in providing a plugin of their choice for the dependency. However, there is still some rigidity in that the expected class of plugins for a given dependency must all implement the same interface. We’re giving more thought to this area to see if there is room for improvement.

As stated, some details are still in flux and will be ironed out in subsequent updates. We’d love to continue to refine the proposal with input from the community. Let us know what you think!
PR: Update EIP-6900: Spec update 3 by jaypaik · Pull Request #7516 · ethereum/EIPs · GitHub

Author: conner
Date: 2023-08-28T20:23:23.152Z

Can someone help give me an example of a Validation Hook (either pre-UserOp or pre-Runtime)? I’m not immediately seeing the practical utility of this hook at validation time that differentiates from the same logic at pre-execution runtime?

Author: adamegyed
Date: 2023-09-05T16:00:03.551Z

Good question! There are a couple of design patterns that are a better fit as a pre-validation hook than a pre-exec hook.
Consider a “permission delegated” entity, like a session key or an authorized spender. These may be other contracts, or a specific EOA, that are able to interact through your account but only in a predefined way - such as only interacting with a specific contract, or only to send a specific amount of a given token.
When defining the rules and conditions under which this entity may interact through your account, it is preferable to perform the checks during validation instead of execution for the user operation case, because if the checks fail (like an attempt to interact with a different contract that what’s been assigned), then there is no valid user operation. If this check was instead performed at runtime, then the result of validateUserOp(...) would be a valid operation to perform that just reverts during execution. This would allow the entity to waste gas on behalf of the account.
There is less clear of a distinction with pre runtime validation hooks, but there still are some cases where this makes sense. Having these hooks allows you to define actions that should occur based only on the validator chosen, instead of for all uses of the execution method. This mostly matters for the standard execution functions execute and executeBatch, where more than one validator may be assigned and which one is used depends on how the function is called. For instance, you may want a hook to only run when a specific validator is used to invoke a call, either to enforce permission checks or to perform a related action like emitting a log.
Generally, we hope to be un-opinionated about what plugins should do - we want to support as much creativity and innovation in plugin design as possible. This can come with added workloads to the account though, so managing complexity is also a design goal we need to balance. If you have ideas for different account organization strategies, we’d love to hear it too!

Author: PixelCircuits
Date: 2023-09-28T16:42:06.733Z

Sorry if this doesn’t make sense, but why can’t validateUserOp just be a module/plugin? The module can then use a runtimeValidator which appropriately casts the data to (UserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds) and does whatever additional checks it needs to do like signature validation.

Author: adamegyed
Date: 2023-09-28T18:30:52.815Z

Good question This is something we chose to “bake in” to the standard to allow the account to be a single source of truth for the modular account config.
One of the core parts of the standard is allowing for multiple types of transaction validity to coexist at the same time in an account. To do this, the logic of validateUserOp switches and is routed differently based on what function is being called, as specified by the selector in the first 4 bytes of userOp.callData sent to validateUserOp. If the switching behavior of validateUserOp is implemented as a plugin, then the relationship between the function selector and which validation function to run would have to be implemented within the plugin, and the “modular account config” would end up being in 2 locations.
If we only wanted one logical component to run during validateUserOp, then it would simple to make a plugin that just defines the function with its single implementation. But, since we want to allow different types of user ops to be validated differently, and for this validation logic to live in separate contracts that can be added or removed, then the plugin defining validateUserOp must now manage the execution → validation function mapping and the overall state of installed / uninstalled plugins. The reason validateUserOp is given special treatment and required to be a native function for 6900 is to consolidate the installation management and modular features, so plugin developers don’t have to re-invent modularity.
Would love to hear more of your thoughts too – is this something you’re interested in for supporting other validity checking functions?

Author: PixelCircuits
Date: 2023-09-29T01:58:57.161Z

Yeah, I was trying to think how wallets could install a plugin to support ERC-7521, which requires a similar validateUserIntents function. I’m still confused why whatever you would have installed as a Pre User Operation Validation Hook couldn’t just be installed as a Pre Runtime Validation Hook for the function validateUserOp installed in some base 4337 plugin. This would allow you to stack any sort of additional rules that apply to specific user operations. It would be kind of ugly to add another exception to ERC-6900 for ERC-7521 user intent validity checks. It would also help reduce ERC-6900 complexity if there were no special exceptions at all.

Author: trinity-0111
Date: 2023-09-29T23:13:09.350Z

Hi all, we are excited to share the latest update to the ERC spec. This completes the update we began with the changes we posted on August 23, and focuses on expanding and refining the permissions system for plugins. This change also allowed us to streamline parts of the spec by removing the special-case validation for standard execute functions. Finally, we have updated visual and text portions of the spec to make it easier for builders and stakeholders to engage with.
As a next step, we’ll be releasing a Reference Implementation incorporating all of these changes in the next few weeks. Stay tuned!
The full changelog, along with areas we’re still thinking through, can be found below.
ERC Changelog

Expanded permission system

Plugins that define execution functions can optionally define sets of permission strings for each function to indicate their level of sensitivity. These can be discovered with view calls by wallets and displayed to users when other plugins request permission to use them.
Plugins that wish to perform calls through the account to external contracts can request specific permissions to do so. These requests can be scoped to specific address, or to selectors on those addresses, via executeFromPluginExternal. This was introduced in the previous update, and now the plugin manifest struct for permittedExternalCalls is updated to move the field for “any external contract allowed” up one level to the manifest itself.


Updates to standard executions

Introduce return data to standard execute functions.
Removed the “multiple validation functions” special casing for the standard execute functions. This was originally added specifically to provide expanded capabilities for plugins, but using validation functions to do so ended up being rather cumbersome. It also introduced an additional difficulty for wallets preparing calldata. Since plugins no longer execute through the standard execute functions, it is therefore unnecessary to support multiple validation functions for the standard execute functions.

This also removes the validator based hook assignment in the plugin manifest used to special-case standard execution hooks.
This also removes previous account storage fields.
Together, these changes greatly simplify the account storage requirements to be ERC-6900 compliant, without compromising on feature capabilities.




Updates to installPlugin and uninstallPlugin

The spec is now more open-ended in implementation choices for how accounts should maintain consistency of installations, but requires that consistency is enforced.

For instance, one wallet implementation may choose to put installation data into storage, while another may choose only calldata with hash checking.
A custom configuration field is added to uninstallPlugin to support implementation-specific data needs. This is expected to be handled via the wallet software itself.


Dependency injection in plugin installation is updated to also inject the function ids of the provided methods, as opposed to just the plugin address, to allow for more user flexibility in configuration.
Naming change: pluginExecutionHooks ⇒ permittedCallHooks
Additional permission-enforcing mechanisms can be done by hooks, with an added workflow for applying permittedCallHooks during install time to protect the account. These hooks may perform custom checks, like decoding calldata to interpret a parameter or checking storage for valid state.
Installation of protected function selectors is now prohibited by the spec. Protected selectors include:

Function selectors for calls the ERC-4337 EntryPoint contract makes to non-account contracts, such as paymasters and aggregators. Without blocking these, accounts installing unknown plugins with clashing selectors may exhibit unexpected behavior and pay gas for other entities.
Function selectors for methods natively defined on the account (non-plugin functions). To help accounts maintain data consistency, these should be avoided.




Miscellaneous changes

Updated drawings, Terms and refactored Interfaces sections to improve clarity and readability.
Simplified manifest structs for validators and hooks, due to  the same type of assignment.
There are more naming changes on concepts and variable. For example,

for clarity, replace usage of the phrasing “execution selector” with just “function selector”. “Execution function” remains as the name for fallback-handling functions on the account.
VALIDATION_ALWAYS_DENY ⇒ renamed to PRE_HOOK_ALWAYS_DENY to indicate valid assignment target.
VALIDATION_ALWAYS_ALLOW ⇒ renamed to RUNTIME_VALIDATION_ALWAYS_ALLOW to indicate valid assignment target.





Areas we’re still working on

Working on the final pieces of a reference implementation to be released in the coming weeks.
How the plugins should request access for and use native tokens.

PR: Pull Request #7796​

Author: z0r0z
Date: 2023-10-12T12:32:41.371Z

are there implementations of std execute that consider the toggle for delegatecall if value is set to max uint, or otherwise, examples of providing both call and delegatecall in an account that conforms to 6900 (whether in the combined function mentioned above or separately)?

Author: z0r0z
Date: 2023-10-13T17:12:11.484Z

related somewhat, I think ERC6900 might recommend, but not require, the convenience of executeBatch in order to simplify its adoption by accounts. Accounts currently can implement a multicall method from libraries like Solady and OZ to batch execute as well as other ops. There are also multisend batching contracts (though this would encourage delegatecall which might be noted). The goal I think should be to maintain simpliciity, like ERC4337 (which only requires that accounts implement one function, validateUserOp). I am a fan of the execute interface and see this an overdue standardization :~).

Author: adamegyed
Date: 2023-10-17T18:26:38.049Z

Hi all, we are excited to share the reference implementation of ERC-6900 with you. The reference implementation reflects the most recent revisions to the spec around the plugin manifest and permissions. To maximize alignment between the spec and this implementation, we’ve also made some smaller changes to the spec itself. We describe these in the changelog below.
Publishing these changes is an opportunity to build on the conversations we’ve already been having with the community. Please take a look at the repo and leave comments and PRs or leave comments here!
We’re looking forward to building together!
Ref Implementation
The reference implementation is a draft implementation of ERC-6900. The reference implementation is an example to show how to build an ERC-6900 compliant modular account. It is NOT a production-ready implementation.
The implementation includes an upgradable modular account with two plugins (SingleOwnerPlugin and TokenReceiverPlugin). It is compliant with ERC-6900 with the latest updates.
Important Callouts

Not audited and should NOT be used in production.
Not optimized in both deployments and execution. We’ve explicitly removed some optimizations for reader comprehension.

ERC Changelog
Most of the changes to the ERC are for clarify and consistency, including

More sections are added like expected behavior for different Executors.
Struct and interface updates to be more efficient and consistent with reference implementation.
Naming changes for clarity and consistency, notably:

permittedCallHooks ⇒ InjectedHooks in IPluginManager .
validator ⇒ validation / validationFunction in various places.
association ⇒ associated in various places.
functionSelector ⇒ selector in various places.
ManifestFunctionType ⇒ ManifestAssociationFunctionType in plugin manifest.

Author: adamegyed
Date: 2023-10-17T18:32:39.974Z

@PixelCircuits Implementing ERC-7521 via a plugin, and making use of these hooks, is a good way to go about it. The specific handling of validateUserOp is needed for transaction validity switching, and we are looking into either making this modular switching step either apply to other functions too, or de-enshrining it’s handling, to better support other transaction flows in the future. Will definitely take this into consideration for that design research as well.

Author: adamegyed
Date: 2023-10-17T18:49:00.940Z

@z0r0z Thanks for the suggestions! A while back we made the call to not require accounts to support arbitrary external delegatecall s in the spec, and leave that choice up to implementation, to support a higher maximum security bar for ERC-6900 compliant accounts. Packaging it within the existing execute function using the maximum amount for the value param would technically break this spec, but it seems like a reasonable way to save some gas and code overhead, so it should be fine as an implementation choice. Wallet software for those accounts would just need to be aware of that behavior and display the appropriate warnings and safeguards.
Re: multicall, also something worth considering. Right now there’s one ERC-6900 specific reason to support executeBatch, and that is to avoid duplicating work with validation functions and hooks. Since those outer functions happen before the inner native function, naively doing batch executions only with a multicall and the execute function would result in duplicate ownership / validity checks for each call. With executeBatch, these calls happen once. For hooks that want to do per-execution checks as well, they’re given as parameters all of the requested calls to perform.
There might be a way to write a more custom multicall that allows for aggregation of these checks, but it would take some extra design effort to make sure none of the security invariants are broken. Will explore this idea a bit. Previously we’ve also considered dropping support for the non-batch execute as a way to simplify things, but people generally like the calldata savings.

Author: PixelCircuits
Date: 2023-10-17T19:14:10.812Z

adamegyed:

transaction validity switching


What do you mean by “transaction validity switching”? Is this a security concern the ERC-7521 validation function would run into too?

Author: adamegyed
Date: 2023-10-17T19:28:28.984Z

I’m referring to what I tried to explain previously here: ERC-6900: Modular Smart Contract Accounts and Plugins - #33 by adamegyed
An ERC-6900 modular account maintains a mapping from function selector → plugin address for execution functions. For validation functions, it also maintains this mapping, even though they all go through the single external function validateUserOp. So, to “switch” which validation function to actually run during the call to validateUserOp, it decodes the function selector from the first 4 bytes of userOp.callData, then does a mapping lookup, then runs the validation function.
If validateUserOp were itself implemented as a plugin’s execution function, it could work. However, it would then run into a problem, having to pick 1 of these 2 options:

The plugin implements validateUserOp with a single “logical” validation function, like checking an owner’s ECDSA signature.

This limits the flexibility of modular accounts.


The plugin itself has to re-implement modularity and do the same type of switching as what the account currently does.

This makes it harder to do account management and to enforce consistency guarantees.



So, to avoid either of these options, we chose to make validateUserOp a native account function with switching. But like you said, this also limits flexibility for other validity functions. So, I’m currently trying to find a way to possibly genericize the switching process to be able to apply it to other functions as well.
Hope this can be helpful, and let me know if there’s something else I can clarify.

Author: PixelCircuits
Date: 2023-10-17T22:37:40.181Z

So it’s thought that wallets will want different validation logic based on the calldata of the userOp? I’m confused because the reference implementation for 4337 has an example wallet that always uses the same validation logic. I’m also confused because the first 4 bytes of a userOp will almost always be the same (again, in the 4337 reference implementation , userOps are almost always calling the wallets “execute” of “executeBatch” function).

Author: fmc
Date: 2023-10-18T08:27:42.585Z

the reference implementation for 4337 has an example wallet that always uses the same validation logic.

The example wallet from reference 4337 implementation is not meant to be Modular. It is just a SimpleAccount(.sol).

the first 4 bytes of a userOp will almost always be the same

Yes, it can be like this. However, in 6900 it is expected that there will be plenty of execution methods called on MSCA and then forwarded by the fallback function to the plug-in which contains the implementation of this method. So, the first 4 bytes of userOp.callData can be different from execute or executeBatch selector.

Author: PixelCircuits
Date: 2023-10-18T13:14:34.919Z

Has the team heard of a fancy use case where signature verification depends on the calldata?
Also, I can’t really see use cases for wanting to hit a function other than execute or executeBatch. Granted, those functions wouldn’t be able to do script like things that need memory, but that could be solved with executeDelegate and executeDelegateBatch calls. I would say that’s actually preferable to the alternative of requiring a user to install some script function to their wallet. The user can just elect to use scripts (via a delegate call) at sign time instead.

Author: fmc
Date: 2023-10-19T11:07:32.634Z

fancy use case where signature verification depends on the calldata

For example, if there is a method performRecurringPayment(...) in the plugin,
This method can be called by anyone and we do not even need MSCA’s owner sig to validate such an userOp.
So if the userOp.callData[0:4] is performRecurringPayment.selector we want not just some other signature verification algorithm, but no signature verification at all. In this case validation function will verify has the period passed or not and maybe something else.
btw, I’m not a part of the 6900 team. They could have better use case examples.

Author: doublespending
Date: 2023-11-07T10:03:40.016Z

I think IPluginManager.installPlugin and IPluginManager.uninstallPlugin are not enough for the ERC-6900 spec.
Consider this situation, if an account has only one plugin. At the same time, this plugin is responsible for selector IPluginManager.installPlugin.
If I want to switch to a new plugin. Maybe I should uninstall the old plugin (IPluginManager. uninstallPlugin) and install the new plugin (IPluginManager.installPlugin).
Uninstalling the old plugin is fine. However, I cannot install new plugin through IPluginManager.installPlugin  for there is no plugin responsible for the selector IPluginManager.installPlugin after uninstalling the old plugin.
I think maybe we can unify the interfaces like this.
function updatePlugin(
    UninstallPluginParams pluginToUninstall
    InstallPluginParams pluginToInstall,
)

If pluginToUninstall is empty, just install pluginToInstall.
If pluginToUninstall is not empty, uninstall pluginToUninstall first and then install pluginToInstall.

Author: Cshirley69
Date: 2023-11-13T21:46:18.272Z

[details=“Summary”]
This text will be hidden
[/details[quote=“cejay, post:15, topic:13885, full:true”]
“EIP: Modular Smart Contract Accounts and Plugins” is generally feasible!
But I think there are several points that may need attention:


add/remove Hook plugins may need to be divided into two parts:
For example: 1. add plugin → 2. wait for a security time delay (e.g. 48 hours) → 3. user confirms that the plugin has been added.At least the implementation in soulwallet is like this, this is mainly for security reasons, for full context see the previous discussion with the author of ‘EIP-2535 Diamonds’ mudgen at here . So for us only PluginAdd function is not enough, at least a PluginPreAdd similar process is needed, and this process for monitoring purposes, will also emit event


validateUserOp because it will be called at high frequency, so we need to consider the ‘gas efficiency’, in our consideration for the time being will be validateUserOp logic fixed in the contract (rather than through the plugin way to achieve), later if user need to upgrade the logic of validateUserOp, the user can update the ‘proxy contract’->‘logic contract’ address pointer(lower gas).


I think MSCA implicitly aims to create a set of standards that any standard-compliant SCA can use any standard-compatible frontEnd (users don’t need to rely on ‘ONE’ frontEnd only), while the signature assembly and verification process often differs from one SCA to another (gas efficiency first｜stability first｜code readability first… etc.), I’m thinking that this area could be a major challenge for MSCA implementation.
[/quote]


]

Author: adamegyed
Date: 2023-12-01T20:56:47.577Z

Hi all, we have some updates to share regarding ERC-6900. We have made some minor changes to the spec to improve clarity and consistency, and updated the reference implementation to be fully compliant with the ERC specification. We’ve also identified necessary areas to work on for stabilizing and simplifying the standard, and opened an agenda signup for the next community call.
Spec updates and fixes
We’ve addressed some small issues and added simplifications to the existing standard. This makes it easier to dive into the spec and the reference implementation and use modular account features. It is not comprehensive of all planned improvements discussed below.
Change log

Renamed Execution to Call.
Added the missing struct definition for ManifestExternalCallPermission.
Added a native token spending permission flag into the manifest.
Moved view-only fields in the manifest out into a separate pluginMetadata function, since they don’t affect the installation procedure but occupy space.
Renamed IPluginLoupe to IAccountLoupe for clarity on what is being inspected.
Added extra fields to the plugin installation events, to allow full reconstruction of account state by offchain entities.
Relaxed rules to allow overlapping hook definitions.
Other small cleanups and fixes pertaining to the EIP → ERC repo migration.

The standard and the reference implementation have been updated to reflect these changes. The reference implementation’s git tag has been advanced to v0.2.0.
Updated ERC PR: Update ERC-6900: spec update 6 by fangting-alchemy · Pull Request #116 · ethereum/ERCs · GitHub
Reference implementation: GitHub - erc6900/reference-implementation at v0.2.0
Ongoing work to stabilize and simplify the standard
For a standard to provide value, the upside of conforming to it must outweigh the added costs. We’ve received feedback that ERC-6900 shifts too much of the burden onto account implementers, which risks fragmentation of plugin development. We’ve put together a list of specific steps we can take to consolidate aspects of the standard. This can help reduce the overhead of implementing a 6900-compliant account and make it easier to reason about.

Multiple validation function support

As discussed in the previous community call, many workflows could be simplified if there was a way for multiple validation functions to be applied over the same execution function. Some proposals were discussed around adding extra data into userOp.signature, but this data is inaccessible during validation. If we can create a generalized calldata format to pass validation context, we can easily allow for multiple validation functions on the same selector.


Replace Plugin Operation

Uninstalling and reinstalling plugins through executeBatch relies on some fragile assumptions for how the account should behave. If we introduce a standardized replacePlugin method, these issues can be avoided. The current blocker has been the handling of dependencies, which add significant overhead to what the replace plugin operation needs to do to prevent inconsistent account state (described more below).


Consolidate user-supplied install config

Currently, ERC-6900 supports dependencies and injected hooks as ways to supplement the install config in a plugin manifest. These allow for wider cross-plugin compatibility and for stricter permissions, respectively. But, they come with large complexity costs on the implementation side, due to added tracking outside of the manifest. If we can simplify & consolidate these into a single user-supplied config, we can reduce complexity and make plugin replace operations feasible.


De-enshrine validateUserOp

The special status of validateUserOp in ERC-6900 is key to allowing custom account security settings and automations. However, with proposed changes to ERC-4337’s UserOperation struct and RIP-7560’s validateTransaction, we do not want to be stuck re-enshrining more special functions. We need to define a strategy for functions to have implementation-switching as needed to have modular validation functions.



We’ve opened these specific topics (and more!) as GitHub issues here, to track design progress and allow for easier discussion. Big thanks to community feedback from previous call, posts here, and others working on modular accounts for helping improve the standard and bring us closer to interoperability.
Community call agenda
We’ve also opened a public agenda for the community calls for anyone to sign up with a topic they’d like to discuss here. And, we’ve added some starting topics about the planned changes.
As always, we appreciate feedback in all forms. Feel free to leave comments here, open issues on GitHub, or bring up topics in the community calls.

Author: jayk
Date: 2023-12-04T20:46:56.611Z

Great point. There are a couple of solutions we are exploring that tackle this problem.
One is very similar to the solution you’ve described: [Improvement] Replace plugin operation · Issue #13 · erc6900/resources · GitHub
The other is a change that would allow more than one validation function to be installed for a given selector. This would, for example, allow one to install a new ownership plugin before uninstalling the old one: [Improvement] Multiple validation function support · Issue #4 · erc6900/resources · GitHub
Note that multiple validation function support would not help in a strict plugin upgrade scenario if the new plugin shares the same execution selectors as the older version. And as such, these solutions are not mutually exclusive.
If you have more ideas here, would love to hear your thoughts in the issues above!

Author: jayk
Date: 2024-01-25T01:05:33.410Z

Hi all, thank you to those who have been attending and contributing via our community calls and standard improvement issues! We have a new spec update that includes changes that came out of these discussions:

The removal of permitted call hooks and injected hooks during plugin installation
Disallowing use of hooks as dependencies

These changes will help to simplify the standard and account implementation, and improve security around state held in plugins. Please take a look at the issues linked above for more context and description of the changes.
Spec updates and fixes
Change log

IPluginManager

Removed structs InjectedHook, InjectedHooksInfo
Removed the injectedHooks parameter from event PluginInstalled
Removed the injectedHooks parameter from function installPlugin
Removed the hookUnapplyData parameter from function uninstallPlugin
Renamed the callbacksSucceeded param of the PluginUninstalled event to onUninstallSucceeded, as there are no longer multiple callbacks for uninstalls
Renamed pluginInitData param of installPlugin to pluginInstallData to be more consistent with the pluginUninstallData param of uninstallPlugin
FunctionReference type declaration has been moved from IAccountLoupe to IPluginManager, since the former is an optional interface
installPlugin’s comment for the dependencies param is now explicit about each FunctionReference being a validation function


IAccountLoupe

Removed function getPermittedCallHooks


IPlugin

Removed function onHookApply and onHookUnapply
Removed member permittedCallHooks from struct PluginManifest


PluginManifest

Added a note for canSpendNativeToken to clarify that the plugin can spend up to the amount that it sends to the account in the same call even if this value is false
Updated comment for dependencyInterfaceIds to no longer include hooks


Calls to installPlugin

Included instruction to store the plugin’s native token spending permission
Included instruction to store interface IDs for the manifest’s interfaceIds
Included instruction to also emit dependencies in the PluginInstall event


Calls to uninstallPlugin

Removed reference to hook dependencies
Included instruction to remove the plugin’s native token spending permission
Included instruction to remove interface IDs
Included note that if there is more than one instance of the same hook installed on a given function or if the same interface ID is added to the account by more than one plugin, they must persist until the last plugin that added them is uninstalled
Included instruction to emit the PluginUninstalled event


Calls to execution functions

Added clarity around how duplicate hooks (identical FunctionReferences that are applied to the same function) should be handled during execution
Added more clarity around how hooks should be executed


Simplified the plugin execution flow diagram and the description of the install and uninstall behaviors accordingly
Consistency improvements across comments
Fixed typos

The standard and the reference implementation have been updated to reflect these changes. The reference implementation’s git tag has been advanced to v0.7.0.
Updated ERC PR: Update ERC-6900: 0.7.0 by jaypaik · Pull Request #219 · ethereum/ERCs · GitHub
Reference implementation: GitHub - erc6900/reference-implementation at v0.7.0

Author: arr00
Date: 2025-01-29T19:54:45.790Z

ERC-6900 and ERC-7579 both specify an interface called IModule. To increase clarity and avoid file/interface confusion, can we prepend all interfaces defined in this ERC with ERC6900 (such as IERC6900Module? I will be posting the same to the ERC-7579 discussion.
ERC-6900 IModule
ERC-7579 IModule

Author: 0xrubes
Date: 2025-03-26T04:12:09.695Z

Great suggestion! This change has been merged and will be part of the v0.8.1 release of the reference implementation

Author: 0xrubes
Date: 2025-03-26T04:13:28.468Z

Announcing ERC-6900 v0.8
We’re excited to announce the release of ERC-6900 v0.8! While we want to keep breaking changes to the minimum, the learnings of the past 2 years around modular smart accounts have required us to revisit some aspects. Generally, the design improvements can be classified into three categories: overall design changes, maintenance, and utility. The most important ones we would like to touch on are in the following section:
Overall Design Changes
We significantly reworked the permissions framework and decided to shift away from a static module-defined manifest (link), where the module used to define how and with which permissions a module was to be installed on an account. We found that this artificially limits the flexibility of modules in how they could be configured in accounts. While this added flexibility also comes with the tradeoff of possible misconfiguration, we deemed that the benefits are ultimately far too great. Now, users supply a custom ValidationConfig for Validation Module installations and an ExecutionManifest for Execution Module installations. We also added support for multi-validation (link), breaking up the pre-existing 1:1 relationship between an execution function and corresponding validation function as specified by the module manifest. Now, an execution can possibly be unlocked by multiple validations, possibly from different Validation Modules. This again offers vastly more flexibility and also enables simpler social recovery features. You can now also install the same validation module multiple times (link) with different validation config data. Furthermore, we’re introducing the concept of global validation (link). Any execution function being installed can optionally be unlocked by this so-called global validation. Global validation functions are essentially a pool of installed validation functions that are specifically marked as global. Each validation function marked as global can then unlock all execution functions that have global validation enabled. By introducing a “default” validation function pool, we can reduce plugin installation costs, particularly for ownership plugins, by allowing execution functions to opt-in to a default validation pool.
We also embedded ERC-1271 into validation modules link, which allows for routing ERC-1271 checks in cases of multiple signature validation plugins. We also added the possibility for signature validation hooks.
Maintenance
A simple overall design change is the renaming of plugins to modules (link) for consistency around the modular smart account ecosystem. Furthermore, we updated the account interface to comply with the EntryPoint v0.7 interface (link). This version of the standard no longer works with EntryPoint v0.6.
Furthermore, some interfaces got merged. The IStandardExecutor and IModuleManagergot merged into a new IModularAccount interface. We also split out the slightly convoluted IModule into the different interfaces for each of the different module types, namely the IValidationModule for validations for an account, the IValidationHookModule for hooks for validations, the IExecutionModule for execution functions and their installations on the account and finally the IExecutionHookModule interface for pre & post-execution hooks for execution functions.
Utility
We decided to add one more hook type to the standard that module developers can define, so-called permission hooks. These are execution hooks that are associated with a validation function instead of being associated with a selector. So depending on by what validation function a certain execution is unlocked, the permission hooks might or might not run. This addition makes it easier for accounts to configure execution hooks across multiple session keys. It’s also more efficient, since only the relevant hooks for a session key are loaded per use.
ERC-6900 v0.7 already offered a vast set of different hook types that tended to overwhelm devs getting started with the standard. The reason for that is that we think explicit definition and distinctions between hook types ultimately enables true interoperability and flexibility for module developers. Permission hooks were an important missing piece in our hook definitions, especially with the shift away from the manifest.
This change is part of our general overall change to the way we enforce permissions, away from the manifest-defined definitions, towards a more customizable approach via ValidationConfig and optional permission hooks to further keep the permissions of a validation module in check. A possible use case is native token spend limit permissions for specific validations or a session key for a DEX that is dollar cost averaging into some token in regular intervals, where we could enforce additional slippage checks via execution hooks, therefore keeping the permissions of the semi-trusted session key-validation module in check with an additional hook.
We also acknowledge the need of developers to have a pre-configured, more rigid account, which we coined semi-modular accounts. Semi-modular accounts are accounts that may have some module features built-in directly to the account instead of via installed plugins, which reduces the need to install them during account creation, reducing account creation costs significantly. For example, take a look at our semi-modular account with 7702 support in the reference implementation repository. As part of this release, we explicitly support these kinds of accounts in the standard.
Reference Implementation
We’ve updated the reference implementation to the v0.8 version of the standard. The reference implementation is an example to show how to build an ERC-6900-compliant modular account. It is NOT a production-ready implementation.
The implementation includes an upgradable modular account a set of permissions-enforcing hook modules and a validation module, which are fully compliant with ERC6900 v0.8:
Permission-enforcing hook modules:


AllowlistModule: Enforces ERC-20 spend limits and address/selector allowlists.


NativeTokenLimitModule: Enforces native token spend limits.


ERC20TokenLimitModule: Enforces ERC-20 token spend limits.


Validation Module:

SingleSignerValidationModule: Enables validation for a single signer (EOA or contract).

Important Callouts
The reference implementation is not audited and should NOT be used in production. Furthermore, it is not optimized in both deployments and execution. We’ve explicitly removed some optimizations for reader comprehension.
More Links


For a full changelog, see the GitHub release.


Also, have a look at our revamped website over at erc6900.io!