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, calldata that does not decode, 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 senderAn address on the call route or in a transfer is on the denylist — the sender (or a resolved principal), or the recipient / call target. Despite its name, sender is whichever address matched.
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 resetAtPeriodic cap exceeded; resetAt is when the window resets. Windows are fixed, not rolling — aligned to multiples of resetPeriodSeconds since the Unix epoch, so a 24-hour window resets for everyone at 00:00 UTC.
EasAttestationRequiredaddress senderDeclared, but no policy raises it: EAS_POLICY reports a sender with no indexed attestation as EasNoAttestationReceived.
EasNoAttestationReceivedaddress senderIndexer returned no attestation for the sender under the configured schema.
EasAttestationLookupFailedaddress senderThe attestation the Indexer listed could not be read from the EAS contract the policy names (easContract) — check that address. A failed Indexer lookup is not reported as this error.
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.
AgentKeeperRequired—Agent 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.
Unauthorized—Caller is not the required admin (policy admin, contract admin, or protected caller).
InternalError—State encode / decode failed inside the module.
InvalidCall—The 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 a duplicate selector, with input set to that selector as text — its lowercase hex without 0x (for example the bytes of "a9059cbb"), or __all__ for the empty selector.
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 — raised only by the policyTemplate(templateId) view; admin operations report an unregistered template as PolicyNotRegistered.
PolicyTemplateInUse—Declared, but not raised: removePolicyTemplate does not check whether a PolicySet still references the template, so the removal succeeds. Until the template is registered again, every evaluation of a policy that uses it then fails with PolicyNotRegistered.
UnknownPolicyTypestring templateIdTemplate ID is not one this build knows how to evaluate.
UnknownPolicyConfigType—Enclosing 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.
ChildSelectorNotEmpty—A combinator's child PolicySet must carry an empty selector; a non-empty one was provided.
LogicalPolicyChildrenEmpty—LogicalPolicy.children array was empty.
LogicalPolicyChildNiluint256 indexA child at the given index decoded to a nil PolicySet.
ForEachChildAbsent—ForEachPolicy.child was not set.
ForEachSubjectUnspecified—ForEachPolicy.subject was Unspecified.
UnknownForEachSubjectuint8 subjectSubject value is not a known ForEachSubject.
QuantifierUnspecified—A 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 badA stored address string does not parse as a Bech32 address. In practice this happens only when a policy-admin method is called while the module's policyAdmin parameter is unset, so bad is the empty string; on the testnet policyAdmin is set. Address arguments arrive as 20-byte EVM addresses, so a zero address is not rejected with this error: removeContractPolicies(address(0)), for example, reverts with ContractPolicyNotRegistered. Note the argument is now string bad, not the previous locally-declared string input — the selector is the IPrecompile one.
InvalidAmountstring amountDeclared, but no PCL code path raises it.
ABISetupFailedstring reasonCalldata does not decode against IPcl's ABI: an unknown selector (reason no method with id: 0x…, most commonly a stale ABI) or arguments too short to decode (the ABI decoder's message). Bytes after the last argument are ignored.
QueryFailedstring queryMethod, string reasonA query is rejected, for example a PageRequest with both key and offset set in the periodic-list views.
InvalidPageRequeststring method, uint256 index, string valueDeclared, but a malformed PageRequest in the periodic-list views does not return it: both key and offset set reverts with QueryFailed, and a tuple that does not decode reverts with ABISetupFailed.
SDKUnauthorized / SDKInvalidAddress / SDKInvalidRequest / SDKNotFound / …—Underlying chain-layer rejection surfaced through the mapped SDK error catalog.

InvalidNumberOfArgs and UnknownMethod are declared in IPrecompile as well, but PCL does not raise them for a wrong selector or argument count.

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 { BaseError, ContractFunctionRevertedError, decodeErrorResult } from "viem";
import { iPclAbi } from "@maroo-chain/contracts/abi/precompiles/pcl/IPcl";

try {
  await wallet.writeContract({ /* ... call PCL ... */ });
} catch (err) {
  // viem keeps the revert data on the ContractFunctionRevertedError in the cause chain, not on err.
  const revert = err instanceof BaseError ? err.walk((e) => e instanceof ContractFunctionRevertedError) : null;
  if (!(revert instanceof ContractFunctionRevertedError) || !revert.raw) throw err;
  const decoded = decodeErrorResult({ abi: iPclAbi, data: revert.raw });
  // decoded.errorName is one of the IPcl reason codes OR an inherited
  // IPrecompile error (InvalidAddress, ABISetupFailed, QueryFailed, ...).
  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 "ABISetupFailed":
      // 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 revert with a plain string reason instead of a typed IPcl error. Where that string is ABI-encoded as Solidity's Error(string) — as deployPclProxy's is — viem's decodeErrorResult returns it as errorName: "Error" with the reason in args[0]; match on the reason string.

  • execution reverted — deployPclProxy could not construct the proxy (an initializer that reverts — or is empty, for a Transparent or UUPS proxy — or a logic address without code).
  • 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.


A duplicate selector is not one of them: it reverts with the typed InvalidParameter(bytes input) (see the configuration table above). All other failures listed on this page are typed errors and should be decoded against the IPcl ABI.
ESC
Type to search