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

# Gateway API

> HTTP entry point for subscription-backed rounds, x402 agent rounds, and settlement.

The Molpha Gateway turns an API request into a signed `DataUpdateResult`. It derives the feed identity, checks authorization or payment, dispatches a node round, validates the aggregate signature, and returns a payload that can be submitted to Solana or verified on EVM/Starknet.

## Base URL

The SDK default (`DEFAULT_GATEWAY_ENDPOINT`) is:

```text theme={null}
https://dev-gateway.molpha.io/
```

## Endpoint overview

| Method | Path                       | Purpose                           |
| ------ | -------------------------- | --------------------------------- |
| `GET`  | `/health`                  | Liveness probe                    |
| `GET`  | `/v1/nodes`                | Node metadata and encryption keys |
| `POST` | `/v1/round/execute`        | Subscription-backed signing round |
| `POST` | `/v1/agent/execute`        | x402 agent pay-per-request round  |
| `GET`  | `/v1/agent/{payer}/status` | Advisory x402 escrow status       |

Agent endpoints require `agent.enabled: true` and a configured gateway keypair (`settlement.gateway_keypair_path`).

## Feed ID derivation

Clients do not send an on-chain create transaction. The gateway and SDK derive the feed identity from the caller, config hash, and quorum:

```text theme={null}
feed_id = keccak256("MOLPHA_JOB_V1" || owner || api_config_hash || [signatures_required])
```

* **Subscription path:** `owner` is `consumerAuthority`.
* **Agent path:** `owner` is the payer.
* `signatures_required` is appended as a single byte.

The gateway derives `feedId` and echoes it in successful responses. The Solana `Feed` account is created lazily on first successful `submit_data_update`. Settlement creates a round receipt; it does not write the feed.

## Subscription-backed rounds

### `POST /v1/round/execute`

Runs a signing round for a subscription owner or active delegate.

```json theme={null}
{
  "subscriptionOwner": "<base58 pubkey>",
  "consumerAuthority": "<base58 pubkey>",
  "signaturesRequired": 3,
  "timestamp": 1718650000,
  "registryVersion": 1,
  "authSig": "<64-byte ed25519 signature, hex or base58>",
  "apiConfig": {
    "url": "https://api.example.com/price",
    "method": "GET",
    "headers": {},
    "responseParser": "$.data.price",
    "valueTransform": "multiply:1e6"
  },
  "encKeyBundle": null
}
```

`consumerAuthority` signs:

```text theme={null}
hash = sha256("MOLPHA_REQAUTH_V1" || feed_id || uint64_le(timestamp))
```

`timestamp` must be within \~300s of the gateway clock. `registryVersion` must equal the current on-chain registry version. The signature is single-use per `(feed_id, timestamp)` — retries need a fresh timestamp and signature. An all-zero `authSig` is rejected.

| Status | Typical cause                                                           |
| ------ | ----------------------------------------------------------------------- |
| `400`  | Validation failure, stale `registryVersion`, malformed body             |
| `401`  | Missing or invalid `authSig`                                            |
| `403`  | Inactive/exhausted subscription, unauthorized delegate, quota committed |
| `409`  | Duplicate round identity                                                |
| `503`  | Node timeout or upstream failure                                        |

## x402 agent rounds

### `POST /v1/agent/execute`

Runs a self-funded pay-per-request round. Agent-specific fields use **snake\_case**; `apiConfig` matches the subscription execute shape.

The payer funds a per-gateway escrow ATA, locks an exact USDC `amount` equal to the computed round price, signs `AgentRequestAuth`, and receives the signed result. Settlement via `settle_agent_round` is asynchronous.

```json theme={null}
{
  "payer": "<base58 pubkey>",
  "canonical_timestamp": 1718650000,
  "signatures_required": 3,
  "amount": 1030,
  "registry_version": 1,
  "agent_request_auth_sig": "<64-byte ed25519 signature>",
  "apiConfig": {
    "url": "https://api.example.com/price",
    "method": "GET",
    "headers": {},
    "responseParser": "$.data.price",
    "valueTransform": "multiply:1e6"
  },
  "encKeyBundle": null
}
```

Round price (USDC atomic units):

```text theme={null}
price = protocol_config.agent_round_base
      + signatures_required × protocol_config.agent_round_per_sig
```

`amount` must equal that price exactly. `agent_request_auth_sig` may be omitted on the unfunded first request of the 402 discovery flow.

`AgentRequestAuth` hash:

```text theme={null}
body = borsh(agent_escrow_pda, gateway_pda, feed_id, canonical_timestamp_le, amount_le)
hash = sha256("MOLPHA_AGENT_REQAUTH_V1" || body)
sig  = ed25519_sign(payer, hash)
```

If the escrow ATA is underfunded (available balance = ATA − committed unsettled amounts), the gateway returns a standard x402 envelope:

