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.

// 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();
// 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();

Formal spec, Lean proofs

A Lean model defines the policy format and proves its guarantees.Both implementations pass the same test vectors.

Read the specification
Lean logo