Calldata Policy Engine
Change the rules, not the contract
Type-safe constraints on calldata, enforced onchain and offchain
Enforce onchain, offchain, or both
Two complete implementations, each standalone. Same API, same bytes, same verdict.
Solidity library
// function approve(address spender, uint256 amount)
bytes memory policy = PolicyBuilder
.create("approve(address,uint256)")
.add(arg(0).isIn(trustedSpenders)) // spender
.add(arg(1).lte(uint256(1_000_000e6))) // amount
.build();TypeScript SDK
// function approve(address spender, uint256 amount)
const policy = PolicyBuilder
.create("approve(address,uint256)")
.add(arg(0).isIn(trustedSpenders)) // spender
.add(arg(1).lte(1_000_000n * 10n ** 6n)) // amount
.build();Encoded policy
60095ea7b30004020241200100020000006a0039010100000000000000000041070028111111111111111111111111111111111111111122222222222222222222222222222222222222220031010100010000000020000020050020000000000000000000000000000000000000000000000000000000e8d4a51000
Hover or tap a field
Advanced constraints
Reach into structs and arrays, compare against transaction context, and allow alternatives with OR.
// struct SwapParams { address tokenIn; address tokenOut; uint256 amount; }
// function swap(SwapParams params)
bytes memory policy = PolicyBuilder
.create("swap((address,address,uint256))")
.add(arg(0, 0).notIn(denied)) // params.tokenIn
.add(arg(0, 1).notIn(denied)) // params.tokenOut
.add(arg(0, 2).gt(uint256(0))) // params.amount
.build();// struct Transfer { address to; uint256 value; }
// function batch(Transfer[] transfers)
bytes memory policy = PolicyBuilder
.create("batch((address,uint256)[])")
.add(arg(0, Path.ALL, 0).notIn(denied)) // transfers[*].to
.add(arg(0, Path.ANY, 0).eq(TREASURY)) // transfers[i].to
.add(arg(0, Path.ANY, 1).gte(MIN_AMOUNT)) // transfers[i].value
.build();// function deposit(uint256 assets, address receiver)
bytes memory policy = PolicyBuilder
.create("deposit(uint256,address)")
.add(arg(1).eqCtx(PolicyFormat.CTX_MSG_SENDER)) // receiver
.add(blockTimestamp().lt(DEPOSITS_CLOSE)) // block.timestamp
.build();// function supply(address asset, uint256 amount)
bytes memory policy = PolicyBuilder
.create("supply(address,uint256)")
.add(msgSender().eq(OPERATOR)) // group 0
.or()
.add(arg(0).isIn(allowed)) // group 1
.build();// raw ABI payload: (address recipient, uint256 amount)
bytes memory policy = PolicyBuilder
.createRaw("address,uint256")
.add(arg(0).isIn(allowed)) // recipient
.add(arg(1).lte(uint256(1_000e18))) // amount
.build();Formal spec, Lean proofs
A Lean model defines the policy format and proves its guarantees.Both implementations pass the same test vectors.
Read the specificationPolicy Builder
Build a policy visually, rule by rule.
Policy Inspector
Decode any policy blob, field by field.
Policy Enforcer
Test calldata against a policy.