ModuleKit
ModuleKit Quick Start
The quickest way to get started with building a smart account module. This quickstart guide will walk you through building a simple validator module. To learn more about validators, check out the validators section of the docs. To learn more about the ModuleKit tools that you will be using during this guide, check out the tools section of the docs.
Validators are smart account modules that are called during the validation phase of a UserOperation. This means that their primary function is to verify the signature of a UserOperation and determine whether it is valid and should be executed.
This tutorial uses Foundry, a toolchain to simplify and speed up smart contract development, and ModuleKit, a framework to build and test smart account modules. If you are not familiar with Foundry, feel free to check out their docs.
If you do not have foundry installed, run the following command and follow the onscreen instructions: bash curl -L https://foundry.paradigm.xyz | bash
Installation
- Get started with ModuleKit by using our template:
git clone https://github.com/rhinestonewtf/module-template.git
cd module-template
forge install
Building the validator
In this section, we will walk you through building a simple validator module.
-
Duplicate the file
src/validators/ValidatorTemplate.sol
and rename it toSimpleValidator.sol
and the contract toSimpleValidator
. -
Add the following import statement to the top of the file:
import {ECDSA} from "solady/utils/ECDSA.sol";
and add the following code to the top of the contract:
contract SimpleValidator is BaseValidator {
using ECDSA for bytes32;
mapping(address => address) public owners;
function setOwner(address owner) external {
owners[msg.sender] = owner;
}
...
}
- Edit the contract functions to match the following code:
validateUserOp
function validateUserOp(UserOperation calldata userOp, bytes32 userOpHash)
external
view
override
returns (uint256)
{
bytes32 hash = userOpHash.toEthSignedMessageHash();
(bytes memory sig, ) = abi.decode(userOp.signature, (bytes, address));
if (owners[msg.sender] != hash.recover(sig)) {
return VALIDATION_FAILED;
}
return VALIDATION_SUCCESS;
}
This function validates the userOperation by checking that the signature is valid and made by the owner of the account.
isValidSignature
function isValidSignature(bytes32 signedDataHash, bytes memory moduleSignature)
public
view
override
returns (bytes4)
{
bytes32 hash = signedDataHash.toEthSignedMessageHash();
if (owners[msg.sender] != hash.recover(moduleSignature)) {
return 0xffffffff;
}
return ERC1271_MAGICVALUE;
}
This function checks that the signature is valid and made by the owner of the account. This allows the validator to be used as an EIP-1271 validator.
Congratulations, you just built your first validator!
To continue on your journey, follow the steps below or head into our tutorials section for more walkthroughs, such as how to build different kinds of modules.
Testing the validator
The Rhinestone ModuleKit provides a testing framework to make it easy to test your modules. To learn how to test the SimpleValidator
, continue with this tutorial. To learn more about the tools helpers that are part of the ModuleKit, check out the tools section of the docs.
Deploying the validator
After testing your validator, you can use a dedicated helper component of the ModuleKit to easily deploy the Module and register it on the Module Registry in order to make it available to users. For a walkthrough of how to do this, continue with this tutorial.