```json theme={null}
{
  "x402Version": 1,
  "error": "payment required: escrow ATA underfunded",
  "accepts": [{
    "scheme": "exact",
    "network": "solana-devnet",
    "maxAmountRequired": "1030",
    "payTo": "<escrow ATA>",
    "asset": "<USDC mint>",
    "resource": "/v1/agent/execute",
    "description": "Molpha agent round for feed 0x…",
    "maxTimeoutSeconds": 60,
    "extra": {
      "agent": "<escrow PDA>",
      "gateway": "<gateway PDA>",
      "feedId": "0x…",
      "canonicalTimestamp": 1718650000,
      "amount": "1030",
      "payer": "<payer>",
      "currentAtaBalance": "0",
      "committedAmount": "0",
      "note": "fund at least the shortfall, sign AgentRequestAuth over (agent, gateway, feedId, canonicalTimestamp, amount), then retry with the same canonical_timestamp"
    }
  }]
}
```

402 flow:

1. Call execute (signature optional) → receive 402.
2. Create/fund `payTo` with at least `maxAmountRequired` USDC.
3. Sign `AgentRequestAuth` over the `extra` fields.
4. Retry with the **same** `canonical_timestamp` and the signature.

| Status | Typical cause                                                              |
| ------ | -------------------------------------------------------------------------- |
| `400`  | Validation failure, `amount` ≠ price, stale `registry_version` / timestamp |
| `401`  | Missing or invalid `agent_request_auth_sig` on a funded request            |
| `402`  | Escrow ATA underfunded or missing                                          |
| `403`  | Escrow paused or payer/authority mismatch                                  |
| `409`  | Duplicate `canonical_timestamp` for this feed identity                     |
| `503`  | Node round timeout or upstream failure                                     |

### `GET /v1/agent/{payer}/status`

Advisory read of escrow state. Optional query `signatures_required` (default `1`) controls the price quote. The escrow is per `(payer, gateway)` — the gateway derives and returns both addresses.

```json theme={null}
{
  "payer": "<base58 pubkey>",
  "gateway": "<gateway PDA>",
  "escrow": "<escrow PDA>",
  "exists": true,
  "authority": "<base58 pubkey>",
  "status": "Active",
  "ataAddress": "<ATA>",
  "ataExists": true,
  "ataBalance": "2060",
  "committedAmount": "1030",
  "quotedNextPrice": "1030",
  "unsettledRounds": 1
}
```

USDC amounts are decimal strings of atomic units. `exists: false` is normal before the first successful settlement.

## Data response

Successful round responses return the same signed payload shape. Live rounds always set `fresh: true`.

```json theme={null}
{
  "status": "completed",
  "data": {
    "feedId": "1a2b3c...",
    "value": "65432.10",
    "valuePacked": "0x...",
    "timestamp": 1718650000,
    "registryVersion": 1,
    "signaturesRequired": 3,
    "configHash": "0x...",
    "signersBitmap": "0x07",
    "s": "0x...",
    "rx": "0x...",
    "ryParity": 0,
    "commitmentAddr": "0x...",
    "fresh": true
  }
}
```

## Serve lifecycle

1. Validate JSON, pubkeys, timestamps, and API config.
2. Derive `api_config_hash` and `feed_id`.
3. Verify request auth or x402 funding + `AgentRequestAuth`.
4. Read advisory on-chain state.
5. Reserve the round in the settlement outbox.
6. Dispatch to nodes.
7. Validate returned aggregate signature identity.
8. Complete the outbox record (`pending`).
9. Return the signed payload.

Settlement worker drains `pending` rows into `settle_round_receipt` or `settle_agent_round` when `settlement.enabled` is true.

## Round-record PDAs

Replay protection is the unique round-record PDA `init`:

| Path                    | Seeds                                                                                                      |
| ----------------------- | ---------------------------------------------------------------------------------------------------------- |
| Subscription / delegate | `["molpha_round", consumer_authority, api_config_hash, signatures_required, timestamp_le]`                 |
| x402 agent              | `["molpha_agent_round", authority, gateway, api_config_hash, signatures_required, canonical_timestamp_le]` |

## Self-hosting

The gateway is a Go service configured by YAML and environment variables. There is no separate operator “run-gateway” guide — use this section and the gateway repository config.

```yaml theme={null}
server:
  port: 8080

solana:
  rpc_url: https://api.devnet.solana.com
  program_id: MoLFGDpFoVnQgwbkTNScKPohCxhbfd61JjFrnotuwzh
  usdc_mint: 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU

agent:
  enabled: true
  protocol_config_ttl_seconds: 30
  x402_network: solana-devnet
  timestamp_skew_seconds: 300

settlement:
  enabled: true
  db_path: data/settlement-outbox.db
  gateway_keypair_path: /secure/path/gateway.json
  settle_interval_seconds: 10
  settle_batch_limit: 25
  settle_max_attempts: 10
```

When `agent.enabled` is true, `settlement.gateway_keypair_path` is required because `AgentRequestAuth` binds the gateway PDA. If settlement is disabled, served rounds can remain pending indefinitely.

Settlement uses the on-chain `protocol_config.usdc_mint`; `solana.usdc_mint` only overrides serve-time ATA derivation and must match on-chain.

## Operational notes

* Use durable storage for the settlement outbox.
* Monitor pending outbox records and round timeout rates.
* Keep Solana RPC latency low for escrow and subscription reads.
* Verify returned payloads before application use.
