Policy Patterns
Common recipes and best practices for Matador policies.
This library contains copy-pasteable patterns for common use cases. Mix and match these snippets to build robust security policies for your smart accounts.
Financial Safety Rails
Fixed Token Spend
Restrict token transfers to one exact approved amount.
import "abis/ERC20.json" as Token;
permission SpendLimit -> 1.0.0 {
parameters: {
authorizer: address,
token: address,
maxAmount: uint256
}
pub fn authorize() -> bool {
return authorizedBy(parameters.authorizer);
}
fn main() -> bool {
if (context.target != parameters.token) {
return false;
}
if (context.selector == Token.transfer) {
return Token.transfer.value == parameters.maxAmount;
}
return false;
}
}One-Time Subscription Charge
Allow a specific service provider to charge a fixed fee once until the policy is reset or replaced.
permission Subscription -> 1.0.0 {
parameters: {
serviceProvider: address,
fee: uint256
}
@persist let chargeCount: uint256 = 0;
pub fn authorize() -> bool {
return authorizedBy(parameters.serviceProvider);
}
fn main() -> bool {
if (context.value != parameters.fee) {
return false;
}
if (state.chargeCount >= 1) {
return false;
}
state.chargeCount = state.chargeCount + 1;
return true;
}
}Security & Access Control
Whitelist
Restrict interactions to a known list of safe contracts (e.g., official Uniswap routers).
permission SafeInteractions -> 1.0.0 {
parameters: {
authorizer: address,
router: address,
vault: address
}
pub fn authorize() -> bool {
return authorizedBy(parameters.authorizer);
}
fn main() -> bool {
if (context.target == parameters.router) {
return true;
}
return context.target == parameters.vault;
}
}Circuit Breaker
Block all transactions if an emergency flag is set in the policy parameters.
permission EmergencyStop -> 1.0.0 {
parameters: {
authorizer: address,
paused: bool
}
pub fn authorize() -> bool {
return authorizedBy(parameters.authorizer);
}
fn main() -> bool {
return parameters.paused == false;
}
}DeFi Automation
Swap with Fixed Slippage Floor
Allow an automated bot to execute swaps, but require the calldata minimum output amount to match the policy parameter.
import "abis/UniswapRouter.json" as Uniswap;
permission SafeSwap -> 1.0.0 {
parameters: {
authorizer: address,
router: address,
minAmountOut: uint256
}
pub fn authorize() -> bool {
return authorizedBy(parameters.authorizer);
}
fn main() -> bool {
if (context.target != parameters.router) {
return false;
}
if (context.selector == Uniswap.exactInputSingle) {
return Uniswap.exactInputSingle.params.amountOutMinimum >= parameters.minAmountOut;
}
return false;
}
}Math Precision
Matador's core arithmetic opcodes operate on uint256. For complex fixed-point math or price conversions, it is recommended to use a Custom Module or a helper contract rather than implementing complex math directly in DSL.
Flash Loan Prevention
Prevent the account from being used as a flash loan borrower by ensuring the transaction origin matches the sender.
permission NoFlashLoan -> 1.0.0 {
parameters: {
trustedCaller: address
}
pub fn authorize() -> bool {
return authorizedBy(parameters.trustedCaller);
}
fn main() -> bool {
return true;
}
}Pre/Post Value Invariant
Capture a value before execution and require the post-execution value to be no lower. This pattern requires an adapter with real pre/post lifecycle support, such as the Safe guard path.
permission ValueDoesNotDecrease -> 1.0.0 {
parameters: {
authorizer: address
}
@transient let valueBefore: uint256;
@persist let accepted: uint256 = 0;
pub fn authorize() -> bool {
return authorizedBy(parameters.authorizer);
}
fn main() -> bool {
if (context.phase == pre) {
valueBefore = context.value;
return true;
}
if (context.phase == post) {
if (context.value >= valueBefore) {
state.accepted = state.accepted + 1;
return true;
}
return false;
}
return false;
}
}Kernel lifecycle installation
Kernel phase-aware and @transient policies require KernelMatador to be the
validator's paired ERC-7579 hook and require the executeUserOp envelope.
Installing the validator without that exact hook binding fails validation.