xDAI Price: $0.999987 (-0.03%)
Gas: 1.1 GWei

Contract

0x5a017962c0F851F963c710fF4e15B91211044BeB

Overview

xDAI Balance

Gnosis Chain LogoGnosis Chain LogoGnosis Chain Logo1.094434268653746591 xDAI

xDAI Value

$1.09 (@ $1.00/xDAI)

Token Holdings

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

> 10 Internal Transactions and > 10 Token Transfers found.

Latest 14 internal transactions

Parent Transaction Hash Block From To
379259172025-01-07 20:01:459 days ago1736280105
0x5a017962...211044BeB
0.00032374 xDAI
373449302024-12-04 0:31:4543 days ago1733272305
0x5a017962...211044BeB
0.00032744 xDAI
372897852024-11-30 17:45:2047 days ago1732988720
0x5a017962...211044BeB
0.00031812 xDAI
372897782024-11-30 17:44:4547 days ago1732988685
0x5a017962...211044BeB
0.00031812 xDAI
372897702024-11-30 17:44:0047 days ago1732988640
0x5a017962...211044BeB
0.00031812 xDAI
372897512024-11-30 17:42:1047 days ago1732988530
0x5a017962...211044BeB
0.00031444 xDAI
371408552024-11-21 19:40:5556 days ago1732218055
0x5a017962...211044BeB
0.0002461 xDAI
371399162024-11-21 18:19:5056 days ago1732213190
0x5a017962...211044BeB
0.00035742 xDAI
371182682024-11-20 11:08:0057 days ago1732100880
0x5a017962...211044BeB
0.00025669 xDAI
371182612024-11-20 11:07:2057 days ago1732100840
0x5a017962...211044BeB
0.00014239 xDAI
369410062024-11-09 21:12:3068 days ago1731186750
0x5a017962...211044BeB
0.99988598 xDAI
368741082024-11-05 20:55:0072 days ago1730840100
0x5a017962...211044BeB
0.00252909 xDAI
368741082024-11-05 20:55:0072 days ago1730840100  Contract Creation0 xDAI
368741022024-11-05 20:54:3072 days ago1730840070
0x5a017962...211044BeB
0.1 xDAI
Loading...
Loading

Minimal Proxy Contract for 0x202a5598bdba2ce62bffa13ecccb04969719fad9

Contract Name:
ModularEtherspotWallet

Compiler Version
v0.8.23+commit.f704f362

Optimization Enabled:
Yes with 200 runs

Other Settings:
paris EvmVersion, MIT license

Contract Source Code (Solidity Standard Json-Input format)

File 1 of 20 : ModularEtherspotWallet.sol
// 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();
    }
}

File 2 of 20 : ModeLib.sol
// 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);
}

File 3 of 20 : ExecutionLib.sol
// 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);
    }
}

File 4 of 20 : ExecutionHelper.sol
// 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.
        }
    }
}

File 5 of 20 : PackedUserOperation.sol
// 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;
}

File 6 of 20 : IERC7579Module.sol
// 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 { }

File 7 of 20 : IModularEtherspotWallet.sol
// 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();
}

File 8 of 20 : ModuleManager.sol
// 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())
            }
        }
    }
}

File 9 of 20 : HookManager.sol
// 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();
    }
}

File 10 of 20 : AccessController.sol
// 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;
    }
}

File 11 of 20 : IERC7579Account.sol
// 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);
}

File 12 of 20 : IMSA.sol
// 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;
}

File 13 of 20 : IAccessController.sol
// 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_
        );
}

File 14 of 20 : SentinelList.sol
// 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)
        }
    }
}

File 15 of 20 : AccountBase.sol
// 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
                    )
                )
            }
        }
    }
}

File 16 of 20 : IERC165.sol
// 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);
}

File 17 of 20 : Receiver.sol
// 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 {}
}

File 18 of 20 : ArrayLib.sol
// 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;
    }
}

File 19 of 20 : ErrorsLib.sol
// 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
}

File 20 of 20 : IAccount.sol
// 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);
}

Settings
{
  "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": {}
}

Contract ABI

[{"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"}]

Block Transaction Gas Used Reward
view all blocks validated

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
[ 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.