> ## Documentation Index
> Fetch the complete documentation index at: https://docs.peaq.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> Error class hierarchy, 20 faucet codes, and on-chain revert names for the peaqOS SDK.

Every error raised by the SDK extends a common base. Faucet-specific codes are surfaced on the error instance (`error.code`) so callers can branch without string matching.

## Class hierarchy

<Tabs>
  <Tab title="JavaScript">
    ```text theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
    PeaqosError
    ├── ValidationError
    ├── RuntimeError
    ├── ValueCapExceeded
    └── RateLimitExceeded
    ```

    All four concrete classes extend `PeaqosError` directly. `catch (err instanceof PeaqosError)` covers every SDK error; catching `RuntimeError` alone will miss `ValueCapExceeded` and `RateLimitExceeded`.

    ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
    import {
      PeaqosError,         // base
      ValidationError,     // bad input
      RuntimeError,        // chain or HTTP failure; carries optional `code`
      ValueCapExceeded,    // operational cap hit (extends PeaqosError)
      RateLimitExceeded,   // operational cap hit (extends PeaqosError)
    } from "@peaqos/peaq-os-sdk";
    ```
  </Tab>

  <Tab title="Python">
    ```text theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
    PeaqosError
    ├── ValidationError
    ├── RpcError
    ├── ApiError
    ├── ValueCapExceeded
    └── RateLimitExceeded
    ```

    ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
    from peaq_os_sdk import (
        PeaqosError,
        ValidationError,
        RpcError,         # chain / web3 errors
        ApiError,         # HTTP API errors (faucet, MCR API)
        ValueCapExceeded,
        RateLimitExceeded,
    )
    ```

    Python separates transport-layer failures into `RpcError` (chain) and `ApiError` (faucet + MCR API). JavaScript collapses both into `RuntimeError` with a `code` field.
  </Tab>
</Tabs>

***

## Faucet error codes

All 20 codes the Gas Station can return from `POST /faucet/fund`, `POST /2fa/setup`, and `POST /2fa/confirm`. Each endpoint returns a subset. For example, `INVALID_OWNER_ADDRESS` and `QR_GENERATION_FAILED` only come from `/2fa/setup`. Codes surface as `RuntimeError.code` (JS) or `ApiError.code` (Python).

<AccordionGroup>
  <Accordion title="2FA errors" icon="lock">
    | Code                 | Description                                          | Retry                                          |
    | :------------------- | :--------------------------------------------------- | :--------------------------------------------- |
    | `INVALID_2FA`        | OTP rejected by the faucet                           | Yes: submit a fresh 6-digit code               |
    | `2FA_NOT_CONFIGURED` | Owner has not completed `setup_faucet_2fa`           | No: re-run setup                               |
    | `2FA_NOT_ACTIVE`     | 2FA enrolled but not confirmed                       | No: call `confirm_faucet_2fa` with a valid OTP |
    | `2FA_LOCKED`         | Too many invalid attempts; owner temporarily blocked | After lockout window                           |
  </Accordion>

  <Accordion title="Idempotency errors" icon="copy">
    | Code                        | Description                         | Retry                                                     |
    | :-------------------------- | :---------------------------------- | :-------------------------------------------------------- |
    | `DUPLICATE_REQUEST`         | Same `request_id` already in flight | No: use a different `request_id` or wait for prior result |
    | `REQUEST_ALREADY_PROCESSED` | Same `request_id` already resolved  | No: read the prior result                                 |
  </Accordion>

  <Accordion title="Rate limit & cap errors" icon="gauge">
    | Code                  | Description                         | Retry                                  |
    | :-------------------- | :---------------------------------- | :------------------------------------- |
    | `RATE_LIMITED`        | Per-IP or per-wallet throttle hit   | After cooldown                         |
    | `CAP_EXCEEDED_OWNER`  | Daily per-owner funding cap reached | Next day                               |
    | `CAP_EXCEEDED_WALLET` | Daily per-target-wallet cap reached | Next day, or target a different wallet |
  </Accordion>

  <Accordion title="Validation errors" icon="triangle-exclamation">
    | Code                     | Description                             | Retry               |
    | :----------------------- | :-------------------------------------- | :------------------ |
    | `INVALID_PAYLOAD`        | Request body malformed                  | No: fix the payload |
    | `INVALID_OWNER_ADDRESS`  | `owner_address` not a recognized format | No                  |
    | `INVALID_TARGET_ADDRESS` | `target_wallet_address` not recognized  | No                  |
    | `INVALID_CHAIN_ID`       | `chain_id` not configured on the faucet | No                  |
    | `INVALID_REQUEST_ID`     | `request_id` not a UUID                 | No                  |
  </Accordion>

  <Accordion title="Chain & server errors" icon="server">
    | Code              | Description                           | Retry |
    | :---------------- | :------------------------------------ | :---- |
    | `TRANSFER_FAILED` | On-chain transfer reverted or stalled | Yes   |
    | `CHAIN_RPC_ERROR` | Faucet's RPC call failed              | Yes   |
    | `INTERNAL_ERROR`  | Faucet internal failure               | Yes   |
  </Accordion>

  <Accordion title="QR errors" icon="qrcode">
    | Code                   | Description                              | Retry            |
    | :--------------------- | :--------------------------------------- | :--------------- |
    | `QR_NOT_FOUND`         | QR image expired or never created        | No: re-run setup |
    | `QR_EXPIRED`           | QR retrieval attempted after \~2 min TTL | No: re-run setup |
    | `QR_GENERATION_FAILED` | Faucet could not generate the image      | Yes              |
  </Accordion>
