More Info
Private Name Tags
ContractCreator
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Latest 14 internal transactions
Parent Transaction Hash | Block | From | To | |||
---|---|---|---|---|---|---|
37925917 | 9 days ago | 0.00032374 xDAI | ||||
37344930 | 43 days ago | 0.00032744 xDAI | ||||
37289785 | 47 days ago | 0.00031812 xDAI | ||||
37289778 | 47 days ago | 0.00031812 xDAI | ||||
37289770 | 47 days ago | 0.00031812 xDAI | ||||
37289751 | 47 days ago | 0.00031444 xDAI | ||||
37140855 | 56 days ago | 0.0002461 xDAI | ||||
37139916 | 56 days ago | 0.00035742 xDAI | ||||
37118268 | 57 days ago | 0.00025669 xDAI | ||||
37118261 | 57 days ago | 0.00014239 xDAI | ||||
36941006 | 68 days ago | 0.99988598 xDAI | ||||
36874108 | 72 days ago | 0.00252909 xDAI | ||||
36874108 | 72 days ago | Contract Creation | 0 xDAI | |||
36874102 | 72 days ago | 0.1 xDAI |
Loading...
Loading
Minimal Proxy Contract for 0x202a5598bdba2ce62bffa13ecccb04969719fad9
Contract Name:
ModularEtherspotWallet
Compiler Version
v0.8.23+commit.f704f362
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import "../erc7579-ref-impl/libs/ModeLib.sol"; import {ExecutionLib} from "../erc7579-ref-impl/libs/ExecutionLib.sol"; import {ExecutionHelper} from "../erc7579-ref-impl/core/ExecutionHelper.sol"; import {PackedUserOperation} from "../../../account-abstraction/contracts/interfaces/PackedUserOperation.sol"; import "../erc7579-ref-impl/interfaces/IERC7579Module.sol"; import {IModularEtherspotWallet} from "../interfaces/IModularEtherspotWallet.sol"; import {ModuleManager} from "../erc7579-ref-impl/core/ModuleManager.sol"; import {HookManager} from "../erc7579-ref-impl/core/HookManager.sol"; import {AccessController} from "../access/AccessController.sol"; contract ModularEtherspotWallet is AccessController, IModularEtherspotWallet, ExecutionHelper, ModuleManager, HookManager { using ExecutionLib for bytes; using ModeLib for ModeCode; address public immutable implementation = address(this); /** * @dev modifier to restrict access to calling on implementation */ modifier onlyProxy() { if (address(this) == implementation) revert OnlyProxy(); _; } /** * @dev see {IERC7579Account}. * @dev this function is only callable by the entry point or the account itself * @dev this function demonstrates how to implement * CallType SINGLE and BATCH and ExecType DEFAULT and TRY * @dev this function demonstrates how to implement hook support (modifier) */ function execute( ModeCode mode, bytes calldata executionCalldata ) external payable onlyEntryPointOrSelf withHook { (CallType callType, ExecType execType, , ) = mode.decode(); // check if calltype is batch or single if (callType == CALLTYPE_BATCH) { // destructure executionCallData according to batched exec Execution[] calldata executions = executionCalldata.decodeBatch(); // check if execType is revert or try if (execType == EXECTYPE_DEFAULT) _execute(executions); else if (execType == EXECTYPE_TRY) _tryExecute(executions); else revert UnsupportedExecType(execType); } else if (callType == CALLTYPE_SINGLE) { // destructure executionCallData according to single exec ( address target, uint256 value, bytes calldata callData ) = executionCalldata.decodeSingle(); // check if execType is revert or try if (execType == EXECTYPE_DEFAULT) _execute(target, value, callData); // TODO: implement event emission for tryExecute singleCall else if (execType == EXECTYPE_TRY) _tryExecute(target, value, callData); else revert UnsupportedExecType(execType); } else if (callType == CALLTYPE_DELEGATECALL) { // destructure executionCallData according to single exec address delegate = address( uint160(bytes20(executionCalldata[0:20])) ); bytes calldata callData = executionCalldata[20:]; // check if execType is revert or try if (execType == EXECTYPE_DEFAULT) _executeDelegatecall(delegate, callData); else if (execType == EXECTYPE_TRY) _tryExecuteDelegatecall(delegate, callData); else revert UnsupportedExecType(execType); } else { revert UnsupportedCallType(callType); } } /** * @dev see {IERC7579Account}. * @dev this function is only callable by an installed executor module * @dev this function demonstrates how to implement * CallType SINGLE and BATCH and ExecType DEFAULT and TRY * @dev this function demonstrates how to implement hook support (modifier) */ function executeFromExecutor( ModeCode mode, bytes calldata executionCalldata ) external payable onlyExecutorModule withHook returns ( bytes[] memory returnData // TODO returnData is not used ) { (CallType callType, ExecType execType, , ) = mode.decode(); // check if calltype is batch or single if (callType == CALLTYPE_BATCH) { // destructure executionCallData according to batched exec Execution[] calldata executions = executionCalldata.decodeBatch(); // check if execType is revert or try if (execType == EXECTYPE_DEFAULT) returnData = _execute(executions); else if (execType == EXECTYPE_TRY) returnData = _tryExecute(executions); else revert UnsupportedExecType(execType); } else if (callType == CALLTYPE_SINGLE) { // destructure executionCallData according to single exec ( address target, uint256 value, bytes calldata callData ) = executionCalldata.decodeSingle(); returnData = new bytes[](1); bool success; // check if execType is revert or try if (execType == EXECTYPE_DEFAULT) { returnData[0] = _execute(target, value, callData); } // TODO: implement event emission for tryExecute singleCall else if (execType == EXECTYPE_TRY) { (success, returnData[0]) = _tryExecute(target, value, callData); if (!success) emit TryExecuteUnsuccessful(0, returnData[0]); } else { revert UnsupportedExecType(execType); } } else if (callType == CALLTYPE_DELEGATECALL) { // destructure executionCallData according to single exec address delegate = address( uint160(bytes20(executionCalldata[0:20])) ); bytes calldata callData = executionCalldata[20:]; // check if execType is revert or try if (execType == EXECTYPE_DEFAULT) _executeDelegatecall(delegate, callData); else if (execType == EXECTYPE_TRY) _tryExecuteDelegatecall(delegate, callData); else revert UnsupportedExecType(execType); } else { revert UnsupportedCallType(callType); } } /** * @dev see {IERC7579Account}. */ function executeUserOp( PackedUserOperation calldata userOp ) external payable onlyEntryPointOrSelf { bytes calldata callData = userOp.callData[4:]; (bool success, ) = address(this).delegatecall(callData); if (!success) revert ExecutionFailed(); } /** * @dev see {IERC7579Account}. */ function installModule( uint256 moduleTypeId, address module, bytes calldata initData ) external payable onlyEntryPointOrSelf { if (moduleTypeId == MODULE_TYPE_VALIDATOR) _installValidator(module, initData); else if (moduleTypeId == MODULE_TYPE_EXECUTOR) _installExecutor(module, initData); else if (moduleTypeId == MODULE_TYPE_FALLBACK) { _installFallbackHandler(module, initData); } else if (moduleTypeId == MODULE_TYPE_HOOK) _installHook(module, initData); else revert UnsupportedModuleType(moduleTypeId); emit ModuleInstalled(moduleTypeId, module); } /** * @dev see {IERC7579Account}. */ function uninstallModule( uint256 moduleTypeId, address module, bytes calldata deInitData ) external payable onlyEntryPointOrSelf { if (moduleTypeId == MODULE_TYPE_VALIDATOR) { _uninstallValidator(module, deInitData); } else if (moduleTypeId == MODULE_TYPE_EXECUTOR) { _uninstallExecutor(module, deInitData); } else if (moduleTypeId == MODULE_TYPE_FALLBACK) { _uninstallFallbackHandler(module, deInitData); } else if (moduleTypeId == MODULE_TYPE_HOOK) { _uninstallHook(module, deInitData); } else { revert UnsupportedModuleType(moduleTypeId); } emit ModuleUninstalled(moduleTypeId, module); } /** * @dev see {IERC7579Account}. */ function validateUserOp( PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds ) external payable virtual override onlyEntryPoint payPrefund(missingAccountFunds) returns (uint256 validSignature) { address validator; // @notice validator encoding in nonce is just an example! // @notice this is not part of the standard! // Account Vendors may choose any other way to implement validator selection uint256 nonce = userOp.nonce; assembly { validator := shr(96, nonce) } // check if validator is enabled. If not terminate the validation phase. if (!_isValidatorInstalled(validator)) return VALIDATION_FAILED; // bubble up the return value of the validator module validSignature = IValidator(validator).validateUserOp( userOp, userOpHash ); } /** * @dev ERC-1271 isValidSignature * This function is intended to be used to validate a smart account signature * and may forward the call to a validator module * * @param hash The hash of the data that is signed * @param data The data that is signed */ function isValidSignature( bytes32 hash, bytes calldata data ) external view virtual override returns (bytes4) { address validator = address(bytes20(data[0:20])); if (!_isValidatorInstalled(validator)) revert InvalidModule(validator); return IValidator(validator).isValidSignatureWithSender( msg.sender, hash, data[20:] ); } /** * @dev see {IERC7579Account}. */ function isModuleInstalled( uint256 moduleTypeId, address module, bytes calldata additionalContext ) external view override returns (bool) { if (moduleTypeId == MODULE_TYPE_VALIDATOR) { return _isValidatorInstalled(module); } else if (moduleTypeId == MODULE_TYPE_EXECUTOR) { return _isExecutorInstalled(module); } else if (moduleTypeId == MODULE_TYPE_FALLBACK) { return _isFallbackHandlerInstalled( abi.decode(additionalContext, (bytes4)), module ); } else if (moduleTypeId == MODULE_TYPE_HOOK) { return _isHookInstalled(module); } else { return false; } } /** * @dev see {IERC7579Account}. */ function accountId() external view virtual override returns (string memory) { return "etherspotwallet.modular.v1.0.0"; } /** * @dev see {IERC7579Account}. */ function supportsExecutionMode( ModeCode mode ) external view virtual override returns (bool isSupported) { (CallType callType, ExecType execType, , ) = mode.decode(); if (callType == CALLTYPE_BATCH) isSupported = true; else if (callType == CALLTYPE_SINGLE) isSupported = true; else if (callType == CALLTYPE_DELEGATECALL) isSupported = true; // if callType is not single, batch or delegatecall return false else return false; if (execType == EXECTYPE_DEFAULT) isSupported = true; else if (execType == EXECTYPE_TRY) isSupported = true; // if execType is not default or try, return false else return false; } /** * @dev see {IERC7579Account}. */ function supportsModule( uint256 modulTypeId ) external view virtual override returns (bool) { if (modulTypeId == MODULE_TYPE_VALIDATOR) return true; else if (modulTypeId == MODULE_TYPE_EXECUTOR) return true; else if (modulTypeId == MODULE_TYPE_FALLBACK) return true; else if (modulTypeId == MODULE_TYPE_HOOK) return true; else return false; } /** * @dev see {IERC7579Account}. */ function initializeAccount( bytes calldata data ) public payable virtual onlyProxy { _initModuleManager(); (address owner, address bootstrap, bytes memory bootstrapCall) = abi .decode(data, (address, address, bytes)); _addOwner(owner); (bool success, ) = bootstrap.delegatecall(bootstrapCall); if (!success) revert AccountInitializationFailed(); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; /** * @title ModeLib * To allow smart accounts to be very simple, but allow for more complex execution, A custom mode * encoding is used. * Function Signature of execute function: * function execute(ModeCode mode, bytes calldata executionCalldata) external payable; * This allows for a single bytes32 to be used to encode the execution mode, calltype, execType and * context. * NOTE: Simple Account implementations only have to scope for the most significant byte. Account that * implement * more complex execution modes may use the entire bytes32. * * |--------------------------------------------------------------------| * | CALLTYPE | EXECTYPE | UNUSED | ModeSelector | ModePayload | * |--------------------------------------------------------------------| * | 1 byte | 1 byte | 4 bytes | 4 bytes | 22 bytes | * |--------------------------------------------------------------------| * * CALLTYPE: 1 byte * CallType is used to determine how the executeCalldata paramter of the execute function has to be * decoded. * It can be either single, batch or delegatecall. In the future different calls could be added. * CALLTYPE can be used by a validation module to determine how to decode <userOp.callData[36:]>. * * EXECTYPE: 1 byte * ExecType is used to determine how the account should handle the execution. * It can indicate if the execution should revert on failure or continue execution. * In the future more execution modes may be added. * Default Behavior (EXECTYPE = 0x00) is to revert on a single failed execution. If one execution in * a batch fails, the entire batch is reverted * * UNUSED: 4 bytes * Unused bytes are reserved for future use. * * ModeSelector: bytes4 * The "optional" mode selector can be used by account vendors, to implement custom behavior in * their accounts. * the way a ModeSelector is to be calculated is bytes4(keccak256("vendorname.featurename")) * this is to prevent collisions between different vendors, while allowing innovation and the * development of new features without coordination between ERC-7579 implementing accounts * * ModePayload: 22 bytes * Mode payload is used to pass additional data to the smart account execution, this may be * interpreted depending on the ModeSelector * * ExecutionCallData: n bytes * single, delegatecall or batch exec abi.encoded as bytes */ import { Execution } from "../interfaces/IERC7579Account.sol"; // Custom type for improved developer experience type ModeCode is bytes32; type CallType is bytes1; type ExecType is bytes1; type ModeSelector is bytes4; type ModePayload is bytes22; // Default CallType CallType constant CALLTYPE_SINGLE = CallType.wrap(0x00); // Batched CallType CallType constant CALLTYPE_BATCH = CallType.wrap(0x01); // @dev Implementing delegatecall is OPTIONAL! // implement delegatecall with extreme care. CallType constant CALLTYPE_STATIC = CallType.wrap(0xFE); CallType constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF); // @dev default behavior is to revert on failure // To allow very simple accounts to use mode encoding, the default behavior is to revert on failure // Since this is value 0x00, no additional encoding is required for simple accounts ExecType constant EXECTYPE_DEFAULT = ExecType.wrap(0x00); // @dev account may elect to change execution behavior. For example "try exec" / "allow fail" ExecType constant EXECTYPE_TRY = ExecType.wrap(0x01); ModeSelector constant MODE_DEFAULT = ModeSelector.wrap(bytes4(0x00000000)); // Example declaration of a custom mode selector ModeSelector constant MODE_OFFSET = ModeSelector.wrap(bytes4(keccak256("default.mode.offset"))); /** * @dev ModeLib is a helper library to encode/decode ModeCodes */ library ModeLib { function decode(ModeCode mode) internal pure returns ( CallType _calltype, ExecType _execType, ModeSelector _modeSelector, ModePayload _modePayload ) { assembly { _calltype := mode _execType := shl(8, mode) _modeSelector := shl(48, mode) _modePayload := shl(80, mode) } } function encode( CallType callType, ExecType execType, ModeSelector mode, ModePayload payload ) internal pure returns (ModeCode) { return ModeCode.wrap( bytes32( abi.encodePacked(callType, execType, bytes4(0), ModeSelector.unwrap(mode), payload) ) ); } function encodeSimpleBatch() internal pure returns (ModeCode mode) { mode = encode(CALLTYPE_BATCH, EXECTYPE_DEFAULT, MODE_DEFAULT, ModePayload.wrap(0x00)); } function encodeSimpleSingle() internal pure returns (ModeCode mode) { mode = encode(CALLTYPE_SINGLE, EXECTYPE_DEFAULT, MODE_DEFAULT, ModePayload.wrap(0x00)); } function getCallType(ModeCode mode) internal pure returns (CallType calltype) { assembly { calltype := mode } } } using { eqModeSelector as == } for ModeSelector global; using { eqCallType as == } for CallType global; using { eqExecType as == } for ExecType global; function eqCallType(CallType a, CallType b) pure returns (bool) { return CallType.unwrap(a) == CallType.unwrap(b); } function eqExecType(ExecType a, ExecType b) pure returns (bool) { return ExecType.unwrap(a) == ExecType.unwrap(b); } function eqModeSelector(ModeSelector a, ModeSelector b) pure returns (bool) { return ModeSelector.unwrap(a) == ModeSelector.unwrap(b); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import {Execution} from "../interfaces/IERC7579Account.sol"; /** * Helper Library for decoding Execution calldata * malloc for memory allocation is bad for gas. use this assembly instead */ library ExecutionLib { function decodeBatch( bytes calldata callData ) internal pure returns (Execution[] calldata executionBatch) { /* * Batch Call Calldata Layout * Offset (in bytes) | Length (in bytes) | Contents * 0x0 | 0x4 | bytes4 function selector * 0x4 | - | abi.encode(IERC7579Execution.Execution[]) */ // solhint-disable-next-line no-inline-assembly assembly ("memory-safe") { let dataPointer := add( callData.offset, calldataload(callData.offset) ) // Extract the ERC7579 Executions executionBatch.offset := add(dataPointer, 32) executionBatch.length := calldataload(dataPointer) } } function encodeBatch( Execution[] memory executions ) internal pure returns (bytes memory callData) { callData = abi.encode(executions); } function decodeSingle( bytes calldata executionCalldata ) internal pure returns (address target, uint256 value, bytes calldata callData) { target = address(bytes20(executionCalldata[0:20])); value = uint256(bytes32(executionCalldata[20:52])); callData = executionCalldata[52:]; } function encodeSingle( address target, uint256 value, bytes memory callData ) internal pure returns (bytes memory userOpCalldata) { userOpCalldata = abi.encodePacked(target, value, callData); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { Execution } from "../interfaces/IERC7579Account.sol"; /** * @title Execution * @dev This contract executes calls in the context of this contract. * @author zeroknots.eth | rhinestone.wtf * shoutout to solady (vectorized, ross) for this code * https://github.com/Vectorized/solady/blob/main/src/accounts/ERC4337.sol */ contract ExecutionHelper { error ExecutionFailed(); event TryExecuteUnsuccessful(uint256 batchExecutionindex, bytes result); function _execute(Execution[] calldata executions) internal returns (bytes[] memory result) { uint256 length = executions.length; result = new bytes[](length); for (uint256 i; i < length; i++) { Execution calldata _exec = executions[i]; result[i] = _execute(_exec.target, _exec.value, _exec.callData); } } function _tryExecute(Execution[] calldata executions) internal returns (bytes[] memory result) { uint256 length = executions.length; result = new bytes[](length); for (uint256 i; i < length; i++) { Execution calldata _exec = executions[i]; bool success; (success, result[i]) = _tryExecute(_exec.target, _exec.value, _exec.callData); if (!success) emit TryExecuteUnsuccessful(i, result[i]); } } function _execute( address target, uint256 value, bytes calldata callData ) internal virtual returns (bytes memory result) { /// @solidity memory-safe-assembly assembly { result := mload(0x40) calldatacopy(result, callData.offset, callData.length) if iszero(call(gas(), target, value, result, callData.length, codesize(), 0x00)) { // Bubble up the revert if the call reverts. returndatacopy(result, 0x00, returndatasize()) revert(result, returndatasize()) } mstore(result, returndatasize()) // Store the length. let o := add(result, 0x20) returndatacopy(o, 0x00, returndatasize()) // Copy the returndata. mstore(0x40, add(o, returndatasize())) // Allocate the memory. } } function _tryExecute( address target, uint256 value, bytes calldata callData ) internal virtual returns (bool success, bytes memory result) { /// @solidity memory-safe-assembly assembly { result := mload(0x40) calldatacopy(result, callData.offset, callData.length) success := call(gas(), target, value, result, callData.length, codesize(), 0x00) mstore(result, returndatasize()) // Store the length. let o := add(result, 0x20) returndatacopy(o, 0x00, returndatasize()) // Copy the returndata. mstore(0x40, add(o, returndatasize())) // Allocate the memory. } } /// @dev Execute a delegatecall with `delegate` on this account. function _executeDelegatecall( address delegate, bytes calldata callData ) internal returns (bytes memory result) { /// @solidity memory-safe-assembly assembly { result := mload(0x40) calldatacopy(result, callData.offset, callData.length) // Forwards the `data` to `delegate` via delegatecall. if iszero(delegatecall(gas(), delegate, result, callData.length, codesize(), 0x00)) { // Bubble up the revert if the call reverts. returndatacopy(result, 0x00, returndatasize()) revert(result, returndatasize()) } mstore(result, returndatasize()) // Store the length. let o := add(result, 0x20) returndatacopy(o, 0x00, returndatasize()) // Copy the returndata. mstore(0x40, add(o, returndatasize())) // Allocate the memory. } } /// @dev Execute a delegatecall with `delegate` on this account and catch reverts. function _tryExecuteDelegatecall( address delegate, bytes calldata callData ) internal returns (bool success, bytes memory result) { /// @solidity memory-safe-assembly assembly { result := mload(0x40) calldatacopy(result, callData.offset, callData.length) // Forwards the `data` to `delegate` via delegatecall. success := delegatecall(gas(), delegate, result, callData.length, codesize(), 0x00) mstore(result, returndatasize()) // Store the length. let o := add(result, 0x20) returndatacopy(o, 0x00, returndatasize()) // Copy the returndata. mstore(0x40, add(o, returndatasize())) // Allocate the memory. } } }
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.5; /** * User Operation struct * @param sender - The sender account of this request. * @param nonce - Unique value the sender uses to verify it is not a replay. * @param initCode - If set, the account contract will be created by this constructor/ * @param callData - The method call to execute on this account. * @param accountGasLimits - Packed gas limits for validateUserOp and gas limit passed to the callData method call. * @param preVerificationGas - Gas not calculated by the handleOps method, but added to the gas paid. * Covers batch overhead. * @param gasFees - packed gas fields maxPriorityFeePerGas and maxFeePerGas - Same as EIP-1559 gas parameters. * @param paymasterAndData - If set, this field holds the paymaster address, verification gas limit, postOp gas limit and paymaster-specific extra data * The paymaster will pay for the transaction instead of the sender. * @param signature - Sender-verified signature over the entire request, the EntryPoint address and the chain ID. */ struct PackedUserOperation { address sender; uint256 nonce; bytes initCode; bytes callData; bytes32 accountGasLimits; uint256 preVerificationGas; bytes32 gasFees; bytes paymasterAndData; bytes signature; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.21; import {PackedUserOperation} from "../../../../account-abstraction/contracts/interfaces/PackedUserOperation.sol"; uint256 constant VALIDATION_SUCCESS = 0; uint256 constant VALIDATION_FAILED = 1; uint256 constant MODULE_TYPE_VALIDATOR = 1; uint256 constant MODULE_TYPE_EXECUTOR = 2; uint256 constant MODULE_TYPE_FALLBACK = 3; uint256 constant MODULE_TYPE_HOOK = 4; interface IModule { error AlreadyInitialized(address smartAccount); error NotInitialized(address smartAccount); /** * @dev This function is called by the smart account during installation of the module * @param data arbitrary data that may be required on the module during `onInstall` * initialization * * MUST revert on error (i.e. if module is already enabled) */ function onInstall(bytes calldata data) external; /** * @dev This function is called by the smart account during uninstallation of the module * @param data arbitrary data that may be required on the module during `onUninstall` * de-initialization * * MUST revert on error */ function onUninstall(bytes calldata data) external; /** * @dev Returns boolean value if module is a certain type * @param moduleTypeId the module type ID according the ERC-7579 spec * * MUST return true if the module is of the given type and false otherwise */ function isModuleType(uint256 moduleTypeId) external view returns (bool); /** * @dev Returns if the module was already initialized for a provided smartaccount */ function isInitialized(address smartAccount) external view returns (bool); } interface IValidator is IModule { error InvalidTargetAddress(address target); /** * @dev Validates a transaction on behalf of the account. * This function is intended to be called by the MSA during the ERC-4337 validaton phase * Note: solely relying on bytes32 hash and signature is not suffcient for some * validation implementations (i.e. SessionKeys often need access to userOp.calldata) * @param userOp The user operation to be validated. The userOp MUST NOT contain any metadata. * The MSA MUST clean up the userOp before sending it to the validator. * @param userOpHash The hash of the user operation to be validated * @return return value according to ERC-4337 */ function validateUserOp( PackedUserOperation calldata userOp, bytes32 userOpHash ) external returns (uint256); /** * Validator can be used for ERC-1271 validation */ function isValidSignatureWithSender( address sender, bytes32 hash, bytes calldata data ) external view returns (bytes4); } interface IExecutor is IModule { } interface IHook is IModule { function preCheck( address msgSender, bytes calldata msgData ) external returns (bytes memory hookData); function postCheck(bytes calldata hookData) external returns (bool success); } interface IFallback is IModule { }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.21; import {IMSA} from "../erc7579-ref-impl/interfaces/IMSA.sol"; import {IAccessController} from "./IAccessController.sol"; interface IModularEtherspotWallet is IMSA, IAccessController { error OnlyProxy(); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.21; import {SentinelListLib, SENTINEL} from "../libs/SentinelList.sol"; import {CallType, CALLTYPE_SINGLE, CALLTYPE_DELEGATECALL, CALLTYPE_STATIC} from "../libs/ModeLib.sol"; import {AccountBase} from "./AccountBase.sol"; import "../interfaces/IERC7579Module.sol"; import "forge-std/interfaces/IERC165.sol"; import "./Receiver.sol"; import {ArrayLib} from "../../libraries/ArrayLib.sol"; /** * @title ModuleManager * @author zeroknots.eth | rhinestone.wtf * @dev This contract manages Validator, Executor and Fallback modules for the MSA * @dev it uses SentinelList to manage the linked list of modules * NOTE: the linked list is just an example. accounts may implement this differently */ abstract contract ModuleManager is AccountBase, Receiver { using SentinelListLib for SentinelListLib.SentinelList; error InvalidModule(address module); error NoFallbackHandler(bytes4 selector); error CannotRemoveLastValidator(); error FallbackInvalidCallType(); error InvalidFallbackCaller(address caller); // keccak256("modulemanager.storage.msa"); bytes32 internal constant MODULEMANAGER_STORAGE_LOCATION = 0xf88ce1fdb7fb1cbd3282e49729100fa3f2d6ee9f797961fe4fb1871cea89ea02; struct FallbackHandler { address handler; CallType calltype; address[] allowedCallers; } /// @custom:storage-location erc7201:modulemanager.storage.msa struct ModuleManagerStorage { // linked list of validators. List is initialized by initializeAccount() SentinelListLib.SentinelList $validators; // linked list of executors. List is initialized by initializeAccount() SentinelListLib.SentinelList $executors; // single fallback handler for all fallbacks // account vendors may implement this differently. This is just a reference implementation mapping(bytes4 selector => FallbackHandler fallbackHandler) $fallbacks; } function $moduleManager() internal pure virtual returns (ModuleManagerStorage storage $ims) { bytes32 position = MODULEMANAGER_STORAGE_LOCATION; assembly { $ims.slot := position } } modifier onlyExecutorModule() { SentinelListLib.SentinelList storage $executors = $moduleManager() .$executors; if (!$executors.contains(msg.sender)) revert InvalidModule(msg.sender); _; } modifier onlyValidatorModule(address validator) { SentinelListLib.SentinelList storage $validators = $moduleManager() .$validators; if (!$validators.contains(validator)) revert InvalidModule(validator); _; } function _initModuleManager() internal virtual { ModuleManagerStorage storage $ims = $moduleManager(); $ims.$executors.init(); $ims.$validators.init(); } function isAlreadyInitialized() internal view virtual returns (bool) { ModuleManagerStorage storage $ims = $moduleManager(); return $ims.$validators.alreadyInitialized(); } ///////////////////////////////////////////////////// // Manage Validators //////////////////////////////////////////////////// function _installValidator( address validator, bytes calldata data ) internal virtual { SentinelListLib.SentinelList storage $validators = $moduleManager() .$validators; $validators.push(validator); IValidator(validator).onInstall(data); } function _uninstallValidator( address validator, bytes calldata data ) internal { // TODO: check if its the last validator. this might brick the account SentinelListLib.SentinelList storage $validators = $moduleManager() .$validators; (address prev, bytes memory disableModuleData) = abi.decode( data, (address, bytes) ); $validators.pop(prev, validator); IValidator(validator).onUninstall(disableModuleData); } function _isValidatorInstalled( address validator ) internal view virtual returns (bool) { SentinelListLib.SentinelList storage $validators = $moduleManager() .$validators; return $validators.contains(validator); } /** * THIS IS NOT PART OF THE STANDARD * Helper Function to access linked list */ function getValidatorPaginated( address cursor, uint256 size ) external view virtual returns (address[] memory array, address next) { SentinelListLib.SentinelList storage $validators = $moduleManager() .$validators; return $validators.getEntriesPaginated(cursor, size); } ///////////////////////////////////////////////////// // Manage Executors //////////////////////////////////////////////////// function _installExecutor(address executor, bytes calldata data) internal { SentinelListLib.SentinelList storage $executors = $moduleManager() .$executors; $executors.push(executor); IExecutor(executor).onInstall(data); } function _uninstallExecutor( address executor, bytes calldata data ) internal { SentinelListLib.SentinelList storage $executors = $moduleManager() .$executors; (address prev, bytes memory disableModuleData) = abi.decode( data, (address, bytes) ); $executors.pop(prev, executor); IExecutor(executor).onUninstall(disableModuleData); } function _isExecutorInstalled( address executor ) internal view virtual returns (bool) { SentinelListLib.SentinelList storage $executors = $moduleManager() .$executors; return $executors.contains(executor); } /** * THIS IS NOT PART OF THE STANDARD * Helper Function to access linked list */ function getExecutorsPaginated( address cursor, uint256 size ) external view virtual returns (address[] memory array, address next) { SentinelListLib.SentinelList storage $executors = $moduleManager() .$executors; return $executors.getEntriesPaginated(cursor, size); } ///////////////////////////////////////////////////// // Manage Fallback //////////////////////////////////////////////////// function _installFallbackHandler( address handler, bytes calldata params ) internal virtual { bytes memory _params = params; bytes4 selector; CallType calltype; address[] memory allowedCallers; bytes memory initData; assembly { let configPtr := add(params.offset, 0x20) let configLen := calldataload(params.offset) selector := calldataload(params.offset) calltype := calldataload(configPtr) let allowedCallersLen := calldataload(add(configPtr, 0x20)) allowedCallers := mload(0x40) mstore( 0x40, add( allowedCallers, and(add(mul(allowedCallersLen, 0x20), 0x1f), not(0x1f)) ) ) for { let i := 0 } lt(i, allowedCallersLen) { i := add(i, 1) } { mstore( add(allowedCallers, mul(i, 0x20)), calldataload(add(configPtr, add(0x60, mul(i, 0x20)))) ) } let initDataPos := calldataload(add(configPtr, 0x40)) let initDataLen := calldataload( sub(add(configPtr, initDataPos), 0x20) ) let initDataPtr := 0x60 mstore(initDataPtr, initDataLen) calldatacopy( add(initDataPtr, 0x20), add(configPtr, initDataPos), initDataLen ) initData := initDataPtr } if (calltype == CALLTYPE_DELEGATECALL) revert FallbackInvalidCallType(); if (_isFallbackHandlerInstalled(selector)) { revert("Function selector already used"); } $moduleManager().$fallbacks[selector] = FallbackHandler( handler, calltype, allowedCallers ); IFallback(handler).onInstall(initData); } function _uninstallFallbackHandler( address handler, bytes calldata deInitData ) internal virtual { bytes4 selector = bytes4(deInitData[0:4]); bytes memory _deInitData = deInitData[4:]; if (!_isFallbackHandlerInstalled(selector)) { revert("Function selector not used"); } FallbackHandler memory activeFallback = $moduleManager().$fallbacks[ selector ]; if (activeFallback.handler != handler) { revert("Function selector not used by this handler"); } CallType callType = activeFallback.calltype; if (callType == CALLTYPE_DELEGATECALL) revert FallbackInvalidCallType(); address[] memory allowedCallers = new address[](0); $moduleManager().$fallbacks[selector] = FallbackHandler( address(0), CallType.wrap(0x00), allowedCallers ); IFallback(handler).onUninstall(_deInitData); } function _isFallbackHandlerInstalled( bytes4 functionSig ) internal view virtual returns (bool) { FallbackHandler storage $fallback = $moduleManager().$fallbacks[ functionSig ]; return $fallback.handler != address(0); } function _isFallbackHandlerInstalled( bytes4 functionSig, address _handler ) internal view virtual returns (bool) { FallbackHandler storage $fallback = $moduleManager().$fallbacks[ functionSig ]; return $fallback.handler == _handler; } function getActiveFallbackHandler( bytes4 functionSig ) external view virtual returns (FallbackHandler memory) { return $moduleManager().$fallbacks[functionSig]; } // validates that the caller is allowed and reverts if not. function _validateCaller(bytes4 sig) private view { address[] memory allowed = $moduleManager() .$fallbacks[sig] .allowedCallers; if (ArrayLib._contains(allowed, msg.sender) == false) { revert InvalidFallbackCaller(msg.sender); } } // FALLBACK // calling _validateCaller() fallback() external payable override(Receiver) { _validateCaller(msg.sig); FallbackHandler storage $fallbackHandler = $moduleManager().$fallbacks[ msg.sig ]; address handler = $fallbackHandler.handler; CallType calltype = $fallbackHandler.calltype; if (handler == address(0)) revert NoFallbackHandler(msg.sig); if (calltype == CALLTYPE_STATIC) { assembly { function allocate(length) -> pos { pos := mload(0x40) mstore(0x40, add(pos, length)) } let calldataPtr := allocate(calldatasize()) calldatacopy(calldataPtr, 0, calldatasize()) // The msg.sender address is shifted to the left by 12 bytes to remove the padding // Then the address without padding is stored right after the calldata let senderPtr := allocate(20) mstore(senderPtr, shl(96, caller())) // Add 20 bytes for the address appended add the end let success := staticcall( gas(), handler, calldataPtr, add(calldatasize(), 20), 0, 0 ) let returnDataPtr := allocate(returndatasize()) returndatacopy(returnDataPtr, 0, returndatasize()) if iszero(success) { revert(returnDataPtr, returndatasize()) } return(returnDataPtr, returndatasize()) } } if (calltype == CALLTYPE_SINGLE) { assembly { function allocate(length) -> pos { pos := mload(0x40) mstore(0x40, add(pos, length)) } let calldataPtr := allocate(calldatasize()) calldatacopy(calldataPtr, 0, calldatasize()) // The msg.sender address is shifted to the left by 12 bytes to remove the padding // Then the address without padding is stored right after the calldata let senderPtr := allocate(20) mstore(senderPtr, shl(96, caller())) // Add 20 bytes for the address appended add the end let success := call( gas(), handler, 0, calldataPtr, add(calldatasize(), 20), 0, 0 ) let returnDataPtr := allocate(returndatasize()) returndatacopy(returnDataPtr, 0, returndatasize()) if iszero(success) { revert(returnDataPtr, returndatasize()) } return(returnDataPtr, returndatasize()) } } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.21; import "./ModuleManager.sol"; import "../interfaces/IERC7579Account.sol"; import "../interfaces/IERC7579Module.sol"; /** * @title reference implementation of HookManager * @author zeroknots.eth | rhinestone.wtf */ abstract contract HookManager { /// @custom:storage-location erc7201:hookmanager.storage.msa struct HookManagerStorage { IHook _hook; } // keccak256("hookmanager.storage.msa"); bytes32 constant HOOKMANAGER_STORAGE_LOCATION = 0x36e05829dd1b9a4411d96a3549582172d7f071c1c0db5c573fcf94eb28431608; error HookPostCheckFailed(); error HookAlreadyInstalled(address currentHook); modifier withHook() { address hook = _getHook(); if (hook == address(0)) { _; } else { bytes memory hookData = IHook(hook).preCheck(msg.sender, msg.data); _; if (!IHook(hook).postCheck(hookData)) revert HookPostCheckFailed(); } } function _setHook(address hook) internal virtual { bytes32 slot = HOOKMANAGER_STORAGE_LOCATION; assembly { sstore(slot, hook) } } function _installHook(address hook, bytes calldata data) internal virtual { address currentHook = _getHook(); if (currentHook != address(0)) { revert HookAlreadyInstalled(currentHook); } _setHook(hook); IHook(hook).onInstall(data); } function _uninstallHook( address hook, bytes calldata data ) internal virtual { _setHook(address(0)); IHook(hook).onUninstall(data); } function _getHook() internal view returns (address _hook) { bytes32 slot = HOOKMANAGER_STORAGE_LOCATION; assembly { _hook := sload(slot) } } function _isHookInstalled(address module) internal view returns (bool) { return _getHook() == module; } function getActiveHook() external view returns (address hook) { return _getHook(); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.21; import {IAccessController} from "../interfaces/IAccessController.sol"; import {ErrorsLib} from "../libraries/ErrorsLib.sol"; contract AccessController is IAccessController { /// State Variables uint128 constant MULTIPLY_FACTOR = 1000; uint16 constant SIXTY_PERCENT = 600; uint24 constant INITIAL_PROPOSAL_TIMELOCK = 24 hours; uint256 public ownerCount; uint256 public guardianCount; uint256 public proposalId; uint256 public proposalTimelock; /// Mappings mapping(address => bool) private _owners; mapping(address => bool) private _guardians; mapping(uint256 => NewOwnerProposal) private _proposals; /// Modifiers modifier onlyOwnerOrSelf() { if (!(isOwner(msg.sender) || msg.sender == address(this))) { revert ErrorsLib.OnlyOwnerOrSelf(); } _; } modifier onlyGuardian() { if (!isGuardian(msg.sender)) { revert ErrorsLib.OnlyGuardian(); } _; } modifier onlyOwnerOrGuardianOrSelf() { if ( !(isOwner(msg.sender) || isGuardian(msg.sender) || msg.sender == address(this)) ) { revert ErrorsLib.OnlyOwnerOrGuardianOrSelf(); } _; } /// External /** * @notice Add owner to the wallet. * @dev Only owner or wallet. * @param _newOwner address of new owner to add. */ function addOwner(address _newOwner) external onlyOwnerOrSelf { if ( _newOwner == address(0) || isGuardian(_newOwner) || isOwner(_newOwner) ) { revert ErrorsLib.AddingInvalidOwner(); } _addOwner(_newOwner); emit OwnerAdded(address(this), _newOwner); } /** * @notice Remove owner from wallet. * @dev Only owner or wallet. * @param _owner address of wallet owner to remove . */ function removeOwner(address _owner) external onlyOwnerOrSelf { if (!isOwner(_owner)) revert ErrorsLib.RemovingInvalidOwner(); if (ownerCount <= 1) { revert ErrorsLib.WalletNeedsOwner(); } _removeOwner(_owner); emit OwnerRemoved(address(this), _owner); } /** * @notice Add guardian for the wallet. * @dev Only owner or wallet. * @param _newGuardian address of new guardian to add to wallet. */ function addGuardian(address _newGuardian) external onlyOwnerOrSelf { if ( _newGuardian == address(0) || isGuardian(_newGuardian) || isOwner(_newGuardian) ) { revert ErrorsLib.AddingInvalidGuardian(); } _addGuardian(_newGuardian); emit GuardianAdded(address(this), _newGuardian); } /** * @notice Remove guardian from the wallet. * @dev Only owner or wallet. * @param _guardian address of existing guardian to remove. */ function removeGuardian(address _guardian) external onlyOwnerOrSelf { if (!isGuardian(_guardian)) revert ErrorsLib.RemovingInvalidGuardian(); _removeGuardian(_guardian); emit GuardianRemoved(address(this), _guardian); } /** * @notice Change the timelock on proposals. * The minimum time (secs) that a proposal is allowed to be discarded. * @dev Only owner or wallet. * @param _newTimelock new timelock in seconds. */ function changeProposalTimelock( uint256 _newTimelock ) external onlyOwnerOrSelf { assembly { sstore(proposalTimelock.slot, _newTimelock) } } /** * @notice Discards the current proposal. * @dev Only owner or guardian or wallet. Must be after the proposal timelock is met. */ function discardCurrentProposal() public onlyOwnerOrGuardianOrSelf { NewOwnerProposal storage prop = _proposals[proposalId]; uint256 timelock = proposalTimelock == 0 ? INITIAL_PROPOSAL_TIMELOCK : proposalTimelock; if (_resolvedProposal()) { revert ErrorsLib.ProposalResolved(); } bool allowed = isGuardian(msg.sender); if (allowed && (prop.proposedAt + timelock >= block.timestamp)) revert ErrorsLib.ProposalTimelocked(); prop.resolved = true; emit ProposalDiscarded(address(this), proposalId, msg.sender); } /** * @notice Creates a new owner proposal (adds new owner to wallet). * @dev Only guardian. * @param _newOwner the proposed new owner for the wallet. */ function guardianPropose(address _newOwner) external onlyGuardian { if ( _newOwner == address(0) || isGuardian(_newOwner) || isOwner(_newOwner) ) { revert ErrorsLib.AddingInvalidOwner(); } if (guardianCount < 3) { revert ErrorsLib.NotEnoughGuardians(); } NewOwnerProposal storage prop = _proposals[proposalId]; if (prop.guardiansApproved.length != 0 && !prop.resolved) { revert ErrorsLib.ProposalUnresolved(); } uint256 newProposalId = proposalId + 1; _proposals[newProposalId].newOwnerProposed = _newOwner; _proposals[newProposalId].guardiansApproved.push(msg.sender); _proposals[newProposalId].approvalCount++; _proposals[newProposalId].resolved = false; _proposals[newProposalId].proposedAt = block.timestamp; proposalId = newProposalId; emit ProposalSubmitted( address(this), newProposalId, _newOwner, msg.sender ); } /** * @notice Cosigns a new owner proposal. * @dev Only guardian. Must meet minimum threshold of 60% of total guardians to add new owner. */ function guardianCosign() external onlyGuardian { uint256 latestId = proposalId; NewOwnerProposal storage latestProp = _proposals[latestId]; if (latestId == 0) { revert ErrorsLib.InvalidProposal(); } if (_checkIfSigned(latestId)) { revert ErrorsLib.AlreadySignedProposal(); } if (_resolvedProposal()) { revert ErrorsLib.ProposalResolved(); } _proposals[latestId].guardiansApproved.push(msg.sender); _proposals[latestId].approvalCount++; address newOwner = latestProp.newOwnerProposed; if (_checkQuorumReached(latestId)) { _proposals[latestId].resolved = true; _addOwner(newOwner); } else { emit QuorumNotReached( address(this), latestId, newOwner, _proposals[latestId].approvalCount ); } } /// Views /** * @notice Checks if _address is owner of wallet. * @param _address address to check if owner of wallet. * @return bool. */ function isOwner(address _address) public view returns (bool) { return _owners[_address]; } /** * @notice Checks if _address is guardian of wallet. * @param _address address to check if guardian of wallet. * @return bool. */ function isGuardian(address _address) public view returns (bool) { return _guardians[_address]; } /** * @notice Returns new owner proposal data. * @param _proposalId proposal id to return data for. * @return ownerProposed_ the new owner proposed. * @return approvalCount_ number of guardians that have approved the proposal. * @return guardiansApproved_ array of guardian addresses that have approved proposal. * @return resolved_ bool is the proposal resolved. * @return proposedAt_ timestamp of when proposal was initiated. */ function getProposal( uint256 _proposalId ) public view returns ( address ownerProposed_, uint256 approvalCount_, address[] memory guardiansApproved_, bool resolved_, uint256 proposedAt_ ) { if (_proposalId == 0 || _proposalId > proposalId) { revert ErrorsLib.InvalidProposal(); } NewOwnerProposal memory proposal = _proposals[_proposalId]; return ( proposal.newOwnerProposed, proposal.approvalCount, proposal.guardiansApproved, proposal.resolved, proposal.proposedAt ); } /// Internal function _addOwner(address _newOwner) internal { _owners[_newOwner] = true; ownerCount++; } function _addGuardian(address _newGuardian) internal { _guardians[_newGuardian] = true; guardianCount++; if (!_resolvedProposal()) discardCurrentProposal(); } function _removeOwner(address _owner) internal { _owners[_owner] = false; ownerCount--; } function _removeGuardian(address _guardian) internal { _guardians[_guardian] = false; guardianCount--; if (!_resolvedProposal()) discardCurrentProposal(); } function _checkIfSigned(uint256 _proposalId) internal view returns (bool) { for ( uint256 i; i < _proposals[_proposalId].guardiansApproved.length; i++ ) { if (_proposals[_proposalId].guardiansApproved[i] == msg.sender) { return true; } } return false; } function _checkQuorumReached( uint256 _proposalId ) internal view returns (bool) { return ((_proposals[_proposalId].approvalCount * MULTIPLY_FACTOR) / guardianCount >= SIXTY_PERCENT); } function _resolvedProposal() internal view returns (bool) { NewOwnerProposal storage prop = _proposals[proposalId]; return prop.resolved; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.21; import { CallType, ExecType, ModeCode } from "../libs/ModeLib.sol"; import {PackedUserOperation} from "../../../../account-abstraction/contracts/interfaces/IAccount.sol"; struct Execution { address target; uint256 value; bytes callData; } interface IERC7579Account { event ModuleInstalled(uint256 moduleTypeId, address module); event ModuleUninstalled(uint256 moduleTypeId, address module); /** * @dev Executes a transaction on behalf of the account. * This function is intended to be called by ERC-4337 EntryPoint.sol * @dev Ensure adequate authorization control: i.e. onlyEntryPointOrSelf * * @dev MSA MUST implement this function signature. * If a mode is requested that is not supported by the Account, it MUST revert * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details * @param executionCalldata The encoded execution call data */ function execute(ModeCode mode, bytes calldata executionCalldata) external payable; /** * @dev Executes a transaction on behalf of the account. * This function is intended to be called by Executor Modules * @dev Ensure adequate authorization control: i.e. onlyExecutorModule * * @dev MSA MUST implement this function signature. * If a mode is requested that is not supported by the Account, it MUST revert * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details * @param executionCalldata The encoded execution call data */ function executeFromExecutor( ModeCode mode, bytes calldata executionCalldata ) external payable returns (bytes[] memory returnData); /** * @dev ERC-4337 executeUserOp according to ERC-4337 v0.7 * This function is intended to be called by ERC-4337 EntryPoint.sol * @dev Ensure adequate authorization control: i.e. onlyEntryPointOrSelf * The implementation of the function is OPTIONAL * * @param userOp PackedUserOperation struct (see ERC-4337 v0.7+) */ function executeUserOp(PackedUserOperation calldata userOp) external payable; /** * @dev ERC-4337 validateUserOp according to ERC-4337 v0.7 * This function is intended to be called by ERC-4337 EntryPoint.sol * this validation function should decode / sload the validator module to validate the userOp * and call it. * * @dev MSA MUST implement this function signature. * @param userOp PackedUserOperation struct (see ERC-4337 v0.7+) */ function validateUserOp( PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds ) external payable returns (uint256 validSignature); /** * @dev ERC-1271 isValidSignature * This function is intended to be used to validate a smart account signature * and may forward the call to a validator module * * @param hash The hash of the data that is signed * @param data The data that is signed */ function isValidSignature(bytes32 hash, bytes calldata data) external view returns (bytes4); /** * @dev installs a Module of a certain type on the smart account * @dev Implement Authorization control of your chosing * @param moduleTypeId the module type ID according the ERC-7579 spec * @param module the module address * @param initData arbitrary data that may be required on the module during `onInstall` * initialization. */ function installModule( uint256 moduleTypeId, address module, bytes calldata initData ) external payable; /** * @dev uninstalls a Module of a certain type on the smart account * @dev Implement Authorization control of your chosing * @param moduleTypeId the module type ID according the ERC-7579 spec * @param module the module address * @param deInitData arbitrary data that may be required on the module during `onUninstall` * de-initialization. */ function uninstallModule( uint256 moduleTypeId, address module, bytes calldata deInitData ) external payable; /** * Function to check if the account supports a certain CallType or ExecType (see ModeLib.sol) * @param encodedMode the encoded mode */ function supportsExecutionMode(ModeCode encodedMode) external view returns (bool); /** * Function to check if the account supports installation of a certain module type Id * @param moduleTypeId the module type ID according the ERC-7579 spec */ function supportsModule(uint256 moduleTypeId) external view returns (bool); /** * Function to check if the account has a certain module installed * @param moduleTypeId the module type ID according the ERC-7579 spec * Note: keep in mind that some contracts can be multiple module types at the same time. It * thus may be necessary to query multiple module types * @param module the module address * @param additionalContext additional context data that the smart account may interpret to * identifiy conditions under which the module is installed. * usually this is not necessary, but for some special hooks that * are stored in mappings, this param might be needed */ function isModuleInstalled( uint256 moduleTypeId, address module, bytes calldata additionalContext ) external view returns (bool); /** * @dev Returns the account id of the smart account * @return accountImplementationId the account id of the smart account * the accountId should be structured like so: * "vendorname.accountname.semver" */ function accountId() external view returns (string memory accountImplementationId); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.21; import {IERC7579Account} from "./IERC7579Account.sol"; import {CallType, ExecType, ModeCode} from "../libs/ModeLib.sol"; interface IMSA is IERC7579Account { // Error thrown when an unsupported ModuleType is requested error UnsupportedModuleType(uint256 moduleType); // Error thrown when an execution with an unsupported CallType was made error UnsupportedCallType(CallType callType); // Error thrown when an execution with an unsupported ExecType was made error UnsupportedExecType(ExecType execType); // Error thrown when account initialization fails error AccountInitializationFailed(); /** * @dev Initializes the account. Function might be called directly, or by a Factory * @param data. encoded data that can be used during the initialization phase */ function initializeAccount(bytes calldata data) external payable; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.21; interface IAccessController { /// Structs struct NewOwnerProposal { address newOwnerProposed; bool resolved; address[] guardiansApproved; uint256 approvalCount; uint256 proposedAt; } /// Events event OwnerAdded(address account, address newOwner); event OwnerRemoved(address account, address removedOwner); event GuardianAdded(address account, address newGuardian); event GuardianRemoved(address account, address removedGuardian); event ProposalSubmitted( address account, uint256 proposalId, address newOwnerProposed, address proposer ); event QuorumNotReached( address account, uint256 proposalId, address newOwnerProposed, uint256 approvalCount ); event ProposalDiscarded( address account, uint256 proposalId, address discardedBy ); /// External function addOwner(address _newOwner) external; function removeOwner(address _owner) external; function addGuardian(address _newGuardian) external; function removeGuardian(address _guardian) external; function changeProposalTimelock(uint256 _newTimelock) external; function discardCurrentProposal() external; function guardianPropose(address _newOwner) external; function guardianCosign() external; /// Views function isOwner(address _address) external view returns (bool); function isGuardian(address _address) external view returns (bool); function getProposal( uint256 _proposalId ) external view returns ( address ownerProposed_, uint256 approvalCount_, address[] memory guardiansApproved_, bool resolved_, uint256 proposedAt_ ); }
// https://github.com/zeroknots/sentinellist/ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; address constant SENTINEL = address(0x1); address constant ZERO_ADDRESS = address(0x0); library SentinelListLib { struct SentinelList { mapping(address => address) entries; } error LinkedList_AlreadyInitialized(); error LinkedList_InvalidPage(); error LinkedList_InvalidEntry(address entry); error LinkedList_EntryAlreadyInList(address entry); function init(SentinelList storage self) internal { if (alreadyInitialized(self)) revert LinkedList_AlreadyInitialized(); self.entries[SENTINEL] = SENTINEL; } function alreadyInitialized( SentinelList storage self ) internal view returns (bool) { return self.entries[SENTINEL] != ZERO_ADDRESS; } function getNext( SentinelList storage self, address entry ) internal view returns (address) { if (entry == ZERO_ADDRESS) { revert LinkedList_InvalidEntry(entry); } return self.entries[entry]; } function push(SentinelList storage self, address newEntry) internal { if (newEntry == ZERO_ADDRESS || newEntry == SENTINEL) { revert LinkedList_InvalidEntry(newEntry); } if (self.entries[newEntry] != ZERO_ADDRESS) revert LinkedList_EntryAlreadyInList(newEntry); self.entries[newEntry] = self.entries[SENTINEL]; self.entries[SENTINEL] = newEntry; } function pop( SentinelList storage self, address prevEntry, address popEntry ) internal { if (popEntry == ZERO_ADDRESS || popEntry == SENTINEL) { revert LinkedList_InvalidEntry(prevEntry); } if (self.entries[prevEntry] != popEntry) revert LinkedList_InvalidEntry(popEntry); self.entries[prevEntry] = self.entries[popEntry]; self.entries[popEntry] = ZERO_ADDRESS; } function contains( SentinelList storage self, address entry ) internal view returns (bool) { return SENTINEL != entry && self.entries[entry] != ZERO_ADDRESS; } function getEntriesPaginated( SentinelList storage self, address start, uint256 pageSize ) internal view returns (address[] memory array, address next) { if (start != SENTINEL && contains(self, start)) revert LinkedList_InvalidEntry(start); if (pageSize == 0) revert LinkedList_InvalidPage(); // Init array with max page size array = new address[](pageSize); // Populate return array uint256 entryCount = 0; next = self.entries[start]; while ( next != ZERO_ADDRESS && next != SENTINEL && entryCount < pageSize ) { array[entryCount] = next; next = self.entries[next]; entryCount++; } /** * Because of the argument validation, we can assume that the loop will always iterate over the valid entry list values * and the `next` variable will either be an enabled entry or a sentinel address (signalling the end). * * If we haven't reached the end inside the loop, we need to set the next pointer to the last element of the entry array * because the `next` variable (which is a entry by itself) acting as a pointer to the start of the next page is neither * incSENTINELrent page, nor will it be included in the next one if you pass it as a start. */ if (next != SENTINEL) { next = array[entryCount - 1]; } // Set correct size of returned array // solhint-disable-next-line no-inline-assembly /// @solidity memory-safe-assembly assembly { mstore(array, entryCount) } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.21; /** * @title reference implementation of the minimal modular smart account with Hook Extension * @author zeroknots.eth | rhinestone.wtf */ contract AccountBase { error AccountAccessUnauthorized(); ///////////////////////////////////////////////////// // Access Control //////////////////////////////////////////////////// modifier onlyEntryPointOrSelf() virtual { if (!(msg.sender == entryPoint() || msg.sender == address(this))) { revert AccountAccessUnauthorized(); } _; } modifier onlyEntryPoint() virtual { if (msg.sender != entryPoint()) { revert AccountAccessUnauthorized(); } _; } function entryPoint() public view virtual returns (address) { return 0x0000000071727De22E5E9d8BAf0edAc6f37da032; } /// @dev Sends to the EntryPoint (i.e. `msg.sender`) the missing funds for this transaction. /// Subclass MAY override this modifier for better funds management. /// (e.g. send to the EntryPoint more than the minimum required, so that in future transactions /// it will not be required to send again) /// /// `missingAccountFunds` is the minimum value this modifier should send the EntryPoint, /// which MAY be zero, in case there is enough deposit, or the userOp has a paymaster. modifier payPrefund(uint256 missingAccountFunds) virtual { _; /// @solidity memory-safe-assembly assembly { if missingAccountFunds { // Ignore failure (it's EntryPoint's job to verify, not the account's). pop( call( gas(), caller(), missingAccountFunds, codesize(), 0x00, codesize(), 0x00 ) ) } } } }
// SPDX-License-Identifier: MIT pragma solidity >=0.6.2; interface IERC165 { /// @notice Query if a contract implements an interface /// @param interfaceID The interface identifier, as specified in ERC-165 /// @dev Interface identification is specified in ERC-165. This function /// uses less than 30,000 gas. /// @return `true` if the contract implements `interfaceID` and /// `interfaceID` is not 0xffffffff, `false` otherwise function supportsInterface(bytes4 interfaceID) external view returns (bool); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; /** * @title Receiver * @dev This contract receives safe-transferred ERC721 and ERC1155 tokens. * @author Modified from Solady * (https://github.com/Vectorized/solady/blob/main/src/accounts/Receiver.sol) */ abstract contract Receiver { /// @dev For receiving ETH. receive() external payable virtual {} /// @dev Fallback function with the `receiverFallback` modifier. fallback() external payable virtual {} }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; library ArrayLib { function _contains( address[] memory A, address a ) internal pure returns (bool) { (, bool isIn) = _indexOf(A, a); return isIn; } function _indexOf( address[] memory A, address a ) internal pure returns (uint256, bool) { uint256 length = A.length; for (uint256 i = 0; i < length; i++) { if (A[i] == a) { return (i, true); } } return (0, false); } function _removeElement( address[] storage _data, address _element ) internal { uint256 length = _data.length; // remove item from array and resize array for (uint256 ii = 0; ii < length; ii++) { if (_data[ii] == _element) { if (length > 1) { _data[ii] = _data[length - 1]; } _data.pop(); break; } } } function _removeElement( address[] memory _data, address _element ) internal pure returns (address[] memory) { address[] memory newData = new address[](_data.length - 1); uint256 j; for (uint256 i; i < _data.length; i++) { if (_data[i] != _element) { newData[j] = _data[i]; j++; } } return newData; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; library ErrorsLib { /// AccessController error OnlyOwnerOrSelf(); error OnlyGuardian(); error OnlyOwnerOrGuardianOrSelf(); error AddingInvalidOwner(); error RemovingInvalidOwner(); error AddingInvalidGuardian(); error RemovingInvalidGuardian(); error WalletNeedsOwner(); error NotEnoughGuardians(); error ProposalResolved(); error ProposalUnresolved(); error AlreadySignedProposal(); error ProposalTimelocked(); error InvalidProposal(); // EtherspotWallet7579 Errors }
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.5; import "./PackedUserOperation.sol"; interface IAccount { /** * Validate user's signature and nonce * the entryPoint will make the call to the recipient only if this validation call returns successfully. * signature failure should be reported by returning SIG_VALIDATION_FAILED (1). * This allows making a "simulation call" without a valid signature * Other failures (e.g. nonce mismatch, or invalid signature format) should still revert to signal failure. * * @dev Must validate caller is the entryPoint. * Must validate the signature and nonce * @param userOp - The operation that is about to be executed. * @param userOpHash - Hash of the user's request data. can be used as the basis for signature. * @param missingAccountFunds - Missing funds on the account's deposit in the entrypoint. * This is the minimum amount to transfer to the sender(entryPoint) to be * able to make the call. The excess is left as a deposit in the entrypoint * for future calls. Can be withdrawn anytime using "entryPoint.withdrawTo()". * In case there is a paymaster in the request (or the current deposit is high * enough), this value will be zero. * @return validationData - Packaged ValidationData structure. use `_packValidationData` and * `_unpackValidationData` to encode and decode. * <20-byte> sigAuthorizer - 0 for valid signature, 1 to mark signature failure, * otherwise, an address of an "authorizer" contract. * <6-byte> validUntil - Last timestamp this operation is valid. 0 for "indefinite" * <6-byte> validAfter - First timestamp this operation is valid * If an account doesn't use time-range, it is enough to * return SIG_VALIDATION_FAILED value (1) for signature failure. * Note that the validation code cannot use block.timestamp (or block.number) directly. */ function validateUserOp( PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds ) external returns (uint256 validationData); }
{ "remappings": [ "ds-test/=lib/forge-std/lib/ds-test/src/", "forge-std/=lib/forge-std/src/", "@openzeppelin/=node_modules/@openzeppelin/", "@solady/=node_modules/solady/src/", "sentinellist/=erc7579-ref-impl/lib/sentinellist/src/", "@ensdomains/=node_modules/@ensdomains/", "@uniswap/=node_modules/@uniswap/", "base64-sol/=node_modules/base64-sol/", "eth-gas-reporter/=node_modules/eth-gas-reporter/", "hardhat-deploy/=node_modules/hardhat-deploy/", "hardhat/=node_modules/hardhat/", "solady/=node_modules/solady/" ], "optimizer": { "enabled": true, "runs": 200 }, "metadata": { "useLiteralContent": false, "bytecodeHash": "ipfs", "appendCBOR": true }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "abi" ] } }, "evmVersion": "paris", "viaIR": false, "libraries": {} }
[{"inputs":[],"name":"AccountAccessUnauthorized","type":"error"},{"inputs":[],"name":"AccountInitializationFailed","type":"error"},{"inputs":[],"name":"AddingInvalidGuardian","type":"error"},{"inputs":[],"name":"AddingInvalidOwner","type":"error"},{"inputs":[],"name":"AlreadySignedProposal","type":"error"},{"inputs":[],"name":"CannotRemoveLastValidator","type":"error"},{"inputs":[],"name":"ExecutionFailed","type":"error"},{"inputs":[],"name":"FallbackInvalidCallType","type":"error"},{"inputs":[{"internalType":"address","name":"currentHook","type":"address"}],"name":"HookAlreadyInstalled","type":"error"},{"inputs":[],"name":"HookPostCheckFailed","type":"error"},{"inputs":[{"internalType":"address","name":"caller","type":"address"}],"name":"InvalidFallbackCaller","type":"error"},{"inputs":[{"internalType":"address","name":"module","type":"address"}],"name":"InvalidModule","type":"error"},{"inputs":[],"name":"InvalidProposal","type":"error"},{"inputs":[],"name":"LinkedList_AlreadyInitialized","type":"error"},{"inputs":[{"internalType":"address","name":"entry","type":"address"}],"name":"LinkedList_EntryAlreadyInList","type":"error"},{"inputs":[{"internalType":"address","name":"entry","type":"address"}],"name":"LinkedList_InvalidEntry","type":"error"},{"inputs":[],"name":"LinkedList_InvalidPage","type":"error"},{"inputs":[{"internalType":"bytes4","name":"selector","type":"bytes4"}],"name":"NoFallbackHandler","type":"error"},{"inputs":[],"name":"NotEnoughGuardians","type":"error"},{"inputs":[],"name":"OnlyGuardian","type":"error"},{"inputs":[],"name":"OnlyOwnerOrGuardianOrSelf","type":"error"},{"inputs":[],"name":"OnlyOwnerOrSelf","type":"error"},{"inputs":[],"name":"OnlyProxy","type":"error"},{"inputs":[],"name":"ProposalResolved","type":"error"},{"inputs":[],"name":"ProposalTimelocked","type":"error"},{"inputs":[],"name":"ProposalUnresolved","type":"error"},{"inputs":[],"name":"RemovingInvalidGuardian","type":"error"},{"inputs":[],"name":"RemovingInvalidOwner","type":"error"},{"inputs":[{"internalType":"CallType","name":"callType","type":"bytes1"}],"name":"UnsupportedCallType","type":"error"},{"inputs":[{"internalType":"ExecType","name":"execType","type":"bytes1"}],"name":"UnsupportedExecType","type":"error"},{"inputs":[{"internalType":"uint256","name":"moduleType","type":"uint256"}],"name":"UnsupportedModuleType","type":"error"},{"inputs":[],"name":"WalletNeedsOwner","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"newGuardian","type":"address"}],"name":"GuardianAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"removedGuardian","type":"address"}],"name":"GuardianRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"moduleTypeId","type":"uint256"},{"indexed":false,"internalType":"address","name":"module","type":"address"}],"name":"ModuleInstalled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"moduleTypeId","type":"uint256"},{"indexed":false,"internalType":"address","name":"module","type":"address"}],"name":"ModuleUninstalled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"removedOwner","type":"address"}],"name":"OwnerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"proposalId","type":"uint256"},{"indexed":false,"internalType":"address","name":"discardedBy","type":"address"}],"name":"ProposalDiscarded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"proposalId","type":"uint256"},{"indexed":false,"internalType":"address","name":"newOwnerProposed","type":"address"},{"indexed":false,"internalType":"address","name":"proposer","type":"address"}],"name":"ProposalSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"proposalId","type":"uint256"},{"indexed":false,"internalType":"address","name":"newOwnerProposed","type":"address"},{"indexed":false,"internalType":"uint256","name":"approvalCount","type":"uint256"}],"name":"QuorumNotReached","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"batchExecutionindex","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"result","type":"bytes"}],"name":"TryExecuteUnsuccessful","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"accountId","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newGuardian","type":"address"}],"name":"addGuardian","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newOwner","type":"address"}],"name":"addOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newTimelock","type":"uint256"}],"name":"changeProposalTimelock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"discardCurrentProposal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"entryPoint","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"ModeCode","name":"mode","type":"bytes32"},{"internalType":"bytes","name":"executionCalldata","type":"bytes"}],"name":"execute","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"ModeCode","name":"mode","type":"bytes32"},{"internalType":"bytes","name":"executionCalldata","type":"bytes"}],"name":"executeFromExecutor","outputs":[{"internalType":"bytes[]","name":"returnData","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"bytes32","name":"accountGasLimits","type":"bytes32"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"bytes32","name":"gasFees","type":"bytes32"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct PackedUserOperation","name":"userOp","type":"tuple"}],"name":"executeUserOp","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"functionSig","type":"bytes4"}],"name":"getActiveFallbackHandler","outputs":[{"components":[{"internalType":"address","name":"handler","type":"address"},{"internalType":"CallType","name":"calltype","type":"bytes1"},{"internalType":"address[]","name":"allowedCallers","type":"address[]"}],"internalType":"struct ModuleManager.FallbackHandler","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getActiveHook","outputs":[{"internalType":"address","name":"hook","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"cursor","type":"address"},{"internalType":"uint256","name":"size","type":"uint256"}],"name":"getExecutorsPaginated","outputs":[{"internalType":"address[]","name":"array","type":"address[]"},{"internalType":"address","name":"next","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_proposalId","type":"uint256"}],"name":"getProposal","outputs":[{"internalType":"address","name":"ownerProposed_","type":"address"},{"internalType":"uint256","name":"approvalCount_","type":"uint256"},{"internalType":"address[]","name":"guardiansApproved_","type":"address[]"},{"internalType":"bool","name":"resolved_","type":"bool"},{"internalType":"uint256","name":"proposedAt_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"cursor","type":"address"},{"internalType":"uint256","name":"size","type":"uint256"}],"name":"getValidatorPaginated","outputs":[{"internalType":"address[]","name":"array","type":"address[]"},{"internalType":"address","name":"next","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"guardianCosign","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"guardianCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newOwner","type":"address"}],"name":"guardianPropose","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"implementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"initializeAccount","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"moduleTypeId","type":"uint256"},{"internalType":"address","name":"module","type":"address"},{"internalType":"bytes","name":"initData","type":"bytes"}],"name":"installModule","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"isGuardian","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"moduleTypeId","type":"uint256"},{"internalType":"address","name":"module","type":"address"},{"internalType":"bytes","name":"additionalContext","type":"bytes"}],"name":"isModuleInstalled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"isOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"isValidSignature","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ownerCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proposalId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proposalTimelock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_guardian","type":"address"}],"name":"removeGuardian","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"removeOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"ModeCode","name":"mode","type":"bytes32"}],"name":"supportsExecutionMode","outputs":[{"internalType":"bool","name":"isSupported","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"modulTypeId","type":"uint256"}],"name":"supportsModule","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"moduleTypeId","type":"uint256"},{"internalType":"address","name":"module","type":"address"},{"internalType":"bytes","name":"deInitData","type":"bytes"}],"name":"uninstallModule","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"bytes32","name":"accountGasLimits","type":"bytes32"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"bytes32","name":"gasFees","type":"bytes32"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct PackedUserOperation","name":"userOp","type":"tuple"},{"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"internalType":"uint256","name":"missingAccountFunds","type":"uint256"}],"name":"validateUserOp","outputs":[{"internalType":"uint256","name":"validSignature","type":"uint256"}],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}]
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.