rhinestone

ModuleKit

Tools

Building modules

Interfaces

  • ValidatorBase: Interface for Validators to inherit from.
  • ExecutorBase: Interface for Executors to inherit from.

Templates

  • Validator: Template implementation for Validators.
  • Executor: Template implementation for Executors.

Conditions

To allow for high flexibility, ModuleKit allows users to configure and set their own conditions on when an executor can be triggered. Executors can be extended to support this behavior by validating conditions using the ComposableConditionManager. The ModuleKit currently has conditions for:

  • Gas price
  • Token price
  • Time based schedules

... and more to come! View all conditions here.

Users can select different conditions and in order to support any existing condition, the executor needs to call the ComposableConditionManager.

...
import { ComposableConditionManager, ConditionConfig } from "modulekit/core/ComposableCondition.sol";

contract GasPriceExecutor is ExecutorBase {
    ...

    mapping(address account => ConditionConfig[] conditions) conditions;

    function execute(address account) external {
        if (!ComposableConditionManager(manager).checkCondition(account, conditions[account])) revert NotExecutable();
        ...
    }

    ...
}

Integrations

Integrations are Solidity libraries that can easily be integrated into modules to simplify the developer experience for common use cases. The ModuleKit currently has integrations for:

  • Yearn
  • ERC-4626 Vaults
  • ChainLink
  • Uniswap

... and more to come! View all integrations here.

To use an integration, simply import it into your module and call the functions that you need.

...
import {YearnWithdraw} from "modulekit/modulekit/integrations/yearn/YearnWidthdraw.sol";

contract YearnModule is ExecutorBase {
    ...

    function withdrawFromYearn(IExecutorManager manager, address account, address yToken) external {
        ...
        YearnWithdraw.withdraw(manager, account, yToken);
    }

    ...
}

Testing modules

Execute ERC-4337 flow

Execute an ERC-4337 transaction on the account with and address target and bytes calldata.

instance.exec4337(address target, bytes memory data);

Execute an ERC-4337 transaction on the account with and address target, a uint256 value and bytes calldata.

instance.exec4337(address target, uint256 value, bytes memory data);

Execute an ERC-4337 transaction on the account with and address target, a uint256 value, bytes calldata and bytes signature.

instance.exec4337(address target, uint256 value, bytes memory data, bytes memory signature);

Add and remove modules to the account

Add a validator to the account by providing the address of the validator.

instance.addValidator(address validator);

Remove a validator to the account by providing the address of the validator.

instance.removeValidator(address validator);

Check if a validator is enabled on the account by providing the address of the validator.

instance.isValidatorEnabled(address validator);

Add a session key using the session key manager, by providing the session key config data.

instance.addSessionKey(uint256 validUntil, uint256 validAfter, address sessionValidationModule, bytes memory sessionKeyData);

Add a hook to the account by providing the address of the hook. (Note: this is not supported on all accounts yet)

instance.addHook(address hook);

Check if a hook is enabled on the account by providing the address of the hook. (Note: this is not supported on all accounts yet)

instance.isHookEnabled(address hook);

Add an executor to the account by providing the address of the executor.

instance.addExecutor(address executor);

Remove an executor to the account by providing the address of the executor.

instance.removeExecutor(address executor);

Check if an executor is enabled on the account by providing the address of the executor.

instance.isExecutorEnabled(address executor);

Set a condition on the ConditionManager by providing the address of the executor and the ConditionConfig[] condition data.

instance.setCondition(address executor, ConditionConfig[] conditions);

Add a fallback handler to the account by providing the bytes4 function signature, a bool whether the method is static (view) or not and the address of the handler. (Note: this is not supported on all accounts yet)

instance.addFallback(bytes4 handleFunctionSig, bool isStatic, address handler);

ERC-4337 utils

Get the hash of the UserOperation in order to, for example, sign it.

instance.getUserOpHash(address target, uint256 value, bytes memory callData);

Gets the formatted UserOperation with calldata.

instance.getFormattedUserOp(address target, uint256 value, bytes memory callData);

Encodes the chosen validator for a UserOperation.

instance.encodeValidator(bytes memory signature, address validator);

Expects an ERC-4337 transaction to revert, similar to Foundry's vm.expectRevert().

instance.expect4337Revert();

Deploying modules

Deterministically deploy a module using the CREATE2 opcode, by providing the bytes creation code of the module, bytes constructor parameters for the module, bytes32 salt vakue to create uniqueness and bytes additional data to be stored on the Module Registry.

deployModule(bytes memory code, bytes memory deployParams, bytes32 salt, bytes memory data);

Deterministically deploy a module using CREATE3, by providing the bytes creation code of the module, bytes constructor parameters for the module, bytes32 salt vakue to create uniqueness and bytes additional data to be stored on the Module Registry.

deployModuleCreate3(bytes memory code, bytes memory deployParams, bytes32 salt, bytes memory data);

Deploy a module using an external factory contract, by providing the address of the factory, bytes encoded calldata to be sent to the factory and bytes additional data to be stored on the Module Registry.

deployModuleViaFactory(address factory, bytes memory callOnFactory, bytes memory data);