Policy & Compliance
Programmable Compliance Layer (PCL) — covers policy structure, templates, enforcement, and precompile interfaces.
Compliance
Maroo processes transactions through two distinct paths: the Open Path for standard, low-risk activities, and the Regulated Path for high-value or compliant asset transfers. The PCL automatically determines the correct path.
Regulations change. Travel-Rule thresholds shift, sanctions lists update, jurisdictional rules evolve. Maroo separates the policy engine (PCL — stable, rarely changes) from the parameter supply (Legal Oracle — updated as laws and regulations evolve). New regulatory requirements are absorbed by adding a parameter — or, at most, registering a new PolicyTemplate — without touching chain core code. The Legal Oracle's authority to update parameters is itself governance-controlled — every change is visible on-chain and auditable.
LogicalPolicy and ForEachPolicy are structural policy templates that combine other policies rather than enforcing a single rule directly. LogicalPolicy bundles children under an AND or OR quantifier; ForEachPolicy fans a single child across a subject set such as the agent owners resolved for the transaction's caller. Each child produces one of three stage results — pass, fail, or skip — and the composite's reduction behavior is defined precisely over these outcomes. Understanding the pass / skip distinction matters when a child policy is inapplicable (e.g., a VOLUME_POLICY whose asset does not match the transaction's asset).
Every ContractPolicyConfig on PCL has an admin field: the only address allowed to later call changeContractPolicies or removeContractPolicies on that contract. This admin slot is bound once per contract address. There are two ways it gets bound: (1) automatically, when the contract is deployed via IPcl.deployPclProxy — the EVM msg.sender of the deploy call is written as admin before the deploy returns; (2) manually, on the legacy path, by the first successful call to registerContractPolicies (now deprecated — prefer changeContractPolicies once the slot is bound). Once written, the slot is anti-takeover: an unrelated caller cannot overwrite it. Handover only happens through changeContractPolicies where the current admin sets a new admin in the config.
Maroo's PCL exposes two execution contexts for smart-contract interactions. The 'Open Track' is a standard, direct EVM call that bypasses contract-scoped policy checks (global policies still apply). The 'Regulated Track' is an opt-in flow — either IPcl.runOnPcl(...) or a PCL-wrapped proxy — which enforces every applicable global and contract-scoped policy before executing the wrapped call. Within the regulated frame, PCL also observes inner transfers emitted by the target contract and attributes them back to the original caller for sender-scoped global checks (e.g., EAS attestation) so that a contract-funded payout still requires the principal to satisfy the sender's compliance requirements.
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 intercepts transactions before they are processed, validating them against a set of global and contract-specific rules. 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.
The Policy Admin is one address (visible to callers via IPcl.policyAdmin()) authorized to register/remove built-in PolicyTemplates and to set/remove the chain-wide GlobalPolicyConfig. Anything that affects every transaction on the network goes through this role. The admin slot is a chain parameter — a single keyholder can't claim or transfer it on their own.
Every Maroo transaction is filtered through PCL before any state-changing work runs. Global policies (a GlobalPolicyConfig set by the policy admin) are evaluated for all transactions pre-execution, before they are included in a block. Contract-scoped policies (a ContractPolicyConfig registered by a contract admin) only apply when a transaction targets that contract through the regulated EVM call path (runOnPcl / preCall + postCall). ERC-4337 userOps are unwrapped so PCL sees the principal (the smart account whose UserOperation is being bundled), not just the bundler.
PCL stores compliance rules as Solidity-defined ABI tuples, not as JSON objects. The hierarchy has three tiers: a PolicyTemplate (the type of rule, registered by the policy admin) is instantiated as a PolicySet (the type ID plus an ABI-encoded parameters blob plus an optional function selector) and bundled into a PolicyConfig (either the global config or a per-contract config). Earlier PCL drafts called the middle tier PolicyRef; the canonical name in the IPcl interface is PolicySet (with its policy field being the ABI-encoded parameter bytes).
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 Programmable Compliance Layer (PCL) precompile lives at the fixed address 0x1000000000000000000000000000000000000005 and is the canonical surface for everything compliance-related: registering policy templates, registering per-contract PolicySets, querying current rules, and executing a call under the regulated path via runOnPcl. Its module parameters are now reachable through a single getParams() view that returns a PclParams struct containing the policyAdmin (the chain-wide admin authorized to register templates and global policies) and entrypoints (the ERC-4337 EntryPoint addresses PCL trusts when recovering the real principal behind account-abstraction calls).
The PCL-wrapped proxy is a TransparentUpgradeableProxy variant deployed exclusively via IPcl.deployPclProxy(...). Its address becomes the PCL policy lookup key: any ContractPolicyConfig registered against the proxy applies uniformly across implementation upgrades. On every non-admin call, the proxy's fallback opens a PCL session via preCall, delegatecalls the current implementation with the original calldata, then closes the session via postCall — surfacing any PCL rejection as a revert. The only admin-privileged path is upgradeToAndCall; any other call from the proxy admin reverts with ProxyDeniedAdminAccess.
Every PCL rejection carries one of the typed Solidity errors defined on IPcl. Wallet and dApp code should decode the revert payload against the IPcl ABI and key UX off the error name plus arguments — never off a free-form string. Codes break into three groups: policy-violation codes (a compliance rule failed), configuration codes (bad template id, malformed parameters, structural constraint broken), and infrastructure codes (unauthorized caller, ABI decode failure).
Caps a single OKRW transfer made by an agent wallet using a limit value stored as on-chain metadata on the ERC-8004 IdentityRegistry — specifically getMetadata(agentId, "TransferLimit"). The policy struct itself has no configurable fields; the cap is whatever the agent's owner has put in metadata. Skipped for non-agent senders. Used to bound how much value an agent (an automated key) can move per transaction without holding it back from human-controlled accounts.
DENYLIST_POLICY is the simplest built-in PCL policy template: it carries a list of addresses that are not allowed to originate value-moving transactions. PCL evaluates the principal of each call — the EOA that signed a normal transaction, or the smart-account sender field of an ERC-4337 userOp — against the list. When the principal matches, the transaction is rejected before execution with the InDenylist reason code. The list can be attached as a global rule via GlobalPolicyConfig (chain-wide, admin-managed) or as a contract-scoped rule via ContractPolicyConfig.
Gates a transaction on whether the sender holds a valid (non-expired, non-revoked) attestation issued under a specific EAS schema. The canonical primitive for KYC, KYB, accreditation, and any credential-based gate. Resolves attestations through the EAS precompile so lookups stay cheap during AnteHandler evaluation.
Combines PERIODIC_VOLUME_POLICY's reset-period cumulative tracking with EAS attestation conditioning, giving un-attested senders a strict daily/monthly OKRW cap and exempting attested senders entirely from this template's accounting. The standard primitive for Travel-Rule-style thresholds.
Combines the EAS attestation check with a per-transaction OKRW cap that only fires for un-attested senders. Attested senders are admitted regardless of amount (subject to whatever other policies apply). The most common production pattern for KYC-tiered access — un-attested users can transact small amounts; attested users get unrestricted (or otherwise-bounded) transfers.
Tracks cumulative transaction volume per sender per denom over a configured reset period and rejects transactions that would push the running total above the limit. Distinct from VOLUME_POLICY, which checks each transaction independently. Used for daily / monthly transfer caps and Travel-Rule–style thresholds.
Enforces per-transaction min/max amount limits for one or more token denoms. Each transaction is evaluated independently against the limits — no rolling window, no cumulative tracking. For period-based cumulative limits, see PERIODIC_VOLUME_POLICY.
Maroo is a permissionless chain where anyone can create a wallet. However, it dynamically routes transactions through a Regulated Path or an Open Path based on counterparty identity, transaction size, and asset type.
Hacks, phishing, and clearly illegal fund flows happen in real-world payment systems. Maroo provides three bounded recovery primitives — freeze, burn, reissue — that correct state without rolling back the chain. All three operate only when there's a documented legal basis and the prescribed procedure has been followed; every invocation is logged to Observer Nodes and the governance audit trail. These are not always-on intervention tools, and Maroo prefers forward correction (new transactions that override prior state) over backward rewriting wherever the legal basis allows.