IPcl.preCall
preCall(address contractAddress, address principal, bytes calldata data, uint256 value) external returns (bytes32 sessionId) Opens a PCL policy-evaluation session for a proxied call. Invoked by the PCL-wrapped proxy hook before the proxy delegatecalls its implementation. preCall evaluates both the contract-scoped ContractPolicyConfig (if one exists for contractAddress) and the chain-wide GlobalPolicyConfig against the supplied principal / data / value, snapshots the pre-call state needed for accumulation-style policies (e.g. periodic volume), and returns a sessionId that the matching postCall must present. If any policy rejects, the call reverts with the corresponding PCL ReasonCode.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
contractAddress | address | ✓ | The proxy address used as the PCL policy-lookup key. Registered ContractPolicyConfig entries are matched by this address. |
principal | address | ✓ | The actual originator of the call (EOA or AA smart account) as observed by the proxy. Forwarded explicitly because the immediate msg.sender seen by PCL is the proxy itself; without this argument, sender / principal denylist checks would not fire. |
data | bytes | ✓ | The original inner calldata forwarded to the implementation. Its first four bytes (when present) are extracted as the function selector and matched against selector-scoped PolicySets. |
value | uint256 | ✓ | The native value (in aokrw) attached to the call. Feeds into volume-based policies. |
Returns
bytes32 A session identifier that the caller must pass back to postCall after the delegatecall completes. The session holds the pre-call log offset and native-transfer collector needed to evaluate accumulation policies at post-call time.
Errors
| Code | Name | Description |
|---|---|---|
Unauthorized | Unauthorized | Reverts when the immediate caller is not an allowed PCL proxy entrypoint (only PCL-registered proxies may open sessions). |
InDenylist | InDenylist | Reverts when the principal (or the target contract) matches an active DENYLIST_POLICY at global or contract scope. |
EasAttestationRequired | EasAttestationRequired | Reverts when an EAS_POLICY requires an attestation the principal does not hold. |
ExceededPeriodicVolume | ExceededPeriodicVolume | Reverts when a periodic-volume policy's accumulated amount plus value exceeds the configured cap. |
Examples
Invoked by the PCL proxy hook
The canonical caller of preCall is the PCL proxy hook. It captures the returned sessionId and pairs it with a matching postCall after the delegatecall. External dApps interact with the proxy directly; the hook wiring is invisible to them.
// Excerpt from PclProxyHook.sol — external dApps do NOT call preCall directly;
// the PCL-wrapped proxy calls it before delegatecalling its implementation.
address constant PCL = 0x1000000000000000000000000000000000000005;
function hookedDelegate(address addrForPolicy, address impl) internal {
address principal = msg.sender;
bytes32 sessionId = IPcl(PCL).preCall(
addrForPolicy, // proxy address = PCL policy key
principal, // actual EOA / AA caller
msg.data,
msg.value
);
(bool ok, bytes memory ret) = impl.delegatecall(msg.data);
IPcl(PCL).postCall(sessionId, addrForPolicy, principal, msg.data, msg.value, ok);
if (!ok) { assembly { revert(add(ret, 0x20), mload(ret)) } }
assembly { return(add(ret, 0x20), mload(ret)) }
} Decoding a PCL revert from a proxy call
When preCall rejects, the revert bubbles through the proxy to the client. Decode with the IPcl ABI to surface the ReasonCode.
import { createWalletClient, decodeErrorResult, http } from "viem";
try {
await walletClient.writeContract({
address: pclProxyAddress,
abi: myTokenAbi,
functionName: "transfer",
args: [recipient, 10_000_000n * 10n ** 18n], // 10,000,000 OKRW
});
} catch (err: any) {
const decoded = decodeErrorResult({ abi: pclAbi, data: err.data });
console.log("PCL ReasonCode:", decoded.errorName, decoded.args);
// e.g. EasAttestationRequired { sender: 0x... }
}