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