{
  "schema_version": "1.0.0",
  "version": "1.2.0",
  "current_sdk_version": "6.0.24",
  "generated": "2026-09-01T00:00:00.000Z",
  "description": "Catalog of Push Chain SDK error conditions with detection signals, user-visible symptoms, machine recovery steps, and escalation guidance. Cross-reference with agents/recovery-playbook.md for step-by-step procedures.",
  "migration_note": "v1.0.0: root was a bare array. v1.0.0+ wraps in object — access via .errors[]. Change errors.json[N] → errors.json.errors[N].",
  "errors": [
    {
      "id": "missing_universal_signer",
      "name": "Missing Universal Signer",
      "description": "PushChain.initialize() was called without a valid UniversalSigner or UniversalAccount",
      "likely_cause": "Developer passed undefined, null, or an incompatible signer object to PushChain.initialize(). The signer was not created using PushChain.utils.signer.toUniversal() or toUniversalFromKeypair().",
      "detection_signal": "TypeError or validation error thrown by PushChain.initialize() indicating missing or invalid signer parameter. Error message typically contains 'signer' or 'account' reference.",
      "user_visible_symptom": "Application fails to initialize. User sees generic error or blank state. No wallet connection prompt appears.",
      "machine_recovery": [
        "Check if signer parameter is defined before calling initialize()",
        "Verify signer was created via PushChain.utils.signer.toUniversal() for EVM signers",
        "Verify signer was created via PushChain.utils.signer.toUniversalFromKeypair() for Solana keypairs",
        "If using UI Kit, ensure PushUniversalWalletProvider is properly wrapping the component tree"
      ],
      "escalation": "If signer creation succeeds but initialize() still fails, escalate to developer with full error stack trace and signer type used.",
      "confidence": "documented"
    },
    {
      "id": "invalid_network",
      "name": "Invalid Network Constant",
      "description": "An invalid or unrecognized PUSH_NETWORK constant was passed to initialize()",
      "likely_cause": "Developer passed a string literal instead of PushChain.CONSTANTS.PUSH_NETWORK.TESTNET or used an outdated/misspelled network constant.",
      "detection_signal": "Error thrown during initialize() referencing invalid network. Check if options.network matches one of PushChain.CONSTANTS.PUSH_NETWORK values.",
      "user_visible_symptom": "Application fails to connect to Push Chain. May show network error or initialization failure message.",
      "machine_recovery": [
        "Replace network string with PushChain.CONSTANTS.PUSH_NETWORK.TESTNET (for testnet)",
        "Verify SDK version supports the target network",
        "Check for typos in network constant access path"
      ],
      "escalation": "If using correct constant from current SDK version and still failing, report as potential SDK bug with SDK version and exact constant used.",
      "confidence": "documented"
    },
    {
      "id": "unsupported_chain",
      "name": "Unsupported Origin Chain",
      "description": "The signer's origin chain is not in Push Chain's supported chain list",
      "likely_cause": "Universal signer was created from a wallet connected to an unsupported network (e.g., a custom L2 not yet integrated with Push Chain).",
      "detection_signal": "Error during initialize() or sendTransaction() indicating chain ID or CAIP-10 chain identifier is not recognized. Check signer.account.chain against PushChain.CONSTANTS.CHAIN values.",
      "user_visible_symptom": "Wallet appears connected but transactions fail. Error message may mention unsupported chain or network.",
      "machine_recovery": [
        "List supported chains via Object.values(PushChain.CONSTANTS.CHAIN)",
        "Prompt user to switch wallet to a supported network (Ethereum Sepolia, Solana Devnet, etc.)",
        "If programmatic, reconnect signer with provider pointing to supported RPC"
      ],
      "escalation": "If user's required chain is not supported, escalate as feature request with chain ID and use case.",
      "confidence": "inferred"
    },
    {
      "id": "signature_mismatch",
      "name": "Signature Format Mismatch",
      "description": "The signer's signature output does not match expected format for the chain type",
      "likely_cause": "Custom signer implementation returns signature in wrong format. EVM expects hex string or Uint8Array, Solana expects Uint8Array. signMessage/signTypedData return type incorrect.",
      "detection_signal": "Signature verification fails during transaction submission. Error may reference signature length, encoding, or verification failure.",
      "user_visible_symptom": "Transaction submission fails after user signs. Error appears after wallet signature prompt completes.",
      "machine_recovery": [
        "Verify custom signer functions return Uint8Array as documented",
        "For EVM signers, ensure signature is 65 bytes (r + s + v)",
        "For Solana signers, ensure signature is 64 bytes",
        "Use PushChain.utils.signer.toUniversal() instead of custom implementation if possible"
      ],
      "escalation": "If using standard toUniversal() conversion and signature still fails, escalate with wallet type, library version, and raw signature output.",
      "confidence": "inferred"
    },
    {
      "id": "insufficient_fee_balance",
      "name": "Insufficient Fee Balance",
      "description": "The user's origin wallet does not have enough native tokens to cover gas fees",
      "likely_cause": "User's wallet on origin chain (ETH on Sepolia, SOL on Devnet) has insufficient balance for gas funding. Universal fee abstraction still requires native token payment on source chain.",
      "detection_signal": "Progress hook emits SEND-TX-05-01 followed by error. Error message references insufficient funds, balance, or gas.",
      "user_visible_symptom": "Transaction fails during gas funding step. User may see 'insufficient funds' or 'not enough ETH/SOL for gas' message.",
      "machine_recovery": [
        "Query origin chain balance before initiating transaction",
        "Display estimated gas cost to user before transaction",
        "Direct user to faucet if on testnet (https://push.org/docs/chain/setup/tooling/faucet/)",
        "Suggest user add funds to origin wallet"
      ],
      "escalation": "No escalation needed. User action required to fund wallet.",
      "confidence": "documented"
    },
    {
      "id": "insufficient_uea_balance",
      "name": "Insufficient UEA Balance (pre-flight)",
      "description": "Pre-flight check detected that the UEA cannot cover the required outbound cost (native PC or burn-token PRC-20), and the caller opted into strict enforcement via `tx.options.enforceGasCheck = true`.",
      "likely_cause": "The caller passed `options: { enforceGasCheck: true }` to `sendTransaction` or `prepareTransaction` on a route where the UEA's balance is below the SDK-computed `requiredFunds` (native PC) or below the PRC-20 amount the outbound leg needs to burn. With `enforceGasCheck` left at its default of `false`, the SDK only emits a `WARNING`-level progress event and proceeds.",
      "detection_signal": "Error name is `InsufficientUEABalanceError`. The error carries `required`, `available`, `shortfall`, `ueaAddress`, `pathTag` (`R2_EVM` / `R2_SVM` / `R3_EVM` / `R3_SVM` / `CASCADE`), and `reason` (`NATIVE` or `PRC20`); `PRC20` shortfalls also include `burnToken`. Cascade failures additionally expose `segmentIndex`. Progress hook fires `SEND-TX-103-04`-class events with `level: 'ERROR'` and `enforceGasCheck: true` in the response.",
      "user_visible_symptom": "Transaction never broadcasts. Error message reads `InsufficientUEABalance [<pathTag>:<reason>]: required <X> vs available <Y> on UEA <addr>`. No state change on Push Chain.",
      "machine_recovery": [
        "Surface `error.shortfall` to the user so they know exactly how much more native PC or PRC-20 is needed.",
        "If `reason === 'NATIVE'`, top up the UEA's PC balance (faucet on testnet, or a small native transfer) and retry.",
        "If `reason === 'PRC20'`, bridge or transfer additional `burnToken` into the UEA before retrying, or reduce the outbound `funds.amount`.",
        "For one-off best-effort flows, drop `options.enforceGasCheck` (or set it to `false`) so the SDK degrades to a warning and lets the existing fee-locking / refill paths recover.",
        "For cascades, inspect `error.segmentIndex` to identify the failing hop; consider splitting the cascade or pre-funding the UEA before the cascade starts."
      ],
      "escalation": "Not a bug; the error is the explicit signal that the caller asked for strict pre-flight. Only escalate if the reported `required` does not match what `prepareTransaction.estimatedGas` predicted, which would indicate a fee-estimation regression.",
      "confidence": "documented"
    },
    {
      "id": "invalid_tx_hash",
      "name": "Invalid Transaction Hash Format",
      "description": "Transaction hash provided to tracking function has invalid format",
      "likely_cause": "Hash is not a valid hex string, wrong length, or missing 0x prefix. May be passing Push Chain tx hash format to EVM tracker or vice versa.",
      "detection_signal": "Validation error thrown immediately when calling track/status function. Error references hash format or length.",
      "user_visible_symptom": "Transaction status lookup fails. User cannot view transaction details or confirmation status.",
      "machine_recovery": [
        "Validate hash is 66 characters for EVM (0x + 64 hex chars)",
        "Ensure hash has 0x prefix for EVM chains",
        "Use hash exactly as returned from sendTransaction() response",
        "Do not truncate or modify hash string"
      ],
      "escalation": "If hash returned from sendTransaction() itself causes this error, escalate as SDK bug.",
      "confidence": "inferred"
    },
    {
      "id": "tx_timeout",
      "name": "Transaction Tracking Timeout",
      "description": "Transaction confirmation monitoring exceeded timeout threshold",
      "likely_cause": "Network congestion, transaction stuck in mempool, or transaction dropped. May also indicate RPC endpoint issues preventing status updates.",
      "detection_signal": "Tracking promise rejects with timeout error. Progress hook stops emitting confirmation events.",
      "user_visible_symptom": "Transaction shows as pending indefinitely. User does not see success or failure confirmation.",
      "machine_recovery": [
        "Retry tracking with fresh RPC connection",
        "Check transaction on block explorer directly using pushChainClient.explorer.getTransactionUrl()",
        "If transaction not found on explorer after timeout, consider it dropped",
        "Allow user to retry original transaction"
      ],
      "escalation": "If timeouts are frequent and explorer shows transactions succeeding, escalate as RPC reliability issue.",
      "confidence": "inferred"
    },
    {
      "id": "tx_reverted",
      "name": "Transaction Execution Reverted",
      "description": "Transaction was mined but execution reverted at contract level",
      "likely_cause": "Smart contract require() or revert() triggered. Common causes: insufficient token balance, failed validation, access control, or reentrancy guard.",
      "detection_signal": "Progress hook emits SEND-TX-99-02 with error details. Transaction receipt shows status 0 or reverted flag.",
      "user_visible_symptom": "Transaction fails after confirmation. Gas is consumed but intended action did not complete.",
      "machine_recovery": [
        "Parse revert reason from error message if available",
        "Check contract input parameters match expected types and ranges",
        "Verify user has sufficient token approvals for ERC20 operations",
        "Simulate transaction before submission using eth_call or equivalent"
      ],
      "escalation": "If revert reason is unclear or contract behaves unexpectedly, escalate with transaction hash and contract address for debugging.",
      "confidence": "documented"
    },
    {
      "id": "rpc_unreachable",
      "name": "RPC Endpoint Unreachable",
      "description": "Cannot establish connection to RPC endpoint for Push Chain or origin chain",
      "likely_cause": "Network connectivity issues, RPC endpoint down, rate limiting, or firewall blocking. Custom RPC URL may be misconfigured.",
      "detection_signal": "Network error or timeout during any RPC call. Error references connection refused, timeout, or fetch failure.",
      "user_visible_symptom": "Application shows connection error. All blockchain operations fail. May appear as blank state or loading indefinitely.",
      "machine_recovery": [
        "Retry with exponential backoff (3 attempts, 1s/2s/4s delays)",
        "If custom rpcUrls provided, fall back to default endpoints",
        "For Push Chain testnet, verify https://evm.donut.rpc.push.org/ is accessible",
        "Check if user is behind corporate firewall or VPN blocking WebSocket/HTTP"
      ],
      "escalation": "If default Push Chain RPC is consistently unreachable from multiple locations, escalate as infrastructure issue.",
      "confidence": "inferred"
    },
    {
      "id": "uea_not_deployed",
      "name": "UEA Not Yet Deployed",
      "description": "Universal Executor Account does not exist on Push Chain for this Universal Origin Account",
      "likely_cause": "This is the user's first transaction on Push Chain. UEA deployment is lazy and happens automatically on first use. Not an error condition.",
      "detection_signal": "getAccountStatus() returns uea.deployed === false. Progress hook shows SEND-TX-03-01 followed by deployment steps.",
      "user_visible_symptom": "First transaction may take slightly longer. No visible error - this is expected behavior.",
      "machine_recovery": [
        "No action required - SDK handles UEA deployment automatically",
        "If checking status proactively, inform user that first transaction will deploy their account",
        "Ensure user has sufficient origin chain balance for deployment gas"
      ],
      "escalation": "If automatic deployment fails repeatedly, escalate with origin account address and error details.",
      "confidence": "documented"
    },
    {
      "id": "chain_not_supported_for_cea",
      "name": "Chain Not Supported for CEA Execution",
      "description": "Attempted Route 2 or Route 3 transaction to/from a chain that does not support CEA deployment",
      "likely_cause": "Developer specified tx.to.chain or tx.from.chain with a chain identifier that Push Chain does not yet support for external execution.",
      "detection_signal": "Error during sendTransaction() referencing unsupported chain for CEA or external execution. Check chain against supported CEA chains.",
      "user_visible_symptom": "Cross-chain transaction fails before submission. Error indicates target chain is not supported.",
      "machine_recovery": [
        "Verify target chain is in supported list for external execution",
        "For Route 1 (Push Chain only), remove chain specification from tx.to",
        "Check documentation for current list of CEA-supported chains",
        "Consider alternative approach using Route 1 with on-chain bridging"
      ],
      "escalation": "If specific chain support is needed, submit feature request with chain ID and use case.",
      "confidence": "inferred"
    },
    {
      "id": "wallet_connection_rejected",
      "name": "Wallet Connection Rejected",
      "description": "User rejected the wallet connection request in their wallet UI",
      "likely_cause": "User clicked 'Reject' or 'Cancel' in wallet popup (MetaMask, Phantom, etc.). May also occur if popup was blocked or closed accidentally.",
      "detection_signal": "Wallet adapter throws rejection error. Error code 4001 (EIP-1193) for EVM wallets. Progress hook emits SEND-TX-04-04.",
      "user_visible_symptom": "Wallet popup disappears. Application shows connection failed or returns to disconnected state.",
      "machine_recovery": [
        "Detect rejection error code and display user-friendly message",
        "Provide 'Try Again' button to re-initiate connection",
        "Check for popup blocker if connection never appears",
        "Do not auto-retry - user explicitly declined"
      ],
      "escalation": "No escalation needed. User action required to approve connection.",
      "confidence": "documented"
    },
    {
      "id": "invalid_to_address",
      "name": "Invalid Recipient Address",
      "description": "The tx.to address is malformed or invalid for the target chain",
      "likely_cause": "Address has wrong length, invalid characters, missing 0x prefix (EVM), or invalid base58 encoding (Solana). May also be passing Solana address to EVM target or vice versa.",
      "detection_signal": "Validation error during sendTransaction() before any network call. Error references address format or validation.",
      "user_visible_symptom": "Transaction fails immediately with address validation error before any wallet prompts.",
      "machine_recovery": [
        "Validate EVM addresses: 42 chars, 0x prefix, valid hex",
        "Validate Solana addresses: 32-44 chars, valid base58",
        "Use ethers.isAddress() or equivalent validation before submission",
        "For contract addresses, verify deployment on target chain"
      ],
      "escalation": "No escalation needed. Developer/input validation issue.",
      "confidence": "inferred"
    },
    {
      "id": "uea_upgrade_required",
      "name": "UEA Upgrade Required",
      "description": "Universal Executor Account implementation is outdated and must be upgraded before sending transactions",
      "likely_cause": "Push Chain protocol upgrade introduced new UEA version. User's existing UEA was deployed with older implementation.",
      "detection_signal": "getAccountStatus() returns uea.requiresUpgrade === true. sendTransaction() may fail with version mismatch error.",
      "user_visible_symptom": "Transactions fail with account version error. User may see prompt to upgrade account.",
      "machine_recovery": [
        "Check getAccountStatus() before transaction submission",
        "If uea.requiresUpgrade === true, call pushChainClient.upgradeAccount()",
        "upgradeAccount() is gasless and signature-based",
        "After upgrade, retry original transaction"
      ],
      "escalation": "If upgradeAccount() fails repeatedly, escalate with account address and error details.",
      "confidence": "documented"
    },
    {
      "id": "funds_transfer_failed",
      "name": "Asset Movement Failed",
      "description": "The tx.funds asset transfer step failed during universal transaction",
      "likely_cause": "Insufficient token balance, token not approved, or bridge/lock contract issue. Origin chain may have rejected the lock transaction.",
      "detection_signal": "Progress hook emits SEND-TX-06-* events followed by error. Error references fund transfer, lock, or bridge failure.",
      "user_visible_symptom": "Transaction fails during asset movement phase. User may see error about insufficient balance or transfer failure.",
      "machine_recovery": [
        "Verify user has sufficient balance of specified token on origin chain",
        "Check token approval if moving ERC20/SPL tokens",
        "Retry transaction if error was transient network issue",
        "Verify token is in supported MOVEABLE.TOKEN list"
      ],
      "escalation": "If balance is sufficient and approvals are in place, escalate with token type, amount, and full error message.",
      "confidence": "inferred"
    },
    {
      "id": "multicall_partial_failure",
      "name": "Multicall Batch Partial Failure",
      "description": "Some calls in a multicall batch executed while a later call failed, leaving partial state",
      "likely_cause": "As of @pushchain/core 6.0.19 this can only happen on the non-atomic fallback path: a native Push Chain EOA multicall whose signer could not sign an EIP-7702 authorization (e.g., a browser JSON-RPC wallet or ethers v5) executed as a sequential per-call loop, and a later call reverted after earlier calls were already committed. Atomic paths — the EIP-7702 batch for native EOAs and UEA_MULTICALL for external-origin senders — revert entirely on any sub-call failure and never leave partial state.",
      "detection_signal": "response.atomic === false on the send result identifies the sequential fallback path. Some per-call transactions succeeded while a later one reverted — inspect the individual call receipts to identify which sub-calls failed.",
      "user_visible_symptom": "Some operations in batch completed, others did not. User sees inconsistent state.",
      "machine_recovery": [
        "Parse per-call results to identify which operations failed",
        "Provide user with clear breakdown of success/failure per operation",
        "Allow retry of only failed operations",
        "To get all-or-nothing behavior from a native Push Chain EOA, use a signer that supports signAuthorization (ethers v6 Wallet or viem local account, or a custom signer built with construct() providing signAuthorization) so the batch runs as a single atomic EIP-7702 transaction",
        "Check response.atomic after sending to confirm which execution path ran"
      ],
      "escalation": "If multicall behavior is inconsistent with documentation, escalate with batch payload and results.",
      "confidence": "documented"
    },
    {
      "id": "multicall_zero_address_entry",
      "name": "Multicall Entry Targets Zero Address",
      "description": "A multicall batch (outer tx.to = zero address) contains an inner entry whose `to` is the zero address; the SDK rejects the batch during validation (@pushchain/core 6.0.19+)",
      "likely_cause": "Developer built the multicall array with a missing or placeholder `to` on one entry. The ERC-7821 batch executor used by the EIP-7702 atomic path reinterprets a zero target as address(this) — the sender's own account — so the SDK rejects the entry to prevent an unintended self-call.",
      "detection_signal": "PushChainExecutionError thrown before any broadcast with message: 'A multicall entry has a zero to address. Use an explicit target (the EIP-7702 executor reinterprets a zero target as the account itself).'",
      "user_visible_symptom": "Multicall transaction fails immediately during validation. Nothing is broadcast and no state changes on any chain.",
      "machine_recovery": [
        "Set an explicit non-zero `to` on every inner multicall entry",
        "Keep the OUTER tx.to as the zero address — that is what signals multicall mode; only the inner entries must be non-zero",
        "If a call genuinely must target the sender's own account, pass that account's address explicitly"
      ],
      "escalation": "No escalation needed. Input validation issue — fix the batch payload.",
      "confidence": "documented"
    },
    {
      "id": "eip7702_not_supported",
      "name": "EIP-7702 Authorization Not Supported (auto-handled)",
      "description": "The signer cannot sign an EIP-7702 authorization, so a native Push Chain EOA multicall cannot use the atomic batch path. The SDK raises EIP7702NotSupportedError internally, strictly before any broadcast, catches it, and falls back to the legacy sequential per-call loop — it is not surfaced to callers as a thrown error (@pushchain/core 6.0.19+)",
      "likely_cause": "The signer is a JSON-RPC account (browser wallets like MetaMask cannot sign raw EIP-7702 authorizations), an ethers v5 signer, or a custom signer built without signAuthorization. The atomic path also requires the batch executor contract to be deployed on the chain (Donut: yes; mainnet: not yet).",
      "detection_signal": "console.warn logged by the SDK when it falls back. Observable signal in the result: response.atomic === false on a native-EOA multicall. Any other error during the atomic attempt surfaces normally.",
      "user_visible_symptom": "The batch still executes, but as separate sequential transactions — an earlier call stays committed if a later one reverts. No error is thrown; there is no double-execution risk.",
      "machine_recovery": [
        "No action required for correctness — the SDK falls back automatically",
        "If atomicity is required, use a signer that supports signAuthorization: an ethers v6 Wallet (local key) or a viem local account (e.g., privateKeyToAccount)",
        "For custom signers, provide signAuthorization in PushChain.utils.signer.construct(account, { ..., signAuthorization })",
        "Check response.atomic after sending to detect which path ran"
      ],
      "escalation": "Not a bug. If atomic batching is needed from a browser context, route signing through Push Wallet (email/google login) or a backend local-key signer instead of a JSON-RPC wallet.",
      "confidence": "documented"
    },
    {
      "id": "pc20_invalid_address",
      "name": "Invalid PC-20 Address (PC20_INVALID_ADDRESS)",
      "description": "InvalidPC20AddressError: the address in a PC-20 reference is malformed for the supplied chain’s VM, or is the zero/default address. Part of the typed PC-20 error family (present in @pushchain/core 6.0.24): every PC-20 error extends PC20Error, carries a stable `code` discriminator, curated context fields (chain, address, expectedChain, chainNamespace), and a remediation `hint`. Classify by `instanceof PC20Error` or `err.code`, never by parsing messages.",
      "likely_cause": "A truncated, wrong-VM (e.g. base58 on an EVM chain), or zero address was passed as `funds.token.address` or to PushChain.utils.tokens.getPC20Address().",
      "detection_signal": "Rejected promise carrying code PC20_INVALID_ADDRESS during pre-flight validation, before any broadcast. Every caller-facing path is async (sendTransaction, prepareTransaction, getPC20Address), so it surfaces as a rejection, never a synchronous throw.",
      "user_visible_symptom": "Transaction or lookup fails immediately with an address validation message; nothing is signed or broadcast.",
      "machine_recovery": [
        "Verify the address is a full, checksummed address valid for the VM of `funds.token.chain`",
        "Resolve the canonical address via PushChain.utils.tokens.getPC20Address() instead of hand-copying it",
        "Never derive PC-20 identity from a symbol — PC-20 identity is address-and-chain only"
      ],
      "escalation": "If a registry-sourced address triggers this, escalate with the address, chain, and SDK version — the registry entry may be corrupt.",
      "confidence": "documented"
    },
    {
      "id": "pc20_token_chain_mismatch",
      "name": "PC-20 Token Chain Mismatch (PC20_TOKEN_CHAIN_MISMATCH)",
      "description": "PC20TokenChainMismatchError: `funds.token.chain` does not match the chain the funds are actually on — the signer’s chain for a direct external route, `from.chain` for a CEA-origin route, or Push Chain for an export. This is the check that stops a wrapper address copied onto the wrong chain. Part of the typed PC-20 error family (present in @pushchain/core 6.0.24): every PC-20 error extends PC20Error, carries a stable `code` discriminator, curated context fields (chain, address, expectedChain, chainNamespace), and a remediation `hint`. Classify by `instanceof PC20Error` or `err.code`, never by parsing messages.",
      "likely_cause": "`funds.token.chain` was set to the destination chain (`to.chain`) instead of the chain the tokens currently sit on, or a wrapper address from one chain was paired with another chain’s constant.",
      "detection_signal": "Error with code PC20_TOKEN_CHAIN_MISMATCH thrown pre-flight. Hint text: “funds.token.chain must be where the token lives, not the destination. `to.chain` is the destination.”",
      "user_visible_symptom": "sendTransaction rejects before broadcast; no funds move.",
      "machine_recovery": [
        "Set `funds.token.chain` to where the tokens are RIGHT NOW (e.g. PUSH_TESTNET_DONUT when exporting a Push-native token)",
        "Set the destination only in `to.chain`",
        "Re-resolve the token with getPC20Address({ chain }) to confirm which chain the supplied address belongs to"
      ],
      "escalation": "None — this is always a caller-side argument fix.",
      "confidence": "documented"
    },
    {
      "id": "pc20_wrapper_not_registered",
      "name": "PC-20 Wrapper Not Registered (PC20_WRAPPER_NOT_REGISTERED)",
      "description": "PC20WrapperNotRegisteredError: UniversalCore’s on-chain registry has no source mapping for this wrapper address on this chain. Part of the typed PC-20 error family (present in @pushchain/core 6.0.24): every PC-20 error extends PC20Error, carries a stable `code` discriminator, curated context fields (chain, address, expectedChain, chainNamespace), and a remediation `hint`. Classify by `instanceof PC20Error` or `err.code`, never by parsing messages.",
      "likely_cause": "The address was never deployed by a PC-20 export to that chain, the address belongs to a different chain, or the token is not a PC-20 wrapper at all.",
      "detection_signal": "Error with code PC20_WRAPPER_NOT_REGISTERED from getPC20Address() or from pre-flight resolution of a `funds.token` PC-20 reference.",
      "user_visible_symptom": "Lookup or transaction fails with “No PC20 wrapper is registered at this address on this chain.”",
      "machine_recovery": [
        "Confirm the wrapper was deployed by a prior export to this chain — a wrapper only exists on a chain after the first export there",
        "Confirm the address belongs to the chain passed in `options.chain` / `funds.token.chain`",
        "If the token is Push-native, pass its canonical Push Chain address with chain = PUSH_TESTNET_DONUT instead of a wrapper address"
      ],
      "escalation": "If a wrapper you deployed via a confirmed export is not found, escalate with the export tx hash and chain.",
      "confidence": "documented"
    },
    {
      "id": "pc20_registry_mismatch",
      "name": "PC-20 Registry Mismatch (PC20_REGISTRY_MISMATCH)",
      "description": "PC20RegistryMismatchError: forward and reverse registry lookups disagree — getPC20Source(wrapper) resolved a source whose getPC20Wrapper(source) is a different address. A stale or partially-written registry entry. The SDK never proceeds: the burn would unlock the wrong Push token. Part of the typed PC-20 error family (present in @pushchain/core 6.0.24): every PC-20 error extends PC20Error, carries a stable `code` discriminator, curated context fields (chain, address, expectedChain, chainNamespace), and a remediation `hint`. Classify by `instanceof PC20Error` or `err.code`, never by parsing messages.",
      "likely_cause": "Registry inconsistency on-chain (stale or partially-written entry). Not caused by caller arguments.",
      "detection_signal": "Error with code PC20_REGISTRY_MISMATCH; message may include the reverse-resolved wrapper address.",
      "user_visible_symptom": "Transaction aborts pre-flight; no funds are locked or burned.",
      "machine_recovery": [
        "Do not retry blindly — the hint says this is a registry inconsistency",
        "Re-resolve via getPC20Address() to capture the conflicting addresses for the report"
      ],
      "escalation": "Always escalate with both addresses and the chain — this indicates on-chain registry corruption, not a caller bug.",
      "confidence": "documented"
    },
    {
      "id": "pc20_factory_mismatch",
      "name": "PC-20 Factory Mismatch (PC20_FACTORY_MISMATCH)",
      "description": "PC20FactoryMismatchError: the live external gateway’s configured pc20Factory differs from the reference factory, or the factory does not recognize the wrapper. Proceeding would risk the gateway taking the non-PC-20 path for this token. Part of the typed PC-20 error family (present in @pushchain/core 6.0.24): every PC-20 error extends PC20Error, carries a stable `code` discriminator, curated context fields (chain, address, expectedChain, chainNamespace), and a remediation `hint`. Classify by `instanceof PC20Error` or `err.code`, never by parsing messages.",
      "likely_cause": "Deployment misconfiguration on the external chain (gateway and UniversalCore disagree about the factory identity).",
      "detection_signal": "Error with code PC20_FACTORY_MISMATCH thrown pre-flight. The gateway and registry factory addresses are compared internally but are NOT exposed on the error object or in its message — only code, chain, address, expectedChain, chainNamespace, and hint are set.",
      "user_visible_symptom": "Transaction aborts before broadcast.",
      "machine_recovery": [
        "Do not retry — the hint says the deployment is misconfigured; report it",
        "Capture err.code, err.chain, err.address, and err.message for the report; the factory addresses are not carried on the error"
      ],
      "escalation": "Always escalate with the destination chain and token address — this is an infrastructure misconfiguration between the live gateway and UniversalCore, not a caller bug.",
      "confidence": "documented"
    },
    {
      "id": "pc20_invalid_metadata",
      "name": "Invalid PC-20 Metadata (PC20_INVALID_METADATA)",
      "description": "InvalidPC20MetadataError: the token does not expose usable ERC-20 metadata (name/symbol/decimals), or the metadata fails validation. Since @pushchain/core 6.0.22 any ERC-20 born on Push Chain with standard name()/symbol()/decimals() qualifies as a PC-20 — there is no pc20Metadata() interface to implement. Part of the typed PC-20 error family (present in @pushchain/core 6.0.24): every PC-20 error extends PC20Error, carries a stable `code` discriminator, curated context fields (chain, address, expectedChain, chainNamespace), and a remediation `hint`. Classify by `instanceof PC20Error` or `err.code`, never by parsing messages.",
      "likely_cause": "The contract at the supplied address is not a standard ERC-20 (missing or reverting name/symbol/decimals), or returns values that fail validation.",
      "detection_signal": "Error with code PC20_INVALID_METADATA during resolution or export preparation.",
      "user_visible_symptom": "Lookup or export fails citing token metadata.",
      "machine_recovery": [
        "Verify the contract implements standard ERC-20 name(), symbol(), and decimals()",
        "Confirm you passed a token contract address, not a wallet, pool, or proxy-admin address"
      ],
      "escalation": "If a standard ERC-20 with verifiable metadata triggers this, escalate with the address and chain.",
      "confidence": "documented"
    },
    {
      "id": "pc20_expected_but_prc20",
      "name": "PRC-20 Supplied Where PC-20 Expected (PC20_EXPECTED_BUT_PRC20)",
      "description": "PC20ExpectedButPRC20Error: a synthetic PRC-20 address (an external-chain token mirrored inward, e.g. USDC.eth, pETH) was supplied where a Push-native PC-20 was expected. A PRC-20 is a legitimate token on the wrong API, not a broken one — the SDK keeps this distinct from invalid-metadata so the fix points at the right call. Part of the typed PC-20 error family (present in @pushchain/core 6.0.24): every PC-20 error extends PC20Error, carries a stable `code` discriminator, curated context fields (chain, address, expectedChain, chainNamespace), and a remediation `hint`. Classify by `instanceof PC20Error` or `err.code`, never by parsing messages.",
      "likely_cause": "The two standards are one letter apart and mean opposite directions: PRC-20 = foreign token mirrored INTO Push; PC-20 = Push-native token mirrored OUT. A PRC-20 address was passed as a `{ chain, address }` PC-20 reference or to getPC20Address().",
      "detection_signal": "Error with code PC20_EXPECTED_BUT_PRC20: “This address is a synthetic PRC20, not a Push-native PC20.”",
      "user_visible_symptom": "Transaction or lookup rejects before anything is signed.",
      "machine_recovery": [
        "Move PRC-20 tokens with the MoveableToken API (PushChain.CONSTANTS.MOVEABLE.TOKEN.* accessors), not a { chain, address } PC-20 reference",
        "Use PushChain.utils.tokens.getPRC20Address() (sync, static table) for PRC-20s; getPC20Address() (async, on-chain registry) is only for Push-native tokens"
      ],
      "escalation": "None — switch to the MoveableToken API for this token.",
      "confidence": "documented"
    },
    {
      "id": "pc20_unsupported_destination",
      "name": "Unsupported PC-20 Destination (PC20_UNSUPPORTED_DESTINATION)",
      "description": "UnsupportedPC20DestinationError: the destination chain has no configured PC-20 factory, or is not supported for PC-20 export. Part of the typed PC-20 error family (present in @pushchain/core 6.0.24): every PC-20 error extends PC20Error, carries a stable `code` discriminator, curated context fields (chain, address, expectedChain, chainNamespace), and a remediation `hint`. Classify by `instanceof PC20Error` or `err.code`, never by parsing messages.",
      "likely_cause": "A PC-20 export targeted a chain namespace with no registered PC-20 factory (chain not yet supported for PC-20).",
      "detection_signal": "Error with code PC20_UNSUPPORTED_DESTINATION thrown pre-flight.",
      "user_visible_symptom": "Export rejects before any lock or approval.",
      "machine_recovery": [
        "Read err.chainNamespace off the error — it names the unsupported destination. No static list of PC-20-capable chains is published; support is decided by UniversalCore's factory registrations at runtime",
        "Pick a destination that already appears in getPC20Address(...).registry for a known exported PC-20, or keep the token on Push Chain"
      ],
      "escalation": "If a chain documented as PC-20-capable triggers this, escalate with the chain namespace from the error context.",
      "confidence": "documented"
    },
    {
      "id": "pc20_insufficient_balance",
      "name": "Insufficient PC-20 Balance (PC20_INSUFFICIENT_BALANCE) — exported, not thrown in 6.0.24",
      "description": "InsufficientPC20BalanceError is exported by @pushchain/core 6.0.24 with code PC20_INSUFFICIENT_BALANCE, but no code path in 6.0.24 instantiates it: the SDK performs no PC-20 balance pre-flight. An underfunded PC-20 move surfaces as an on-chain revert (or the generic gas/balance checks under enforceGasCheck), never as this code. The class exists for forward compatibility; `required`/`available` are folded into the message string only and are not instance properties. It belongs to the typed PC-20 error family (present in @pushchain/core 6.0.24): every PC-20 error extends PC20Error, carries a stable `code` discriminator, curated context fields (chain, address, expectedChain, chainNamespace), and a remediation `hint`. Classify by `instanceof PC20Error` or `err.code`, never by parsing messages.",
      "likely_cause": "Not reachable in 6.0.24. If a future SDK wires it, the cause would be a wrapper (inbound) or Push-native source (export) balance lower than funds.amount.",
      "detection_signal": "None in 6.0.24 — the code is never emitted. Do not write a catch branch that depends on it; check balances yourself before sending.",
      "user_visible_symptom": "Not applicable in 6.0.24. An underfunded PC-20 transfer fails on chain (revert) rather than pre-flight.",
      "machine_recovery": [
        "Check the balance of the exact address that funds the move (UEA for external-origin flows, the wallet itself for Push-native flows) BEFORE calling sendTransaction — the SDK will not do it for PC-20s",
        "Use the decimals from getPC20Address() when computing funds.amount — a decimals mismatch inflates the requested amount",
        "Treat `instanceof InsufficientPC20BalanceError` as forward-compatible only; on 6.0.24 it never matches"
      ],
      "escalation": "None — fund the account or fix the amount.",
      "confidence": "inferred"
    },
    {
      "id": "pc20_ambiguous_address",
      "name": "Ambiguous PC-20 Address (PC20_AMBIGUOUS_ADDRESS)",
      "description": "PC20AmbiguousAddressError: the same address is registered on more than one chain, resolving to different Push sources, and no `chain` option was supplied to disambiguate. Only reachable from getPC20Address()’s optional-chain discovery path; the error carries a `candidates` array of { chain, pushAddress }. Picking the first match silently would give a confident answer about the wrong token, so the SDK throws instead. Part of the typed PC-20 error family (present in @pushchain/core 6.0.24): every PC-20 error extends PC20Error, carries a stable `code` discriminator, curated context fields (chain, address, expectedChain, chainNamespace), and a remediation `hint`. Classify by `instanceof PC20Error` or `err.code`, never by parsing messages.",
      "likely_cause": "getPC20Address(address) was called without `options.chain` and the address exists on multiple chains.",
      "detection_signal": "Error with code PC20_AMBIGUOUS_ADDRESS listing every candidate chain and its Push source address.",
      "user_visible_symptom": "Lookup fails with the candidate list; no transaction was attempted.",
      "machine_recovery": [
        "Pass `options.chain` to getPC20Address() to pin which chain the supplied address lives on",
        "Read the `candidates` array off the error to present the choice to the user"
      ],
      "escalation": "None — supply the chain option.",
      "confidence": "documented"
    },
    {
      "id": "pc20_unknown_chain_namespace",
      "name": "Unknown PC-20 Chain Namespace (PC20_UNKNOWN_CHAIN_NAMESPACE)",
      "description": "PC20UnknownChainNamespaceError: a chain namespace could not be mapped in either direction (SDK chain constant ↔ CAIP-2 namespace). Thrown rather than returning a negative result — an unmapped namespace would read from the registry as “not deployed”, hiding the actual bug. Part of the typed PC-20 error family (present in @pushchain/core 6.0.24): every PC-20 error extends PC20Error, carries a stable `code` discriminator, curated context fields (chain, address, expectedChain, chainNamespace), and a remediation `hint`. Classify by `instanceof PC20Error` or `err.code`, never by parsing messages.",
      "likely_cause": "A chain value outside PushChain.CONSTANTS.CHAIN was supplied, or the SDK version predates the chain’s addition.",
      "detection_signal": "Error with code PC20_UNKNOWN_CHAIN_NAMESPACE; the chainNamespace context field holds the unmapped value.",
      "user_visible_symptom": "Lookup or transaction fails naming the namespace.",
      "machine_recovery": [
        "Use a PushChain.CONSTANTS.CHAIN value, never a hand-written chain string",
        "Upgrade @pushchain/core if the chain was added in a newer release"
      ],
      "escalation": "If a current CONSTANTS.CHAIN value triggers this on the latest SDK, escalate with the namespace and SDK version.",
      "confidence": "documented"
    },
    {
      "id": "pc20_wrapper_prediction_unavailable",
      "name": "PC-20 Wrapper Prediction Unavailable (PC20_WRAPPER_PREDICTION_UNAVAILABLE)",
      "description": "PC20WrapperPredictionUnavailableError: the destination wrapper address for a FIRST export could not be predicted. Thrown strictly BEFORE approval — a first export commits the source token into VaultPC20 before the destination transfer is built, so an unverifiable prediction aborts rather than guessing. Part of the typed PC-20 error family (present in @pushchain/core 6.0.24): every PC-20 error extends PC20Error, carries a stable `code` discriminator, curated context fields (chain, address, expectedChain, chainNamespace), and a remediation `hint`. Classify by `instanceof PC20Error` or `err.code`, never by parsing messages.",
      "likely_cause": "The destination chain’s PC-20 factory was unreachable (RPC outage or misconfiguration) while preparing the token’s first export to that chain.",
      "detection_signal": "Error with code PC20_WRAPPER_PREDICTION_UNAVAILABLE thrown before approval; no funds locked.",
      "user_visible_symptom": "First export to a new chain fails fast; repeat exports (wrapper already deployed) are unaffected.",
      "machine_recovery": [
        "Retry once the destination factory/RPC is reachable — the hint confirms nothing was locked",
        "Verify the destination chain RPC is healthy before retrying"
      ],
      "escalation": "If retries keep failing with healthy RPCs, escalate with the destination chain and token address.",
      "confidence": "documented"
    },
    {
      "id": "pc20_export_reverted",
      "name": "PC-20 Export Reverted (PC20_EXPORT_REVERTED) — exported, not thrown in 6.0.24",
      "description": "PC20ExportRevertedError is exported by @pushchain/core 6.0.24 with code PC20_EXPORT_REVERTED and instance fields outboundTxId / lockedAmount / revertRecipient, but no code path in 6.0.24 instantiates it. A PC-20 export that reverts after the Push-side lock is reported through the normal outbound tracking path (trackTransaction / progress events), not through this error. The class exists for forward compatibility. It belongs to the typed PC-20 error family (present in @pushchain/core 6.0.24): every PC-20 error extends PC20Error, carries a stable `code` discriminator, curated context fields, and a remediation `hint`. Classify by `instanceof PC20Error` or `err.code`, never by parsing messages.",
      "likely_cause": "Not reachable in 6.0.24. If a future SDK wires it, the cause would be the destination leg of an export failing after the Push-side lock.",
      "detection_signal": "None in 6.0.24 — the code is never emitted. Detect a failed export from the receipt / trackTransaction outcome instead.",
      "user_visible_symptom": "Not applicable in 6.0.24. A failed export shows as a failed or reverted outbound in tracking; the locked tokens return to the revert recipient on Push Chain via the protocol's revert path.",
      "machine_recovery": [
        "Track the export with trackTransaction(txHash) and surface the outbound status to the user",
        "Confirm the refund landed on Push Chain (donut.push.network explorer) before retrying",
        "Treat `instanceof PC20ExportRevertedError` as forward-compatible only; on 6.0.24 it never matches"
      ],
      "escalation": "If a reverted export's refund does not appear on Push Chain, escalate immediately with the Push tx hash — cross-reference agents/recovery-playbook.md.",
      "confidence": "inferred"
    },
    {
      "id": "pc20_unsafe_empty_payload",
      "name": "Unsafe Empty PC-20 Payload (PC20_UNSAFE_EMPTY_PAYLOAD)",
      "description": "PC20UnsafeEmptyPayloadError: a wrapper burn was about to be submitted without any Push-side forwarding payload. Until every supported Push Chain version treats selector-only PC-20 payloads as empty, broadcasting this shape could burn the wrapper and leave the canonical token permanently locked — the SDK fails closed. Part of the typed PC-20 error family (present in @pushchain/core 6.0.24): every PC-20 error extends PC20Error, carries a stable `code` discriminator, curated context fields (chain, address, expectedChain, chainNamespace), and a remediation `hint`. Classify by `instanceof PC20Error` or `err.code`, never by parsing messages.",
      "likely_cause": "A hand-built or downlevel-prepared transaction produced a PC-20 burn with no Push-side payload (typically an outdated SDK or manual payload construction).",
      "detection_signal": "Error with code PC20_UNSAFE_EMPTY_PAYLOAD thrown before broadcast.",
      "user_visible_symptom": "The burn never broadcasts; wrapper balance is untouched.",
      "machine_recovery": [
        "Prepare the transaction again with a current @pushchain/core — recent versions always attach the PC-20 transfer payload",
        "Never hand-construct PC-20 burn payloads"
      ],
      "escalation": "If the current SDK still produces this, escalate with the SDK version and the prepared payload shape.",
      "confidence": "documented"
    }
  ]
}
