PCL Built-in Policy Templates
The seven leaf policy templates Maroo ships with V1 — denylists, volume limits, attestation gates, KYC-tiered variants, and per-agent transfer caps — plus two composition templates (LOGICAL_POLICY, FOR_EACH_POLICY) for combining them.
PCL ships with seven leaf policy templates that cover the most common regulatory and business compliance gates: DENYLIST_POLICY, VOLUME_POLICY, PERIODIC_VOLUME_POLICY, EAS_POLICY, OKRW_EAS_TRANSFER_LIMIT_POLICY, OKRW_EAS_PERIODIC_VOLUME_LIMIT_POLICY, and AGENT_OKRW_TRANSFER_LIMIT_POLICY. Each defines a Solidity parameter struct in IPcl.sol that callers abi.encode into PolicySet.policy. On top of the leaves, PCL exposes two composition templates — LOGICAL_POLICY (AND/OR over child PolicySets) and FOR_EACH_POLICY (lift a child policy across an agent's owners) — documented in the pcl-composite-policies concept.
The seven leaf templates at a glance
templateId string with a Solidity parameter struct declared in IPcl.sol.| templateId | Parameter struct | Purpose |
|---|---|---|
DENYLIST_POLICY | DenylistPolicy { address[] addresses; } | Block specific senders / recipients. |
VOLUME_POLICY | VolumePolicy { string[] tokens; VolumeUnitPolicy[] limits; } | Per-transfer min / max amount bounds per asset. |
PERIODIC_VOLUME_POLICY | PeriodicVolumePolicy { string[] tokens; UnitPeriodicVolumePolicy[] limits; } | Rolling per-period volume cap per asset. |
EAS_POLICY | EasPolicy { address easContract; address indexContract; bytes32 schemaUid; } | Require a valid EAS attestation under a specific schema. |
OKRW_EAS_TRANSFER_LIMIT_POLICY | OkrwEasTransferLimitPolicy { …; uint256 transferLimitAmount; } | Cap a single OKRW transfer amount when the sender has (or lacks) an attestation. |
OKRW_EAS_PERIODIC_VOLUME_LIMIT_POLICY | OkrwEasPeriodicVolumeLimitPolicy { …; uint256 maxAmount; uint64 resetPeriodSeconds; } | Periodic OKRW volume cap gated by attestation state. |
AGENT_OKRW_TRANSFER_LIMIT_POLICY | AgentOkrwTransferLimitPolicy { uint256 reserved; } | Per-transfer OKRW cap read from the calling agent's TransferLimit metadata. |
Each template has a dedicated concept page under
/concepts/compliance/ with the exact struct, defaults, and revert paths.Composition templates
| templateId | Parameter struct | Purpose |
|---|---|---|
LOGICAL_POLICY | LogicalPolicy { LogicalQuantifier quantifier; PolicySet[] children; } | Combine children under AND (And) or OR (Or). On full-Or failure, reverts AnyOfRejected(bytes[] childReverts). |
FOR_EACH_POLICY | ForEachPolicy { ForEachQuantifier quantifier; ForEachSubject subject; PolicySet child; } | Apply a single child once per element of a subject set. V1 supports AgentOwners with Any / Every quantifiers. |
Composition rules and error surfaces are documented in
pcl-composite-policies. Nesting is broadly allowed — composites can contain composites up to the depth bound (MaxDepthExceeded(uint8 maxDepth)); the one nesting exception is the leaf OKRW_EAS_PERIODIC_VOLUME_LIMIT_POLICY, which cannot be used as a nested child (PolicyCannotBeNested).Building a PolicySet from any template
abi.encode it, then wrap in a PolicySet:// Denylist example — the same shape works for every leaf template.
DenylistPolicy memory dl = DenylistPolicy({ addresses: new address[](1) });
dl.addresses[0] = 0xAaaAAaAaaAaAAaAaAaaAAAaaAAaAaAaAaAaAAAaA;
PolicySet memory ps = PolicySet({
templateId: "DENYLIST_POLICY",
policy: abi.encode(dl),
selector: "" // applies to every call on the target contract
}); Sanity-check templates before registration
IPcl.policyTemplate(templateId) before submitting — it returns the descriptor if registered and reverts with InvalidPolicyTemplate otherwise. Template registration is a consortium-governance action, not something a dApp performs itself.const PCL = "0x1000000000000000000000000000000000000005";
const descriptor = await publicClient.readContract({
address: PCL,
abi: pclAbi,
functionName: "policyTemplate",
args: ["EAS_POLICY"],
});
console.log(descriptor.templateId, descriptor.name);