> ## 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.

# Gas Station

> 2FA-gated faucet that funds fresh machine wallets on peaq chain so they can bond and register.

Gas Station is a 2FA-gated faucet that funds fresh machine wallets on peaq chain so they can bond and register.

## Why Gas Station exists

A newly generated machine keypair has zero balance. Before it can register an identity (which requires bonding 1 PEAQ), it needs native tokens for gas. Gas Station solves this bootstrap problem: the owner enrolls in 2FA once, then funds each machine wallet with a single SDK call.

## 2FA setup flow

<Steps>
  <Step title="Initiate 2FA enrollment">
    Call `setupFaucet2FA` with the owner's address. The faucet returns an `otpauthUri` for manual entry and a `qrImageUrl` for scanning.

    <CodeGroup>
      ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
      const setup = await client.setupFaucet2FA(
        ownerAddress,
        "https://depinstation.peaq.network"
      );
      console.log(setup);
      // setup.otpauthUri  : paste into authenticator app
      // setup.qrImageUrl  : render immediately, expires in ~2 minutes
      ```

      ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
      setup = client.setup_faucet_2fa(
          owner_address="0xOwner...",
          faucet_base_url="https://depinstation.peaq.network",
      )
      print(setup)
      # setup["otpauth_uri"]  : paste into authenticator app
      # setup["qr_image_url"] : render immediately, expires in ~2 minutes
      ```
    </CodeGroup>
  </Step>

  <Step title="Scan QR code with authenticator app">
    Open any TOTP-compatible authenticator (Google Authenticator, Authy, 1Password). Scan the QR code or manually enter the `otpauthUri`. The QR image expires after approximately 2 minutes.
  </Step>

  <Step title="Confirm 2FA activation">
    Submit the current TOTP code from the authenticator app to activate 2FA for this owner address.

    <CodeGroup>
      ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
      await client.confirmFaucet2FA(
        ownerAddress,
        "https://depinstation.peaq.network",
        "123456" // TOTP code from authenticator
      );
      ```

      ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
      client.confirm_faucet_2fa(
          owner_address="0xOwner...",
          faucet_base_url="https://depinstation.peaq.network",
          two_factor_code="123456",
      )
      ```
    </CodeGroup>
  </Step>
</Steps>

## Funding a machine wallet

After 2FA is active, fund any machine wallet with `fundFromGasStation`. The response is a discriminated union: either `success` (transfer landed) or `skipped` (wallet already funded).

<CodeGroup>
  ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
  const result = await client.fundFromGasStation(
    {
      ownerAddress: "0xOwner...",
      targetWalletAddress: machineAddress,
      chainId: "peaq",
      twoFactorCode: "654321",
    },
    "https://depinstation.peaq.network"
  );

  if (result.status === "success") {
    console.log("Funded:", result.txHash, result.fundedAmount);
  } else {
    // status === "skipped"
    console.log("Already funded:", result.currentBalance);
  }
  ```

  ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
  result = client.fund_from_gas_station(
      owner_address="0xOwner...",
      target_wallet_address=machine_address,
      chain_id="peaq",
      two_factor_code="654321",
      faucet_base_url="https://depinstation.peaq.network",
  )

  if result["status"] == "success":
      print("Funded:", result["tx_hash"], result["funded_amount"])
  else:
      # status == "skipped"
      print("Already funded:", result["current_balance"])
  ```
</CodeGroup>

### Success response

JS (camelCase). The SDK retains the idempotency key on the response so callers can persist it for retry recovery:

| Field          | Type                 | Description                                                  |
| :------------- | :------------------- | :----------------------------------------------------------- |
| `status`       | `"success"`          | Transfer completed                                           |
| `requestId`    | string               | Idempotency key (echoed from request, or SDK-generated UUID) |
| `txHash`       | `0x`-prefixed hex    | Transaction hash of the funding transfer                     |
| `fundedAmount` | string (decimal wei) | Amount transferred                                           |

Python (snake\_case). The SDK does not echo `request_id` on the response. If you need it for retry recovery, pass it in explicitly and hold the value yourself:

| Field           | Type                 | Description                              |
| :-------------- | :------------------- | :--------------------------------------- |
| `status`        | `"success"`          | Transfer completed                       |
| `tx_hash`       | `0x`-prefixed hex    | Transaction hash of the funding transfer |
| `funded_amount` | string (decimal wei) | Amount transferred                       |

### Skipped response

JS (camelCase):

| Field            | Type                 | Description                           |
| :--------------- | :------------------- | :------------------------------------ |
| `status`         | `"skipped"`          | Wallet already has sufficient balance |
| `requestId`      | string               | Idempotency key                       |
| `currentBalance` | string (decimal wei) | Target wallet's current balance       |
| `minGasBalance`  | string (decimal wei) | Faucet-configured minimum threshold   |

Python (snake\_case):

| Field             | Type                 | Description                           |
| :---------------- | :------------------- | :------------------------------------ |
| `status`          | `"skipped"`          | Wallet already has sufficient balance |
| `current_balance` | string (decimal wei) | Target wallet's current balance       |
| `min_gas_balance` | string (decimal wei) | Faucet-configured minimum threshold   |

## Rate limits and caps

