-
Notifications
You must be signed in to change notification settings - Fork 827
Add ERC: Minimal Avatar Smart Wallet (MASW) #1118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
41fac27
ERC: Minimal Avatar Smart Wallet (MASW)
msadjadi-ops 01fb4e3
Lint: restore eip filename, remove requires, fix dashes
msadjadi-ops aa05e66
Fix EIPW lint: filename, links, ASCII dashes
your 0a50a62
updated the file to fix the naming
your 10286e8
Rename eip-0.md → erc-0.md to satisfy eipw rule
your 11c990e
Update and rename erc-0.md to erc-7988.md
SamWilsn b41c154
Rename MASW.sol to MASW.sol
SamWilsn 24ef104
Fix asset directory naming and link reference
MostafaS 75baec4
Update ERC-7988 and MASW.sol implementation
MostafaS 1ff8a5d
Fix internal linking in ERC-7988
MostafaS ae1103b
Update ERCS/erc-7988.md
MostafaS 1c421b1
replaced the lowercase "may" and "must" mentions to uppercase.
MostafaS c57ada0
Apply suggestions from code review
SamWilsn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,166 @@ | ||
| --- | ||
| eip: 7988 | ||
| title: Minimal Avatar Smart Wallet (MASW) | ||
| description: A smart‑wallet interface for EIP‑7702 account‑code delegation. | ||
| author: 0xMostafas (@MostafaS) <[email protected]> | ||
| discussions-to: https://ethereum-magicians.org/t/erc-tbd-minimal-avatar-smart-wallet-masw-delegate-wallet-for-eip-7702/24761 | ||
| status: Draft | ||
| type: Standards Track | ||
| category: ERC | ||
| created: 2025-07-08 | ||
| requires: 7702 | ||
| --- | ||
|
|
||
| ## Abstract | ||
|
|
||
| Minimal Avatar Smart Wallet (MASW) is an immutable delegate‑wallet that any EOA can designate via [EIP‑7702](./eip-7702) (txType `0x04`). Once designated, the wallet's code remains active for every subsequent transaction until the owner sends a new `0x04` to clear or replace it. During each delegated call the EOA is the avatar and MASW's code executes as the delegate at the same address, enabling atomic batched calls ([EIP‑712](./eip-712) signed) and optional sponsor gas reimbursement in ETH or [ERC‑20](./eip-20). | ||
|
|
||
| The contract offers one primary function, `executeBatch`, plus two plug‑in hooks: a Policy Module for pre/post guards and a Recovery Module for alternate signature validation. Replay attacks are prevented by a global metaNonce, an expiry, and a chain‑bound `EIP‑712` domain separator. Standardising this seven‑parameter ABI removes wallet fragmentation while still allowing custom logic through modules. | ||
|
|
||
| ## Motivation | ||
|
|
||
| A single‑transaction code‑injection model (EIP‑7702) grants EOAs full implementation freedom, but unconstrained diversity would impose high coordination costs: | ||
|
|
||
| - **Interoperability** – Divergent ABIs and fee‑settlement conventions force dApps and relayers to maintain per‑wallet adapters, increasing integration complexity and failure modes. | ||
| - **Economic alignment** – Gas‑sponsorship relies on deterministic fee‑reimbursement paths; heterogeneity erodes relayer incentives and throttles sponsored‑transaction volume. | ||
| - **Tooling precision** – Indexers, debuggers, and static‑analysis frameworks achieve optimal decoding and gas estimation when targeting a single, fixed byte‑code and seven‑field call schema. | ||
| - **Extensibility focus** – Constraining variability to two module boundaries (Policy, Recovery) localizes complexity, allowing research and hardening efforts to concentrate on higher‑level security primitives rather than re‑engineering core wallet logic. | ||
|
|
||
| By standardising the immutable byte‑code, signature domain, and minimal ABI while exposing clearly defined extension hooks, MASW minimizes fragmentation and maximizes composability across the Ethereum tooling stack. | ||
|
|
||
| ## Specification | ||
|
|
||
| The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. | ||
|
|
||
| ### Overview of Delegation Flow | ||
|
|
||
| 1. **Deploy** `MASW` with constructor argument `_owner = EOA`. | ||
| 2. The owner sends an EIP‑7702 transaction (txType `0x04`) referencing the contract's byte‑code hash. | ||
| 3. After that transaction the EOA acts as the **avatar wallet** while the MASW logic executes as the **delegate wallet** at the _same_ address. | ||
|
|
||
| ### Public Interface | ||
|
|
||
| ```solidity | ||
| function executeBatch( | ||
| address[] calldata targets, | ||
| uint256[] calldata values, | ||
| bytes[] calldata calldatas, | ||
| address token, | ||
| uint256 fee, | ||
| uint256 expiry, | ||
| bytes calldata signature | ||
| ) external; | ||
|
|
||
| function setPolicyModule(address newModule) external; | ||
| function setRecoveryModule(address newModule) external; | ||
|
|
||
| event BatchExecuted(bytes32 indexed structHash); | ||
| event ModuleChanged(bytes32 indexed kind, address oldModule, address newModule); | ||
| ``` | ||
|
|
||
| ### Transaction Type Hash | ||
|
|
||
| ```solidity | ||
| bytes32 constant BATCH_TYPEHASH = keccak256( | ||
| "Batch(address[] targets,uint256[] values,bytes[] calldatas,address token,uint256 fee,uint256 exp,uint256 metaNonce)" | ||
| ); | ||
| ``` | ||
|
|
||
| ### Storage Layout | ||
|
|
||
| | Slot | Name | Type | Description | | ||
| | ---: | ---------------- | ------- | ---------------------------------------- | | ||
| | 0 | `metaNonce` | uint256 | Monotonically increasing meta‑nonce | | ||
| | 1 | `_entered` | uint256 | Re‑entrancy guard flag | | ||
| | 2 | `policyModule` | address | Optional `IPolicyModule` (zero = none) | | ||
| | 3 | `recoveryModule` | address | Optional `IRecoveryModule` (zero = none) | | ||
|
|
||
| `owner` and `DOMAIN_SEPARATOR` are `immutable` and occupy no storage slots. | ||
|
|
||
| ### Domain Separator Construction | ||
|
|
||
| ```solidity | ||
| DOMAIN_SEPARATOR = keccak256( | ||
| abi.encode( | ||
| keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), | ||
| keccak256("MASW"), | ||
| keccak256("1"), | ||
| block.chainid, // MUST be the live chain‑ID; using 0 is disallowed | ||
| _owner // keeps separator stable before & after delegation | ||
| ) | ||
| ); | ||
| ``` | ||
|
|
||
| ### Batch Execution (`executeBatch`) | ||
|
|
||
| | Stage | Behaviour | | ||
| | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| | **Validation** | ‑ `targets.length == values.length == calldatas.length > 0`<br>‑ `block.timestamp ≤ expiry`<br>‑ `metaNonce` matches then increments<br>‑ `EIP712` digest recovers `owner` **or** is approved by `recoveryModule` | | ||
| | **Policy pre‑hook** | If `policyModule != address(0)`, `preCheck` **MUST** return `true`; a revert or `false` vetoes the batch | | ||
| | **Calls** | For each index _i_: `targets[i].call{value:values[i]}(calldatas[i])`; revert on first failure | | ||
| | **Policy post‑hook** | Same semantics as pre‑hook | | ||
| | **Fee reimbursement** | If `fee > 0`: native transfer (`token == address(0)`) or `ERC20` `transfer` with OpenZeppelin‑style return‑value check <!-- TODO --> | | ||
| | **Emit** | `BatchExecuted(structHash)` | | ||
|
|
||
| #### Gas Sponsorship | ||
|
|
||
| The relayer and owner agree off‑chain on `(token, fee)` prior to submission. | ||
| Because the fee is part of the signed batch, a relayer cannot unilaterally raise it. | ||
| If a rival relayer broadcasts the same signed batch first, they earn the fee and the original relayer's transaction reverts—aligning incentives naturally. | ||
| Relayers **MUST** confirm the avatar's balance up‑front; insufficient funds render the transaction invalid in the mem‑pool. | ||
|
|
||
| ### Modules | ||
|
|
||
| #### Policy Module | ||
|
|
||
| ```solidity | ||
| interface IPolicyModule { | ||
| function preCheck (address sender, bytes calldata rawData, uint256 value) external view returns (bool); | ||
| function postCheck(address sender, bytes calldata rawData, uint256 value) external view returns (bool); | ||
| } | ||
| ``` | ||
|
|
||
| - A module **MAY** veto by reverting _or_ by returning `false`. | ||
| - The `value` parameter represents the total ETH sent with the transaction (`msg.value`), allowing the policy module to validate this against the batch requirements contained in `rawData`. | ||
| - Aggregator designs are encouraged: forward to child policies and stop on first failure (revert or return `false`). | ||
|
|
||
| #### Recovery Module | ||
|
|
||
| ```solidity | ||
| interface IRecoveryModule { | ||
| function isValidSignature(bytes32 hash, bytes calldata sig) external view returns (bytes4); | ||
| } | ||
| ``` | ||
|
|
||
| Must return `0x1626ba7e`. | ||
MostafaS marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ### Nonce‑Race Consideration | ||
|
|
||
| A single global `metaNonce` is used. Two relayers submitting the same nonce concurrently results in one success and one revert. The `expiry` field (wallets typically set ≤ 30 s) makes such races low‑impact, but UIs should surface the failure. | ||
|
|
||
| ## Rationale | ||
|
|
||
| - **Immutable logic** minimizes upgrade risk; a new version requires an explicit 7702 `0x04` call. | ||
| - A **two‑module** boundary captures common customizations without growing byte‑code. | ||
| - No hard `maxTargets`; advanced users can bundle many calls, while conservative users install a size‑capping Policy module. | ||
| - Domain separator binds the real `chainId` to mitigate cross‑chain replays. | ||
|
|
||
| ## Reference Implementation | ||
|
|
||
| Reference implementation can be found here [`MASW.sol`](../assets/eip-7988/MASW.sol). | ||
|
|
||
| ## Security Considerations | ||
|
|
||
| | Threat | Mitigation | | ||
| | ---------------------------- | ------------------------------------------------------------------------------------------------- | | ||
| | Same‑chain replay | Global `metaNonce` | | ||
| | Cross‑chain replay | Chain‑bound domain separator | | ||
| | Fee grief / over‑charge | Fee is part of signed data; front‑running risk sits with relayer | | ||
| | Batch gas grief | Optional Policy can reject oversized batches | | ||
| | `ERC20` non‑standard returns | OpenZeppelin `SafeERC20` transfer check | | ||
| | Re‑entrancy | `nonReentrant` guard; state mutated only before external calls (nonce++) and after (fee transfer) | | ||
| | Malicious Module | Core logic immutable; swapping modules needs an owner‑signed tx | | ||
|
|
||
| ## Copyright | ||
|
|
||
| Copyright and related rights waived via [CC0](../LICENSE.md). | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.