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 specificationThe SDK, live in the browser
Build, inspect and enforce policies with the TypeScript SDK. No install.