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
vieworpure— called viastaticcall, no state changes allowed - 100K gas limit — keep logic simple and efficient
- Must return
bool—trueto allow,falseto reject - Policy params must match — the
policyParamsHashin 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.