PCL ReasonCodes

mechanism compliance

The complete set of typed errors PCL emits when it rejects a transaction, plus the shared IPrecompile errors it now inherits. Wallets and SDKs key off these codes to drive UX.

Every PCL rejection carries one of the typed Solidity errors declared on IPcl — or, since IPcl now inherits from IPrecompile, one of the shared boundary errors. Wallet and dApp code should decode the revert payload against the IPcl ABI (which transitively includes the IPrecompile errors) and drive UX off the error name plus arguments — never off a free-form string. The codes break into four groups: policy-violation codes (the user's transaction failed a compliance rule), configuration codes (an admin call was malformed or unauthorized), composite / structural codes (a LogicalPolicy or ForEachPolicy combinator rejected), and the inherited boundary errors (invalid address, wrong argument count, unknown method, SDK-level rejections). A small number of failures are still plain-string reverts and are not decodable as typed errors — those are called out explicitly.

Policy-violation ReasonCodes

These fire when a user's transaction reaches a leaf policy and that policy rejects. They are the codes wallet UX cares about most — they tell the end user why their payment was blocked.

ErrorArgumentsMeaning
InDenylistaddress senderSender (or a resolved principal) is on the denylist.
VolumeBelowMinLimituint256 minLimit, uint256 valueTransfer amount is below the per-transaction floor.
VolumeAboveMaxLimituint256 maxLimit, uint256 valueTransfer amount is above the per-transaction cap.
ExceededPeriodicVolumeuint256 maxLimit, uint256 value, uint256 resetAtRolling-window cap exceeded; resetAt is when the window resets.
EasAttestationRequiredaddress senderPolicy requires an attestation the sender does not have.
EasNoAttestationReceivedaddress senderIndexer returned no attestation for the sender under the configured schema.
EasAttestationLookupFailedaddress senderIndexer lookup itself failed.
EasAttestationRevokedaddress senderAttestation exists but was revoked.
EasAttestationExpiredaddress senderAttestation exists but is past its expirationTime.
ExceededAgentTransferLimituint256 maxLimit, uint256 valueAgent's on-chain TransferLimit metadata cap was exceeded.
AgentTransferLimitMetadataInvalidstring reasonAgent metadata is malformed.
AgentKeeperRequiredAgent policy evaluated but the agent module is not wired.

Configuration ReasonCodes

These fire on admin operations (registerPolicyTemplate, changeContractPolicies, setGlobalPolicies, etc.) when the payload is malformed or the caller is not authorized.

ErrorArgumentsMeaning
CannotEmptystring fieldA required field was left empty.
UnauthorizedCaller is not the required admin (policy admin, contract admin, or protected caller).
InternalErrorState encode / decode failed inside the module.
InvalidCallThe call context itself is invalid (e.g. deployPclProxy called without an EVM).
InvalidStructTypestring gotABI struct decoded to the wrong shape.
AbiDecodeFailedstring reasonRaw ABI decode failed (initData malformed, etc.).
InvalidParameterbytes inputParameter bytes did not match the template's expected struct. Also raised for duplicate selectors — the payload is the raw selector bytes.
InvalidSelectorbytes inputSelector is not a 4-byte value.
InvalidPolicyTemplatestring inputTemplate ID string is not a recognized template.
DuplicatedPolicyTemplatestring templateIdRegistering a template that already exists.
PolicyTemplateNotFoundstring templateIdTemplate ID is not registered.
PolicyTemplateInUseAttempted to remove a template that is still referenced by an active PolicySet.
UnknownPolicyTypestring templateIdTemplate ID is not one this build knows how to evaluate.
UnknownPolicyConfigTypeEnclosing config type is neither Global nor Contract.
PolicyAlreadyRegisteredaddress contractAddressContractPolicyConfig already exists for this address on a create path.
ContractPolicyNotRegisteredaddress contractAddressNo ContractPolicyConfig for this address on an update / remove path.
PclProxyNotRegisteredaddress contractAddressAddress is not a registered PCL-wrapped proxy.
PolicyNotRegisteredstring templateIdTemplate referenced by a PolicySet is not registered.

Composite and structural ReasonCodes

LogicalPolicy and ForEachPolicy combinators (see pcl-composite-policies) can fail with structural errors that describe how a combinator rejected — most importantly AnyOfRejected, which recursively wraps the child reverts.

ErrorArgumentsMeaning
AnyOfRejectedbytes[] childRevertsAn Or combinator's children all rejected; each child revert is preserved as raw ABI bytes so clients can decode them individually.
MaxDepthExceededuint8 maxDepthCombinator nesting exceeded the structural depth limit.
ChildSelectorNotEmptyA combinator's child PolicySet must carry an empty selector; a non-empty one was provided.
LogicalPolicyChildrenEmptyLogicalPolicy.children array was empty.
LogicalPolicyChildNiluint256 indexA child at the given index decoded to a nil PolicySet.
ForEachChildAbsentForEachPolicy.child was not set.
ForEachSubjectUnspecifiedForEachPolicy.subject was Unspecified.
UnknownForEachSubjectuint8 subjectSubject value is not a known ForEachSubject.
QuantifierUnspecifiedA combinator's quantifier was Unspecified.

When decoding AnyOfRejected, iterate the childReverts array and re-decode each entry against the IPcl ABI to surface the underlying leaf errors.

Inherited IPrecompile errors

IPcl now inherits from IPrecompile, so every shared boundary error is decodable against the same ABI. These fire before any policy code runs — they describe malformed inputs at the precompile edge or SDK-level rejections.

ErrorArgumentsWhen it fires
InvalidAddressstring badAn address argument is the zero address or fails to encode. Note the payload is now a string (the address rendered as hex), not the previous locally-declared string input — the selector is the IPrecompile one.
InvalidAmountstring amountA numeric amount is zero or otherwise invalid.
InvalidNumberOfArgsuint256 expected, uint256 gotPrecompile received the wrong number of ABI arguments — most commonly a stale ABI.
UnknownMethodstring methodNameSelector does not match any function on the precompile.
InvalidPageRequeststring method, uint256 index, string valueA PageRequest argument (in the periodic-list views) is malformed.
SDKUnauthorized / SDKInvalidAddress / SDKInvalidRequest / SDKNotFound / …Underlying chain-layer rejection surfaced through the mapped SDK error catalog.

Any client that previously matched only the local InvalidAddress(string input) shape on IPcl must update its ABI fragment to the IPrecompile InvalidAddress(string bad) — the argument name changed and the declaring interface moved, though the 4-byte selector for InvalidAddress(string) is unchanged.
import { decodeErrorResult } from "viem";
import IPclAbi from "@maroo-chain/contracts/abi/IPcl.json";

try {
  await wallet.writeContract({ /* ... call PCL ... */ });
} catch (err: any) {
  if (!err?.data) throw err;
  const decoded = decodeErrorResult({ abi: IPclAbi, data: err.data });
  // decoded.errorName is one of the IPcl reason codes OR an inherited
  // IPrecompile error (InvalidAddress, InvalidNumberOfArgs, UnknownMethod, ...).
  switch (decoded.errorName) {
    case "InDenylist":
    case "ExceededPeriodicVolume":
    case "EasAttestationRevoked":
      // policy-violation UX
      break;
    case "Unauthorized":
    case "PolicyTemplateNotFound":
      // admin / configuration UX
      break;
    case "AnyOfRejected":
      // recursively decode decoded.args[0]: bytes[]
      break;
    case "InvalidAddress":
    case "InvalidNumberOfArgs":
    case "UnknownMethod":
      // boundary error — usually a stale ABI or wrong argument type
      break;
    default:
      console.warn("unhandled PCL revert:", decoded.errorName, decoded.args);
  }
}

Failures that are NOT typed errors

A small number of PCL failures still revert with a plain string reason and are not decodable with decodeErrorResult. Match them as string reverts instead.

  • duplicate selector: <selector> — two PolicySet entries in the same config share a selector. (Reported through the InvalidParameter selector at the ABI layer today, but historical clients may still surface the string form; decode both.)
  • pcl keeper is not initialized — the module is not wired on this build; indicates a misconfigured node rather than a user error.
  • pcl: parse IPcl.json: <err> — module init failure; will never surface to a live dApp.


All other failures listed on this page are typed errors and should be decoded against the IPcl ABI.
ESC
Type to search