Custom Modules
EVMcrispr is extensible through modules. This guide covers how to build your own module using the SDK.
Module Structure
Section titled “Module Structure”A module lives in modules/<name>/ and contains:
modules/my-module/ src/ commands/ my-command.ts helpers/ my-helper.ts _generated.ts # Auto-generated by codegen index.ts # Module definition package.jsonDefining a Module
Section titled “Defining a Module”Create src/index.ts:
import { defineModule } from "@evmcrispr/sdk";import { commands, helpers } from "./_generated";
export default class MyModule extends defineModule({ name: "my-module", commands, helpers,}) {}Defining a Command
Section titled “Defining a Command”Commands produce transactions (Action[]). Create src/commands/my-command.ts:
import { defineCommand, encodeAction } from "@evmcrispr/sdk";import type MyModule from "..";
export default defineCommand<MyModule>({ name: "my-command", description: "Do something on-chain.", args: [ { name: "target", type: "address" }, { name: "amount", type: "number" }, ], opts: [ { name: "from", type: "address" }, ], async run(module, { target, amount }, { opts }) { return [ encodeAction(target, "transfer(address,uint256)", [ opts.from ?? target, amount, ]), ]; },});Defining a Helper
Section titled “Defining a Helper”Helpers produce values. Create src/helpers/my-helper.ts:
import { defineHelper } from "@evmcrispr/sdk";import type MyModule from "..";
export default defineHelper<MyModule>({ name: "my-helper", description: "Compute something.", returnType: "number", args: [ { name: "value", type: "number" }, { name: "multiplier", type: "number", optional: true }, ], async run(module, { value, multiplier = 2 }) { return String(BigInt(value) * BigInt(multiplier)); },});Argument Types
Section titled “Argument Types”| Type | Description |
|---|---|
address | Ethereum address |
number | Integer (possibly large) |
string | String value |
bytes | Hex-encoded bytes |
bytes32 | 32-byte hex value |
bool | Boolean |
array | Array of values |
any | Any type |
write-abi | Function signature (state-changing) |
read-abi | Function signature (view/pure) |
token-symbol | Token symbol or address |
block | Block of sub-commands |
variable | Variable name ($name) |
helper | Helper reference (@name) |
command | Command reference |
expression | Expression to be evaluated |
dao | DAO identifier |
json-path | JSON path expression |
Argument Options
Section titled “Argument Options”{ name: "params", type: "any", optional: true, // Argument is not required rest: true, // Collects remaining arguments as an array}Running Codegen
Section titled “Running Codegen”After adding commands or helpers, regenerate the import map:
cd modules/my-modulebun ../../packages/sdk/scripts/codegen.tsThis creates src/_generated.ts with lazy imports and metadata.
Accessing Module State
Section titled “Accessing Module State”Commands and helpers receive the module instance as their first argument. Use it to access shared state:
async run(module, args) { const client = await module.getClient(); // Viem PublicClient const chainId = await module.getChainId(); // Current chain ID // ...}Building and Testing
Section titled “Building and Testing”bun run build # Build all packagesbun test:unit # Run tests