PCL ReasonCodes
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
| Error | Arguments | Meaning |
|---|---|---|
InDenylist | address sender | Sender (or a resolved principal) is on the denylist. |
VolumeBelowMinLimit | uint256 minLimit, uint256 value | Transfer amount is below the per-transaction floor. |
VolumeAboveMaxLimit | uint256 maxLimit, uint256 value | Transfer amount is above the per-transaction cap. |
ExceededPeriodicVolume | uint256 maxLimit, uint256 value, uint256 resetAt | Rolling-window cap exceeded; resetAt is when the window resets. |
EasAttestationRequired | address sender | Policy requires an attestation the sender does not have. |
EasNoAttestationReceived | address sender | Indexer returned no attestation for the sender under the configured schema. |
EasAttestationLookupFailed | address sender | Indexer lookup itself failed. |
EasAttestationRevoked | address sender | Attestation exists but was revoked. |
EasAttestationExpired | address sender | Attestation exists but is past its expirationTime. |
ExceededAgentTransferLimit | uint256 maxLimit, uint256 value | Agent's on-chain TransferLimit metadata cap was exceeded. |
AgentTransferLimitMetadataInvalid | string reason | Agent metadata is malformed. |
AgentKeeperRequired | — | Agent policy evaluated but the agent module is not wired. |
Configuration ReasonCodes
registerPolicyTemplate, changeContractPolicies, setGlobalPolicies, etc.) when the payload is malformed or the caller is not authorized.| Error | Arguments | Meaning |
|---|---|---|
CannotEmpty | string field | A 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). |
InvalidStructType | string got | ABI struct decoded to the wrong shape. |
AbiDecodeFailed | string reason | Raw ABI decode failed (initData malformed, etc.). |
InvalidParameter | bytes input | Parameter bytes did not match the template's expected struct. Also raised for duplicate selectors — the payload is the raw selector bytes. |
InvalidSelector | bytes input | Selector is not a 4-byte value. |
InvalidPolicyTemplate | string input | Template ID string is not a recognized template. |
DuplicatedPolicyTemplate | string templateId | Registering a template that already exists. |
PolicyTemplateNotFound | string templateId | Template ID is not registered. |
PolicyTemplateInUse | — | Attempted to remove a template that is still referenced by an active PolicySet. |
UnknownPolicyType | string templateId | Template ID is not one this build knows how to evaluate. |
UnknownPolicyConfigType | — | Enclosing config type is neither Global nor Contract. |
PolicyAlreadyRegistered | address contractAddress | ContractPolicyConfig already exists for this address on a create path. |
ContractPolicyNotRegistered | address contractAddress | No ContractPolicyConfig for this address on an update / remove path. |
PclProxyNotRegistered | address contractAddress | Address is not a registered PCL-wrapped proxy. |
PolicyNotRegistered | string templateId | Template 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.| Error | Arguments | Meaning |
|---|---|---|
AnyOfRejected | bytes[] childReverts | An Or combinator's children all rejected; each child revert is preserved as raw ABI bytes so clients can decode them individually. |
MaxDepthExceeded | uint8 maxDepth | Combinator 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. |
LogicalPolicyChildNil | uint256 index | A child at the given index decoded to a nil PolicySet. |
ForEachChildAbsent | — | ForEachPolicy.child was not set. |
ForEachSubjectUnspecified | — | ForEachPolicy.subject was Unspecified. |
UnknownForEachSubject | uint8 subject | Subject 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.| Error | Arguments | When it fires |
|---|---|---|
InvalidAddress | string bad | An 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. |
InvalidAmount | string amount | A numeric amount is zero or otherwise invalid. |
InvalidNumberOfArgs | uint256 expected, uint256 got | Precompile received the wrong number of ABI arguments — most commonly a stale ABI. |
UnknownMethod | string methodName | Selector does not match any function on the precompile. |
InvalidPageRequest | string method, uint256 index, string value | A 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
decodeErrorResult. Match them as string reverts instead.duplicate selector: <selector>— two PolicySet entries in the same config share a selector. (Reported through theInvalidParameterselector 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.