Policy Authoring Workflow
A step-by-step guide to the lifecycle of a Matador policy.
Authoring Matador policies is a structured process that transforms high-level security requirements into immutable on-chain enforcement. This guide walks you through the complete development lifecycle.
Requirements Gathering
Before writing code, define the strict security bounds of the policy.
- Who is the actor? (e.g., an automated bot, a junior admin)
- Where can they interact? (e.g., Uniswap Router, specific ERC20 tokens)
- What functions can they call? (e.g.,
exactInputSingle,transfer) - How much can they spend? (e.g., max 1 ETH per day)
Environment Setup
Ensure you have the Matador CLI installed and your project directory structured. It is recommended to keep your ABIs in a dedicated folder.
# Install the CLI
npm install -D @steerprotocol/matador-cli
# Create structure
mkdir policies abisWriting the Policy
Create a new .matador file. Start by importing necessary ABIs and defining the parameters.
import "abis/UniswapRouter.json" as Uniswap;
permission SwapGuard -> 1.0.0 {
parameters: {
router: address,
operator: address
}
pub fn authorize() -> bool {
return authorizedBy(parameters.operator);
}
fn main() -> bool {
if (context.target == parameters.router) {
return context.selector == Uniswap.exactInputSingle;
} else {
return false;
}
}
}Compilation & Validation
Use the CLI to compile your policy. The compiler performs static analysis to catch type mismatches and syntax errors before deployment.
npx --package @steerprotocol/matador-cli matador-policy-cli compile policies/swap-guard.matador -d ./outCompiler Feedback
The compiler will report precise errors (e.g., Type mismatch: expected address, got uint256) to help you debug your logic.
Testing (Foundry)
Before deploying to a live account, test your policy bytecode using Foundry. Create mock execution contexts to simulate both valid and invalid transactions.
function testPolicy() public {
// Load compiled bytecode
bytes memory bytecode = vm.readFile("./out/swap-guard.json");
// Direct tests require a nonzero test-install namespace. In production,
// the account/module adapter issues and stores this value.
bytes32 stateNamespaceId = keccak256("test.swap-guard.install.1");
// 1. Test Valid Transaction (Should Pass)
interpreter.enforce(bytecode, subject, stateNamespaceId, validExec);
// 2. Test Invalid Transaction (Should Revert)
vm.expectRevert();
interpreter.enforce(bytecode, subject, stateNamespaceId, invalidExec);
}A direct test call stores state in the interpreter and partitions it by the
test caller. Adapter integration tests should instead exercise the real
account/module wrapper so delegatecall, subject, and namespace authority
match production.
Provisioning
Once tested, provision the policy to your smart account on-chain. This usually involves calling a grantPermission function on your account contract.
// On-chain transaction
smartAccount.grantPermission(permissionId, compiledBytecode);Continuous Integration
For production environments, it is recommended to automate this workflow.
- Lint: Run
matador-policy-cli format --checkin CI to ensure code style. - Compile: Run the compiler to verify all policies are valid.
- Test: Run
forge testto execute your policy test suite against the latest bytecode.