Skip to main content

Writing Custom Policies

You can write your own reveal policy by implementing the IRevealPolicy interface.

Step 1: Implement the interface

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IRevealPolicy} from "./IRevealPolicy.sol";

contract MinAmountPolicy is IRevealPolicy {
function validate(
bytes32 commitment,
bytes32 nullifier,
address recipient,
uint256 amount,
address token,
bytes calldata policyParams
) external view returns (bool) {
uint256 minAmount = abi.decode(policyParams, (uint256));
return amount >= minAmount;
}
}

Step 2: Deploy

forge create src/MinAmountPolicy.sol:MinAmountPolicy \
--rpc-url https://testnet.specterchain.com \
--chain-id 5445 \
--private-key $PRIVATE_KEY

Step 3: Commit with your policy

const policyAddress = '0xYourPolicyAddress';
const minAmount = ethers.parseEther('1'); // minimum 1 GHOST
const policyParams = ethers.AbiCoder.defaultAbiCoder().encode(['uint256'], [minAmount]);

// Compute policyParamsHash for the commitment
const policyParamsHash = poseidon([minAmount]);

const tx = await vault.commitNativeWithPolicy(
commitment,
ethers.ZeroHash,
policyAddress,
policyParamsHash,
{ value: amount, gasPrice: 1_000_000_000n }
);

Constraints

  • Must be view or pure — called via staticcall, no state changes allowed
  • 100K gas limit — keep logic simple and efficient
  • Must return booltrue to allow, false to reject
  • Policy params must match — the policyParamsHash in the commitment must match the hash of the params provided at reveal time (verified in the ZK circuit)

Design patterns

Read external state

Policies can read from other contracts (oracles, registries, etc.):

function validate(...) external view returns (bool) {
uint256 price = IOracle(ORACLE).getPrice(token);
return amount * price >= MIN_USD_VALUE;
}

Combine conditions

function validate(...) external view returns (bool) {
(uint256 notBefore, address allowedRecipient) = abi.decode(policyParams, (uint256, address));
return block.timestamp >= notBefore && recipient == allowedRecipient;
}

Allowlist-based

mapping(address => bool) public allowed;

function validate(...) external view returns (bool) {
return allowed[recipient];
}
warning

Remember the 100K gas limit. Complex logic, large loops, or extensive external calls may exceed this limit and cause the reveal to fail.