Gas Station enforces three independent throttles to prevent abuse, plus a per-chain minimum balance threshold that decides whether a funding request transfers anything at all:

| Limit               | Scope      | Surfaces as           | Description                                                                                                                                                                                  |
| :------------------ | :--------- | :-------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Rate limit          | Per owner  | `RATE_LIMITED`        | Too many funding requests in too short a window. Back off and retry.                                                                                                                         |
| Daily funding cap   | Per owner  | `CAP_EXCEEDED_OWNER`  | Total funded amount per owner per day.                                                                                                                                                       |
| Daily funding cap   | Per wallet | `CAP_EXCEEDED_WALLET` | Total funded amount per target wallet per day.                                                                                                                                               |
| Minimum gas balance | Per chain  | `skipped` response    | When the target wallet already holds at least this much, the faucet returns `skipped` with `currentBalance`/`current_balance` and `minGasBalance`/`min_gas_balance` instead of transferring. |

<Note>
  Exact cap values, rate-limit windows, and the minimum gas balance are configured server-side and are subject to change. Branch on the error codes (or the `skipped` status) rather than hard-coding numbers.
</Note>

## Error codes

Gas Station defines 20 error codes. Not every endpoint can raise every code; the subset depends on what the endpoint actually does.

### `POST /2fa/setup`: `setupFaucet2FA` / `setup_faucet_2fa`

| Code                    | Meaning                                                          |
| :---------------------- | :--------------------------------------------------------------- |
| `INVALID_OWNER_ADDRESS` | Owner address failed server-side format validation (setup-only). |
| `INVALID_PAYLOAD`       | Request body did not match the spec (e.g. unknown `format`).     |
| `QR_GENERATION_FAILED`  | The faucet failed to render the QR image (setup-only).           |
| `INTERNAL_ERROR`        | Unhandled server-side failure.                                   |

### `POST /2fa/confirm`: `confirmFaucet2FA` / `confirm_faucet_2fa`

| Code                 | Meaning                                                   |
| :------------------- | :-------------------------------------------------------- |
| `INVALID_2FA`        | TOTP code did not match the secret.                       |
| `2FA_NOT_CONFIGURED` | Owner never completed setup. Call `setupFaucet2FA` first. |
| `2FA_LOCKED`         | Owner locked after too many invalid attempts.             |
| `INTERNAL_ERROR`     | Unhandled server-side failure.                            |

### `POST /faucet/fund`: `fundFromGasStation` / `fund_from_gas_station`

| Code                        | Meaning                                                                                                                  |
| :-------------------------- | :----------------------------------------------------------------------------------------------------------------------- |
| `INVALID_2FA`               | TOTP code did not match.                                                                                                 |
| `2FA_NOT_CONFIGURED`        | Owner never completed setup.                                                                                             |
| `2FA_NOT_ACTIVE`            | Setup done but never confirmed. Call `confirmFaucet2FA` first.                                                           |
| `2FA_LOCKED`                | Owner locked after too many invalid attempts.                                                                            |
| `DUPLICATE_REQUEST`         | A request with this `requestId` is still in flight.                                                                      |
| `REQUEST_ALREADY_PROCESSED` | This `requestId` has already completed.                                                                                  |
| `RATE_LIMITED`              | Faucet rate limit exceeded.                                                                                              |
| `CAP_EXCEEDED_OWNER`        | Owner daily funding cap exceeded.                                                                                        |
| `CAP_EXCEEDED_WALLET`       | Wallet daily funding cap exceeded.                                                                                       |
| `INVALID_PAYLOAD`           | Request body validation failed server-side.                                                                              |
| `INVALID_OWNER_ADDRESS`     | Malformed `ownerAddress`.                                                                                                |
| `INVALID_TARGET_ADDRESS`    | Malformed `targetWalletAddress`.                                                                                         |
| `INVALID_CHAIN_ID`          | Unsupported `chainId`.                                                                                                   |
| `INVALID_REQUEST_ID`        | The supplied `requestId` was not a valid UUID.                                                                           |
| `TRANSFER_FAILED`           | On-chain transfer failed.                                                                                                |
| `CHAIN_RPC_ERROR`           | Faucet's upstream RPC provider failed.                                                                                   |
| `QR_NOT_FOUND`              | QR token does not exist (rare; surfaces when a stale QR is referenced).                                                  |
| `QR_EXPIRED`                | QR token has expired.                                                                                                    |
| `INTERNAL_ERROR`            | Unhandled server-side failure. The SDK also raises this code locally for non-JSON bodies and unexpected envelope shapes. |

For SDK-owned message strings and HTTP status codes per code, see [errors](/peaqos/sdk-reference/errors).

## Cross-links

* [JS SDK faucet methods](/peaqos/sdk-reference/sdk-js#gas-station) documents `setupFaucet2FA`, `confirmFaucet2FA`, and `fundFromGasStation`
* [Python SDK faucet methods](/peaqos/sdk-reference/sdk-python#gas-station) documents the Python equivalents
* [Self-managed onboarding guide](/peaqos/guides/self-managed-onboarding) shows Gas Station in the full registration flow
* [Errors reference](/peaqos/sdk-reference/errors) lists all error codes with HTTP status codes