</AccordionGroup>

Full flow reference: [Gas Station concept](/peaqos/concepts/gas-station), [setupFaucet2FA](/peaqos/sdk-reference/sdk-js#setupfaucet2fa), [fund\_from\_gas\_station](/peaqos/sdk-reference/sdk-python#fund_from_gas_station).

***

## On-chain revert names

Revert names the SDK translates to a friendly message and surfaces as `RuntimeError.code` (JS) or `RpcError.code` (Python). The contracts define more custom errors than this table; anything not listed surfaces as `code: "TX_REVERTED"` (Python) or `code: "<RawErrorName>"` with a generic `Transaction reverted: …` message (JS, when viem decodes the selector).

| Revert                        | Raised by                                              | Cause                                                               |
| :---------------------------- | :----------------------------------------------------- | :------------------------------------------------------------------ |
| `AlreadyRegistered`           | `registerMachine` / `registerFor` / `register_machine` | Address already has a machine ID                                    |
| `InvalidMachineAddress`       | `registerFor`                                          | `machineAddress` is the zero address                                |
| `InvalidAddress`              | Staking / NFT flows                                    | Caller passed the zero address where a real address is required     |
| `AmountZero`                  | Staking / bond flow                                    | Bond amount is zero                                                 |
| `AlreadyStaked`               | Staking flow                                           | Machine is already bonded/staked                                    |
| `MachineNotBonded`            | `mintNft` / `mint_nft`                                 | Caller's machine is not bonded on the IdentityRegistry              |
| `AlreadyMinted`               | `mintNft` / `mint_nft`                                 | An NFT is already minted for the machine                            |
| `NotMachineOwner`             | `mintNft` / `mint_nft`                                 | Caller is not the owner of the machine                              |
| `RecipientMustBeMachineOwner` | `mintNft` / `mint_nft`                                 | Operator-mint guard: recipient must be the registered machine owner |
| `MachineNotFound`             | `submitEvent` / `tokenIdOf`                            | machineId has no Identity record on the IdentityRegistry            |
| `MachineDeactivated`          | `submitEvent`                                          | The machine has been deactivated by the IdentityRegistry            |
| `NotAuthorizedSubmitter`      | `submitEvent`                                          | Caller is not authorized to submit events for this machine          |
| `InvalidEventType`            | `submitEvent`                                          | `eventType` is not `0` (revenue) or `1` (activity)                  |
| `InvalidTrustLevel`           | `submitEvent`                                          | `trustLevel` is not `0`, `1`, or `2`                                |

## MCR API error codes

Returned by `queryMcr` / `query_mcr`, `queryMachine` / `query_machine`, and `queryOperatorMachines` / `query_operator_machines`. Surfaced as `RuntimeError.code` (JS) or `ApiError.code` (Python).

| Code                  | Cause                                                                  | Retry                                     |
| :-------------------- | :--------------------------------------------------------------------- | :---------------------------------------- |
| `NOT_FOUND`           | The DID, machine, or token ID was not found by the MCR API (HTTP 404)  | No: check the input                       |
| `BAD_RESPONSE`        | The MCR returned an unexpected payload shape                           | No: file an issue if it persists          |
| `HTTP_ERROR`          | The MCR returned an unhandled non-2xx status                           | Maybe: surface the status code and decide |
| `SERVER_ERROR`        | The MCR returned 5xx                                                   | Yes, with backoff                         |
| `SERVICE_UNAVAILABLE` | The MCR returned 503 (`Service not initialised` / `Chain unavailable`) | Yes, with backoff                         |
| `TIMEOUT`             | The HTTP request exceeded `timeoutMs`                                  | Yes                                       |
| `NETWORK_ERROR`       | Transport-level failure (DNS, TCP, TLS)                                | Yes                                       |
| `ABORTED`             | The caller aborted the request via `AbortSignal` (JS only)             | Caller's choice                           |

## OWS signing error codes

Raised when transaction signing routes through an OWS vault wallet (`PeaqosClient.fromWallet` / `from_wallet` with `owsSigning=true`). The SDK normalises the upstream OWS error code into a typed SDK exception — only `INVALID_INPUT` becomes `ValidationError`; the other four become `PeaqosError` (or `RuntimeError` in JS) with the original OWS error preserved as `.cause`.

| Code                  | Cause                                          | Surfaces as                              |
| :-------------------- | :--------------------------------------------- | :--------------------------------------- |
| `WALLET_NOT_FOUND`    | Vault wallet name / UUID does not exist        | `PeaqosError` (Py) / `RuntimeError` (JS) |
| `INVALID_PASSPHRASE`  | Wrong vault passphrase                         | `PeaqosError` (Py) / `RuntimeError` (JS) |
| `INVALID_INPUT`       | Malformed transaction or sign-hash payload     | `ValidationError(field="transaction")`   |
| `POLICY_DENIED`       | Signing blocked by an OWS policy rule          | `PeaqosError` (Py) / `RuntimeError` (JS) |
| `CHAIN_NOT_SUPPORTED` | Transaction `chainId` is not configured in OWS | `PeaqosError` (Py) / `RuntimeError` (JS) |

Constants exported from both SDKs as `OWS_ERROR_WALLET_NOT_FOUND`, `OWS_ERROR_INVALID_PASSPHRASE`, `OWS_ERROR_INVALID_INPUT`, `OWS_ERROR_POLICY_DENIED`, `OWS_ERROR_CHAIN_NOT_SUPPORTED`. JS additionally exports the `OwsSigningErrorCode` union type.

```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
import { OWS_ERROR_INVALID_PASSPHRASE, OWS_ERROR_CHAIN_NOT_SUPPORTED } from "@peaqos/peaq-os-sdk";

try {
  await client.bridgeNft({ tokenId, destination: "base" });
} catch (err) {
  if (err.cause?.code === OWS_ERROR_INVALID_PASSPHRASE) { /* prompt re-auth */ }
  if (err.cause?.code === OWS_ERROR_CHAIN_NOT_SUPPORTED) { /* OWS chain config bug */ }
  throw err;
}
```

```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
from peaq_os_sdk import (
    OWS_ERROR_INVALID_PASSPHRASE,
    OWS_ERROR_CHAIN_NOT_SUPPORTED,
    PeaqosError,
)

try:
    client.bridge_nft(token_id=..., destination="base")
except PeaqosError as err:
    code = getattr(err.__cause__, "code", None)
    if code == OWS_ERROR_INVALID_PASSPHRASE:
        ...  # prompt re-auth
    if code == OWS_ERROR_CHAIN_NOT_SUPPORTED:
        ...  # OWS chain config bug
    raise
```

## SDK transaction sentinels

Raised by the SDK's transaction helper around any contract call. Surfaced as `RuntimeError.code` (JS) or `RpcError.code` (Python).

| Code                         | Cause                                                                                                                                                               |
| :--------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `WALLET_NOT_CONFIGURED`      | Method requires a signer but none was configured. (JS only; the Python `PeaqosClient` constructor requires a `private_key` and surfaces this as `ValidationError`.) |
| `TX_REVERTED`                | The transaction reverted on-chain. The revert reason is in the message; revert names from the table above are decoded into `code` when the SDK recognizes them.     |
| `RECEIPT_AWAIT_FAILED`       | Transaction was submitted but the SDK could not retrieve a receipt. The tx may still have landed; re-query by hash before retrying.                                 |
| `REGISTERED_EVENT_MISSING`   | Receipt has no `Registered` event log. Indicates a contract/SDK ABI mismatch.                                                                                       |
| `REGISTERED_EVENT_MALFORMED` | `Registered` event log was decoded but had unexpected fields. Same root cause as above.                                                                             |
| `INVALID_FEE_RESULT`         | `quoteSend` on the LayerZero ONFT adapter returned a malformed `MessagingFee`. Raised inside `bridge_nft` (Python) before submission.                               |

## Faucet / HTTP envelope sentinels

Raised by the SDK when a faucet or MCR response is reachable but unparseable. Surfaced as `ApiError.code` (Python). The JS SDK collapses these into the generic `RuntimeError` envelope path.

| Code                  | Cause                                                                                                                                                           |
| :-------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `INVALID_RESPONSE`    | Faucet returned a non-JSON body.                                                                                                                                |
| `UNEXPECTED_RESPONSE` | Faucet returned JSON but the `status` / `code` / `data` envelope did not match the expected shape for the endpoint.                                             |
| `NETWORK_ERROR`       | Transport-level failure during a faucet call (DNS, connection refused, TLS, timeout via `requests.RequestException`). Same sentinel as the MCR API table above. |

## Client-side error codes

Raised by the SDK itself (not by a chain revert). Surfaced as `RuntimeError.code` (JS) or `ValidationError`/`RpcError` attributes (Python).

| Code                      | Raised by                                   | Cause                                                                                  |
| :------------------------ | :------------------------------------------ | :------------------------------------------------------------------------------------- |
| `MIN_BOND_INVALID_RESULT` | `registerMachine` / `registerFor` (JS only) | `IdentityRegistry.minBond()` returned a non-`bigint` value before submitting the bond. |

***

## Error handling patterns

<Tabs>
  <Tab title="JavaScript">
    ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
    import {
      PeaqosError,
      ValidationError,
      RuntimeError,
      RateLimitExceeded,
    } from "@peaqos/peaq-os-sdk";

    try {
      await client.fundFromGasStation(params, faucetUrl);
    } catch (err) {
      if (err instanceof ValidationError) {
        // fix caller input
      } else if (err instanceof RateLimitExceeded) {
        // back off
      } else if (err instanceof RuntimeError) {
        switch (err.code) {
          case "INVALID_2FA":
            // prompt for a fresh TOTP
            break;
          case "CAP_EXCEEDED_OWNER":
          case "CAP_EXCEEDED_WALLET":
            // surface cap messaging
            break;
          default:
            throw err;
        }
      } else {
        throw err;
      }
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
    from peaq_os_sdk import PeaqosError, ValidationError, ApiError, RpcError

    try:
        client.fund_from_gas_station(
            owner_address=owner,
            target_wallet_address=target,
            chain_id="peaq",
            two_factor_code=totp,
            faucet_base_url=faucet_url,
        )
    except ValidationError:
        # fix caller input
        raise
    except ApiError as err:
        if err.code == "INVALID_2FA":
            # prompt for a fresh TOTP
            pass
        elif err.code in ("CAP_EXCEEDED_OWNER", "CAP_EXCEEDED_WALLET"):
            # surface cap messaging
            pass
        else:
            raise
    except RpcError:
        # retry with backoff
        raise
    ```
  </Tab>
</Tabs>

***

## Related

* [SDK JS](/peaqos/sdk-reference/sdk-js)
* [SDK Python](/peaqos/sdk-reference/sdk-python)
* [Gas Station concept](/peaqos/concepts/gas-station)
* [Install page troubleshooting](/peaqos/install)
