PCL Built-in Policy Templates

component compliance

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

Every leaf template pairs a templateId string with a Solidity parameter struct declared in IPcl.sol.

templateIdParameter structPurpose
DENYLIST_POLICYDenylistPolicy { address[] addresses; }Block specific senders / recipients.
VOLUME_POLICYVolumePolicy { string[] tokens; VolumeUnitPolicy[] limits; }Per-transfer min / max amount bounds per asset.
PERIODIC_VOLUME_POLICYPeriodicVolumePolicy { string[] tokens; UnitPeriodicVolumePolicy[] limits; }Rolling per-period volume cap per asset.
EAS_POLICYEasPolicy { address easContract; address indexContract; bytes32 schemaUid; }Require a valid EAS attestation under a specific schema.
OKRW_EAS_TRANSFER_LIMIT_POLICYOkrwEasTransferLimitPolicy { …; uint256 transferLimitAmount; }Cap a single OKRW transfer amount when the sender has (or lacks) an attestation.
OKRW_EAS_PERIODIC_VOLUME_LIMIT_POLICYOkrwEasPeriodicVolumeLimitPolicy { …; uint256 maxAmount; uint64 resetPeriodSeconds; }Periodic OKRW volume cap gated by attestation state.
AGENT_OKRW_TRANSFER_LIMIT_POLICYAgentOkrwTransferLimitPolicy { 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

Two additional templates are structural — they contain other PolicySets rather than leaf parameters:

templateIdParameter structPurpose
LOGICAL_POLICYLogicalPolicy { LogicalQuantifier quantifier; PolicySet[] children; }Combine children under AND (And) or OR (Or). On full-Or failure, reverts AnyOfRejected(bytes[] childReverts).
FOR_EACH_POLICYForEachPolicy { 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

The pattern is identical for every template — build the parameter struct, 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

Only templates that have been registered on the current network by the policy admin can be instantiated. Call 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);
Source: maroo
ESC
Type to search