PCL Precompile

component compliance

On-chain compliance enforcement surface at 0x1000…0005. Now exposes getParams() returning both policyAdmin and the regulated entrypoints list.

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 dry-running an EVM call against policies 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 contract addresses that, when called, route a transaction through the regulated EVM path).

Architecture

graph TD
    subgraph EVM Space
        A[DApp / User] -- EVM Call --> B{Your Smart Contract};
        B -- `runOnPcl(target, data, value)` --> C[PCL Precompile @ 0x10...05];
    end

    subgraph Maroo Native Layer
        D[x/pcl module];
        E[Policy Storage];
        F[Compliance Logic];
    end

    C -- Native Bridge --> D;
    D -- Reads --> E[Policy Storage];
    D -- Executes --> F[Compliance Logic];
    F -- Pass/Fail --> D;
    D -- Result --> C;
    C -- Returns / Reverts --> B;
    B -- `(bool success, bytes memory result) = target.call(data)` --> G{Execute Original Call};
    G -- Returns --> B;
    B -- Returns --> A;

A user's call to a compliant smart contract is intercepted and forwarded to the PCL Precompile. The precompile bridges the call to the native x/pcl module, which loads relevant policies and executes compliance logic. If the checks pass, control is returned to the EVM to execute the original intended call.

Surface area

IPcl.sol exports a PCL_CONTRACT constant so callers don't hard-code the address. The relevant types:
import { IPcl, PclParams, PCL_CONTRACT } from "@maroo-chain/contracts/precompiles/pcl/IPcl.sol";

struct PclParams {
    address   policyAdmin;
    address[] entrypoints;
}

// Discovery
function getParams() external view returns (PclParams memory);
function policyAdmin() external view returns (address);

// Templates
function policyTemplate(string calldata templateId) external view returns (PolicyTemplate memory);
function registerPolicyTemplate(string calldata templateId) external;

// Contract policies
function registerContractPolicies(ContractPolicyConfig calldata cfg) external;
function changeContractPolicies(ContractPolicyConfig calldata cfg) external;
function removeContractPolicies(address _contract) external;
function contractPolicies(address _contract) external view returns (ContractPolicyConfig memory);

// Dry-run
function runOnPcl(address contractAddress, bytes calldata data, uint256 value) external returns (bytes memory);

`getParams` vs `policyAdmin`

Both getParams() and the older policyAdmin() view return the same admin address. Prefer getParams() for new code: it's a single call that also returns the entrypoints array, which a dApp needs to know whether a given call target will be evaluated under the regulated path. policyAdmin() remains as a compact accessor.
PclParams memory p = PCL_CONTRACT.getParams();
address admin            = p.policyAdmin;
address[] memory routes  = p.entrypoints;

What `entrypoints` does

PCL distinguishes two transaction tracks (see pcl-dual-track-model). The entrypoints array is the chain-configured list of contract addresses recognized as regulated entrypoints: when a transaction's to matches one of these addresses, the call is routed through the regulated EVM path and any ContractPolicyConfig attached downstream is evaluated. Calls to addresses not in the list still pass through the AnteHandler (global policies) but skip the contract-policy phase. dApps that integrate compliance can read this list to warn users when a target won't trigger ContractPolicyConfig checks.
import { createPublicClient, http } from "viem";

const pcl = await client.readContract({
  address: "0x1000000000000000000000000000000000000005",
  abi: pclAbi,
  functionName: "getParams",
});

const isRegulated = pcl.entrypoints
  .map((a: string) => a.toLowerCase())
  .includes(callTarget.toLowerCase());

if (!isRegulated) {
  console.warn("Target is not a regulated entrypoint; ContractPolicyConfig will be skipped.");
}

Who calls what

Only the policyAdmin can call registerPolicyTemplate and the global-policy mutators. Per-contract policy mutators (registerContractPolicies, changeContractPolicies, removeContractPolicies) are gated by the admin field inside each ContractPolicyConfig — that key is set by the contract owner at registration time, independent of the chain-wide admin. External dApps register their own contract policies through this second path.

Read paths are free

getParams, policyAdmin, policyTemplate, and contractPolicies are pure view methods — no gas-significant work, no state changes, safe to call from off-chain indexers or front-end load paths. Use runOnPcl for the heavier dry-run case when you need to know whether a specific encoded call would be admitted.
Source: maroo
ESC
Type to search