PCL Precompile
EVM surface for the Programmable Compliance Layer at 0x1000…0005. Read the policy graph, register or change templates and configs, deploy PCL-wrapped proxies, and expose the preCall/postCall hook path that regulated proxies use around every call.
The PCL Precompile is a stateful precompile at a fixed EVM address that bridges Solidity code to Maroo's compliance engine. It exposes read views (getParams, globalPolicies, contractPolicies, policyTemplate, periodic-volume queries) and admin-guarded writes (registerPolicyTemplate, setGlobalPolicies, changeContractPolicies, and their remove counterparts). It also owns the regulated execution path: deployPclProxy deploys a canonical PCL-wrapped proxy whose hook path calls preCall before, and postCall after, the underlying execution — the only mechanism through which contract-scoped policies fire.
Architecture
flowchart LR
User[EOA / dApp]:::evm --> Proxy[PCL Proxy<br/>Transparent or UUPS]:::evm
Proxy --> Pre[IPcl.preCall]:::precompile
Pre --> Logic[Logic Contract]:::evm
Logic --> Post[IPcl.postCall]:::precompile
Post --> Result[Return / Revert<br/>with ReasonCode]:::evm
Admin[Contract Admin]:::evm -.-> ChangeCfg[IPcl.changeContractPolicies]:::precompile
Reader[Any address]:::evm -.-> Views[IPcl views<br/>contractPolicies / globalPolicies / policyTemplate]:::precompile
classDef evm fill:#0096AA,stroke:#0096AA,color:#fff;
classDef precompile fill:#FF8C50,stroke:#FF8C50,color:#fff; Regulated execution flows through a PCL proxy that brackets each call with `preCall` and `postCall`. Contract policy configuration is done by the contract admin via `changeContractPolicies`; policy-graph views are open to any reader.
Address and interface
IPcl in @maroo-chain/contracts; the constant PCL_PRECOMPILED_ADDRESS in that package pins the address so contracts do not hard-code it.// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.28;
import {IPcl, PCL_CONTRACT, PCL_PRECOMPILED_ADDRESS} from
"@maroo-chain/contracts/precompiles/pcl/IPcl.sol";
contract Example {
IPcl constant pcl = PCL_CONTRACT; // 0x1000…0005
address constant PCL = PCL_PRECOMPILED_ADDRESS; // same address as a raw literal
} Method groups
- Params & templates —
getParams,policyAdmin,policyTemplate,registerPolicyTemplate,removePolicyTemplate. - Policy configuration —
globalPolicies,setGlobalPolicies,removeGlobalPolicies,contractPolicies,changeContractPolicies,removeContractPolicies. - Periodic-volume queries —
globalPeriodicVolume,contractPeriodicVolume,globalPeriodicList,contractPeriodicListfor reading current usage against periodic caps. - Regulated execution —
deployPclProxy,pclProxy, and the hook methodspreCall/postCallthat PCL-registered proxies call around every user call.
There is no single-call entry point that both checks policy and executes a target function on the PCL precompile itself. All contract-scoped enforcement flows through the proxy's hook path.
Enforcement points
1. Transaction entry — the global
GlobalPolicyConfig is evaluated for every EVM transaction, regardless of target.2. Proxy hook path — when a call targets a PCL-registered proxy (see
pcl-proxy-hook), the proxy calls IPcl.preCall(...) before the underlying execution and IPcl.postCall(...) after. This is where a proxy's ContractPolicyConfig fires.Calls that target a contract's implementation directly (bypassing the proxy) only see the global config, so publishing the proxy address as the user-facing entry point is what makes the regulated track effective.
Reading current state
eth_call. On-chain callers (a contract that reads PCL state during its own execution) pay normal gas.import { createPublicClient, http } from "viem";
const PCL = "0x1000000000000000000000000000000000000005" as const;
const pclAbi = [
{ type: "function", name: "policyAdmin", stateMutability: "view",
inputs: [], outputs: [{ type: "address" }] },
{ type: "function", name: "globalPolicies", stateMutability: "view",
inputs: [], outputs: [{ type: "tuple", components: [
{ name: "policies", type: "tuple[]", components: [
{ name: "templateId", type: "string" },
{ name: "policy", type: "bytes" },
{ name: "selector", type: "bytes" },
] },
] }] },
] as const;
const client = createPublicClient({ transport: http("https://rpc-testnet.maroo.io") });
const admin = await client.readContract({ address: PCL, abi: pclAbi, functionName: "policyAdmin" });
const global = await client.readContract({ address: PCL, abi: pclAbi, functionName: "globalPolicies" });
console.log("policy admin:", admin, "active global policies:", global.policies.length); Failure surface
IPcl. Common ReasonCodes: EasNoAttestationReceived, EasAttestationRevoked, EasAttestationExpired, InDenylist, ExceededPeriodicVolume, VolumeAboveMaxLimit, ExceededAgentTransferLimit. Configuration and admin-guard failures use Unauthorized, PolicyTemplateNotFound, ContractPolicyNotRegistered, and PclProxyNotRegistered. A duplicate selector inside a single ContractPolicyConfig reverts with the plain string duplicate selector: <selector> — this one is not a typed error, so decode it as a string.