Programmable Compliance Layer (PCL)
An on-chain framework for defining and enforcing custom transaction rules and regulatory policies.
The Programmable Compliance Layer (PCL) is a core module of the Maroo network that enables the creation and enforcement of compliance policies directly on the blockchain. It validates EVM transactions against chain-wide global rules before and after they execute. It validates them against contract-specific rules during execution, when a call goes through a PCL-registered proxy or reaches a policy-aware precompile such as Privacy. This allows for the implementation of complex regulatory requirements, such as KYC/AML checks, transfer restrictions, and volume limits, without altering the core logic of smart contracts.
Key Features
Global and Contract-Specific Policies
Apply rules across the entire network or target specific smart contracts for granular control.
Template-Based Policy Creation
Use pre-defined, reusable policy templates (e.g., Denylist, Volume Limits, EAS Attestation) to quickly configure complex rules.
EVM-native integration
The chain accepts only EVM transactions. Each one — except the policy admin's own recovery calls to PCL's control-plane methods — is checked against the global policies before it executes and again after it succeeds, so chain-wide compliance is enforced at the chain layer.
Extensible via EAS
Uses the Ethereum Attestation Service (EAS) to gate transactions on on-chain attestations — the basis for KYC, KYB, and credential checks.
Architecture
graph TD
subgraph TX["Every transaction"]
A["Tx submitted"] --> B{"Global policies<br/>at submission and before execution"}
B -- pass --> D["EVM execution"]
B -- violation --> E["Rejected before execution"]
D --> R{"Global policies again<br/>after execution"}
R -- pass --> F["State committed"]
R -- violation --> S["Tx fails, still mined and charged"]
end
subgraph PX["A call into a PCL-wrapped contract"]
G["Call via PCL proxy"] --> H{"PCL preCall<br/>contract and global policies"}
H -- pass --> K["Execute contract logic"]
H -- violation --> L["Revert"]
K --> T{"PCL postCall"}
T -- pass --> U["Return result"]
T -- violation --> L
end
subgraph CFG["PCL configuration"]
M["Policy admin"] -- manages --> N["Policy templates"]
M -- manages --> O["Global policy config"]
P["Contract policy admin"] -- manages --> Q["Contract policy config"]
N --> O
N --> Q
end
classDef evm fill:#0096AA,stroke:#0096AA,color:#fff;
classDef precompile fill:#FF8C50,stroke:#FF8C50,color:#fff;
class A,D,E,F,S,G,K,L,U,M,P evm;
class B,R,H,T,N,O,Q precompile; PCL runs its global checks in the chain's pre-execution checks, before execution, and again in a post-transaction hook after execution. It integrates into the EVM via a PCL proxy's preCall/postCall hooks for contract-specific checks. All policy configurations are stored on-chain and managed by designated admin accounts.
Policy enforcement flow
Every transaction first goes through the chain's pre-execution checks (the node's JSON-RPC runs the same PCL check when a transaction is submitted). PCL takes part in those checks by running the active Global Policies for the transaction's function selector (and those set for every selector) against the transaction's own call — sender, recipient, value, selector. If any global policy fails at this stage, the transaction is rejected before it executes and pays no gas.
Contract-specific Policies run during execution, and only when a call goes through a PCL-registered proxy or a trusted ERC-4337 EntryPoint calls
preCall / postCall for a user operation (the Privacy precompile, a policy-aware precompile, evaluates its own): the proxy calls PCL's preCall before running the implementation and postCall after it. A call made straight to a contract without a PCL proxy is checked only against the global policies, so that call gets no contract-scoped enforcement. After every successful EVM transaction, the chain runs the global policies again over the sender and every call the transaction made; a violation found there fails the transaction — its state is reverted, but it is still mined and its gas is charged. Together — global policies before and after execution, contract-scoped policies on the proxy hook path — these give consistent enforcement across the entire network.Policy Templates and Configuration
PolicyTemplate is a blueprint for a type of rule — its canonical Solidity struct in IPcl.sol declares the parameter layout for instances. Maroo comes with seven built-in templates (EAS_POLICY, DENYLIST_POLICY, VOLUME_POLICY, PERIODIC_VOLUME_POLICY, AGENT_OKRW_TRANSFER_LIMIT_POLICY, LOGICAL_POLICY, FOR_EACH_POLICY).The
PolicyAdmin, a privileged account set in the module's parameters (controllable by governance), is responsible for registering and managing these templates.To activate a rule, an administrator creates a
PolicySet — a { templateId, policy, selector } tuple where policy is the ABI-encoded parameter struct for that template (NOT JSON). For example, a denylist is a PolicySet with templateId = "DENYLIST_POLICY", policy = abi.encode(DenylistPolicy({ addresses: [...] })), and an empty selector (hex"" in Solidity, "0x" in viem), which applies it to all calls. PolicySet objects are then grouped into a GlobalPolicyConfig (chain-wide) or ContractPolicyConfig (per-contract) to be applied.