IPcl.preCall

preCall(
  address contractAddress,
  address principal,
  bytes calldata data,
  uint256 value
) external returns (bytes32 sessionId)

Opens a PCL enforcement session for a regulated call. The immediate msg.sender must be one of two authorized caller kinds: a PCL-registered proxy invoking its own target (contractAddress == msg.sender), or a governance-trusted ERC-4337 EntryPoint executing a bundle. In both cases PCL evaluates global and contract-scoped policies against the forwarded principal (the EOA or smart-account that originated the call) with the extracted 4-byte selector and value. If any before-execution policy fails the call reverts with the corresponding PCL ReasonCode; otherwise PCL installs an internal tracer, registers a session keyed by a derived sessionId, and returns it. The paired postCall invocation must present the same sessionId to close the session and run after-execution evaluation. A staticcall (readonly) execution returns the zero bytes32 and does not open a session — policy state is evaluated inside a cached context and discarded.

Parameters

Name Type Required Description
contractAddress address The target contract of the regulated call. When the caller is a PCL proxy this MUST equal msg.sender — otherwise PCL reverts with Unauthorized. When the caller is a trusted EntryPoint this constraint is relaxed because the EntryPoint dispatches into the SmartAccount which then calls the target.
principal address The semantically relevant caller — the EOA or ERC-4337 smart-account address that originated the call. The proxy/EntryPoint must forward this value verbatim; PCL uses it as the sender for contract-scope denylist and periodic-volume checks. If PCL only saw the immediate caller (proxy or EntryPoint) sender-side rules could never match a real user.
data bytes The calldata being delegated. PCL slices the leading 4 bytes as the function selector for selector-scoped policies. Data shorter than 4 bytes is treated as no-selector (empty selector).
value uint256 Native OKRW value attached to the underlying call, in aokrw. Non-zero values feed into VOLUME_POLICY / PERIODIC_VOLUME_POLICY evaluation on the native denom.

Returns

Type: bytes32

A session identifier derived from the call metadata plus a per-transaction sequence. The paired postCall must echo this value. For staticcall, returns the zero hash.

Errors

Code Name Description
Unauthorized Unauthorized Reverts when the immediate caller is neither a registered PCL proxy nor a trusted EntryPoint, or when a PCL-proxy caller passes a contractAddress different from its own address.
InDenylist InDenylist Reverts when the resolved principal (or the target contract) matches a denylist entry in either the contract or global policy config.
VolumeAboveMaxLimit VolumeAboveMaxLimit Reverts when the attached native value alone exceeds a VOLUME_POLICY maximum bound.
ExceededPeriodicVolume ExceededPeriodicVolume Reverts when the attached native value would push the principal over a PERIODIC_VOLUME_POLICY cap for the current window.
EasAttestationRequired EasAttestationRequired Reverts when an EAS_POLICY-bound target requires an attestation on the resolved principal and none is present.

Examples

PCL proxy invoking preCall for its own target

Classic PCL proxy hook path. The proxy's own address must match contractAddress; the human user is forwarded as principal.

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.22;

import {IPcl, PCL_CONTRACT} from "@maroo-chain/contracts/precompiles/pcl/IPcl.sol";

contract RegulatedProxy {
    address internal immutable implementation;

    constructor(address impl) { implementation = impl; }

    /// @notice A PCL-wrapped proxy dispatches user calls through preCall/postCall.
    ///         msg.sender at the PCL precompile will be `address(this)` — the proxy itself.
    function forward(bytes calldata data) external payable returns (bytes memory) {
        // The proxy passes itself as `contractAddress` (must equal msg.sender at the precompile),
        // and the real caller as `principal`.
        bytes32 sessionId = PCL_CONTRACT.preCall(address(this), msg.sender, data, msg.value);

        (bool ok, bytes memory ret) =
            implementation.delegatecall(data);

        // Any implementation revert is surfaced to postCall via `workable=false`.
        PCL_CONTRACT.postCall(sessionId, address(this), msg.sender, data, msg.value, ok);

        require(ok, "impl reverted");
        return ret;
    }
}

Decoding preCall reverts on the client

Every preCall failure is a typed error on IPcl. Decode with the interface ABI to branch UX on the specific ReasonCode.

import { createPublicClient, http, decodeErrorResult } from "viem";

const PCL = "0x1000000000000000000000000000000000000005" as const;
const pclAbi = [
  { type: "function", name: "preCall", stateMutability: "nonpayable",
    inputs: [
      { name: "contractAddress", type: "address" },
      { name: "principal", type: "address" },
      { name: "data", type: "bytes" },
      { name: "value", type: "uint256" },
    ], outputs: [{ type: "bytes32" }] },
  { type: "error", name: "Unauthorized", inputs: [] },
  { type: "error", name: "InDenylist", inputs: [{ name: "sender", type: "address" }] },
  { type: "error", name: "ExceededPeriodicVolume",
    inputs: [
      { name: "maxLimit", type: "uint256" },
      { name: "value", type: "uint256" },
      { name: "resetAt", type: "uint256" },
    ] },
] as const;

const publicClient = createPublicClient({ transport: http("https://rpc-testnet.maroo.io") });

try {
  await publicClient.simulateContract({
    address: "0x8f3aC2b1D9e74C05a6B18Fe27dC4913E5A0f7b62", // TODO: replace with real proxy address
    abi: [/* proxy ABI */],
    functionName: "forward",
    args: ["0x"],
  });
} catch (err: any) {
  if (err?.data) {
    const decoded = decodeErrorResult({ abi: pclAbi, data: err.data });
    console.error("PCL preCall rejected:", decoded.errorName, decoded.args);
  }
}
ESC
Type to search