Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions website/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ const withNextra = nextra({
type: 'children',
title: t('global.navigation.indexing'),
},
'---gateways': {
type: 'separator',
},
gateways: {
type: 'children',
title: 'Gateway Software',
},
'---5': {
type: 'separator',
},
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
82 changes: 82 additions & 0 deletions website/public/img/gateway/gateway-architecture.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 61 additions & 0 deletions website/public/img/gateway/gateway-kafka-topics.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions website/public/img/gateway/subgraph-gateway-black.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions website/public/img/gateway/subgraph-gateway-white.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions website/src/layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,30 @@ export default function Layout({ pageOpts, children }: NextraThemeLayoutProps<Fr
if (routeWithoutLocale === '/indexing' || routeWithoutLocale.startsWith('/indexing/')) {
return <RoleIndexer alt="" />
}
if (routeWithoutLocale === '/gateways' || routeWithoutLocale.startsWith('/gateways/')) {
const maskUrl = `url(${process.env.BASE_PATH ?? ''}/img/gateway/subgraph-gateway-icon.png)`
return (
<span
aria-hidden="true"
style={{
display: 'inline-block',
// Match the sibling GDS nav icons: the NavigationItem icon slot sizes icons to size-4 (1rem)
width: '1rem',
height: '1rem',
backgroundColor: 'currentColor',
WebkitMaskImage: maskUrl,
maskImage: maskUrl,
WebkitMaskRepeat: 'no-repeat',
maskRepeat: 'no-repeat',
WebkitMaskPosition: 'center',
maskPosition: 'center',
// Inset the mark to ~75% so its visible size matches the sibling line-icons (which fill ~0.73-0.88 of their 16px box)
WebkitMaskSize: '75%',
maskSize: '75%',
}}
/>
)
}
if (routeWithoutLocale === '/graph-horizon' || routeWithoutLocale.startsWith('/graph-horizon/')) {
return <Horizon alt="" />
}
Expand Down
3 changes: 3 additions & 0 deletions website/src/pages/en/gateways/_meta-titles.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"subgraphs": "Subgraph Gateway"
}
7 changes: 7 additions & 0 deletions website/src/pages/en/gateways/_meta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import titles from './_meta-titles.json'

// Gateway Software groups the different gateway offerings in The Graph ecosystem.
// Add future gateway software packages here to control their order.
export default {
subgraphs: titles['subgraphs'] ?? '',
}
6 changes: 6 additions & 0 deletions website/src/pages/en/gateways/subgraphs/_meta-titles.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"components": "Gateway Components",
"consumer-side": "Consumer-Side",
"supply-side": "Supply-Side",
"ecosystem": "Ecosystem Contributions"
}
9 changes: 9 additions & 0 deletions website/src/pages/en/gateways/subgraphs/_meta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import titles from './_meta-titles.json'

export default {
overview: '',
components: titles['components'] ?? '',
'consumer-side': titles['consumer-side'] ?? '',
'supply-side': titles['supply-side'] ?? '',
ecosystem: titles['ecosystem'] ?? '',
}
8 changes: 8 additions & 0 deletions website/src/pages/en/gateways/subgraphs/components/_meta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default {
overview: '',
'operating-a-gateway': '',
'indexer-selection': 'Indexer Selection',
'graph-tally': '',
titorelli: '',
'gateway-in-a-box': '',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
---
title: Gateway in a Box (Beta)
---

Gateway in a Box (gib) is a self-hostable Subgraph Gateway for The Graph Horizon on Arbitrum One, packaged in a Docker Compose bundle. It exists to let a service provider stand up a working, TAP-native, Horizon-ready Gateway without a multi-day setup ceremony. It is a community reference deployment (a [Nightswatch](https://github.com/nightswatchhq/gib) project, MIT licensed), not an official Graph Foundation release, and it is a good starting point for understanding what a minimal production Gateway actually runs.

## What is in the Box

Under the hood, gib wires together a Gateway fork (TAP v2 / Horizon-native) plus the `graph-tally` aggregator and escrow-manager, a Redpanda bus, and optional Prometheus and Grafana, all configured from a single `.env` file. An Operator's consumer API keys are their own; no Subgraph Studio account is needed for an Operator's users (topology bootstrap currently does require a read-only Studio key, discussed below).

| Service | Role |
| ------------------------ | ---------------------------------------------------------------------------- |
| `gateway` | Routes queries, signs TAP v2 receipts |
| `tap-aggregator` | Public endpoint that aggregates receipts into RAVs for Indexers |
| `escrow-manager` | Auto-authorizes the signer and tops up escrow |
| `redpanda` | Kafka-API bus for receipts and attestations (the fee-metering bus, required) |
| `prometheus` + `grafana` | Optional `monitoring` profile: escrow and query dashboards |

```
Gateway in a Box
┌──────────────────────────────────────────────┐
client ──query──▶ │ gateway ── signs TAP v2 receipts │
(Bearer key) │ │ │
│ ├─▶ trusted_indexers ─▶ network subgraph │ indexer discovery
│ └─▶ selected indexers ─▶ query results │
│ │
│ redpanda ◀─ query fee records (metering) │
│ │ │
│ escrow-manager ─▶ PaymentsEscrow (on-chain) │ auto top-up
│ tap-aggregator ─▶ RAVs for indexers (public)│
└──────────────────────────────────────────────┘
```

## Footprint

gib is light. Measured on Arbitrum One with the full network topology resident (around 16,000 Subgraphs, 26,000 deployments, 12,500 indexings):

| Component | Resident memory |
| --------------- | --------------- |
| gateway (VmRSS) | around 207 MB |
| redpanda | around 330 MB |
| tap-aggregator | around 10 MB |
| Full stack | around 570 MB |

A 2 GB, 1 vCPU box runs it comfortably. A Gateway holds the network topology in memory, but that cost is around 200 MB in practice. Disk needs are a few GB for images plus Redpanda retention.

## Quickstart

Prerequisites: Docker and Compose, `gettext-base` (`envsubst`), `python3`, and optionally Foundry's `cast` for key generation (Docker is used as a fallback). A funded sender wallet is only needed for real payments.

```sh
git clone https://github.com/nightswatchhq/gib && cd gib

cp .env.example .env # 1. fill the TODOs (SENDER_ADDRESS, TOPOLOGY_STUDIO_KEY, GATEWAY_API_KEYS)
./scripts/fetch-addresses.sh # 2. auto-fill verified Horizon addresses -> config/addresses.env
./scripts/gen-keys.sh # 3. generate sender + signer keys -> ./secrets
./scripts/render.sh # 4. render runtime/{gateway.json,escrow-manager.json,.env}
docker compose --env-file runtime/.env up -d # 5. go
```

gib ships payment-safe by default: `PAYMENT_REQUIRED=false` and `ESCROW_DRY_RUN=true`, so an Operator can validate query routing before a single wei moves. Flip both when an Operator is ready for real payments. See [Managing Escrow](/gateways/subgraphs/supply-side/managing-escrow/).

## The Smoke Test

`gib smoke` is the self-test every operator should run before asking any Indexer to whitelist them. It runs one command against a running deployment, touches nothing on-chain, and needs no funded keys:

```sh
docker compose --profile smoke run --rm smoke
```

It self-configures from the Operator's rendered `runtime/gateway.json` and prints a pass/fail table. The checks prove, end to end, that topology synced, a real query selected candidate Indexers and attached receipts, the running Gateway signed with the Operator's configured signer, those receipts aggregate into a RAV that recovers to the Operator's signer with the correct EIP-712 domain and value, RAV fields match the Operator's sender and SubgraphService, and tampered or wrong-key receipts are rejected. A green result is the credibility artifact an Operator brings to Indexers.

## The Onboarding Wall (Why Fresh Deployments Return 402)

A fresh gib deployment signs valid receipts that every Indexer rejects with a `402`. This is protocol design, not a defect, and it is the one part of running a Gateway an Operator cannot do alone.

Indexers only serve paid queries from senders they have explicitly whitelisted. The whitelist is the Indexer's `[tap.sender_aggregator_endpoints]` config: a map of `sender address` to `your aggregator URL`. It is simultaneously the trust list (whose receipts the Indexer will accept) and the address book (where the Indexer's `tap-agent` sends receipts to be turned into RAVs). A sender that is not in the map is rejected, because an unredeemable receipt is a loss the Indexer eats.

An established Gateway avoids this wall only because every Indexer already carries its sender in their config out of the box. That pre-installed trust is the onboarding moat; it is protocol-level and social, not technical, and it is the same wall any independent Gateway faces. Getting Indexers to accept a Gateway is covered in [Routing Queries to Indexers](/gateways/subgraphs/supply-side/routing-queries/), and the aggregator handshake in [Managing Escrow](/gateways/subgraphs/supply-side/managing-escrow/).

A realistic onboarding sequence is: pick the target Subgraphs an Operator's users query, identify the Indexers serving them with good QoS, make the ask to each (the Operator's `gib smoke` output, the one config line mapping the Operator's sender to the Operator's aggregator URL, and confirmation the Operator funded per-Indexer escrow), verify with a paid query, then expand. Because a Gateway selects up to three Indexers per query, three or more accepting Indexers per target Subgraph is the practical point at which that Subgraph serves reliably.

## Deploying a Subgraph Gateway with Skill

For operators using Claude Code, the [companion `gib-skill` plugin](https://github.com/nightswatchhq/gib-skill) acts as a concierge for the whole flow. It is not a generator (gib's own scripts do the mechanics); it adds the judgment around them. The skill interviews an Operator (target box, vhost domains, topology source, key count, fee target, stage, monitoring), hardens the box if it is shared, deploys gib at its pinned release, runs `gib smoke`, and interprets the result, then hands off with exactly what is proven and what still needs escrow and Indexer onboarding.

```
/deploy-gateway
```

Key hygiene is non-negotiable in the skill: sender and signer private keys are generated on the box and never leave it. The only key allowed through chat is the read-only topology (Studio) key, which signs nothing and holds no funds.

## Status and Boundary

gib verifies the payment path up to a signed, verified RAV: signing, EIP-712 domain, Gateway and aggregator signer consistency, and aggregation, all provable by the operator alone. What remains is on-chain and cooperation-dependent: the Indexer's whitelist entry, escrow funding, and the Indexer redeeming RAVs against the Operator's escrow on-chain. A fresh Gateway returns `402` to paid queries by design until an operator funds escrow and Indexers whitelist the sender; that `402` is itself the evidence that the receipts are valid. Every operator must still supply a topology source, fund escrow, and get Indexers to whitelist their sender.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
title: Graph Tally for Gateway Operators
sidebarTitle: Graph Tally
---

Graph Tally is the trust-minimized micropayment system that Subgraph Gateways use to pay Indexers for the queries they serve. It is what makes per-query payment economically viable: instead of one on-chain transaction per query, Indexers are paid through signed off-chain receipts that settle on-chain only periodically.

Graph Tally was formerly known as TAP (Timeline Aggregation Protocol). The Gateway software documented here is Horizon-ready and generates TAP v2 receipts exclusively where "TAP v2" and "Graph Tally" refer to the same thing. Source and services now live in [`graphprotocol/graph-tally`](https://github.com/graphprotocol/graph-tally). The older [`tap-escrow-manager`](https://github.com/edgeandnode/tap-escrow-manager) repo is archived and superseded by the escrow manager in `graph-tally`. Specs: [GIP-0054](https://github.com/graphprotocol/graph-improvement-proposals/blob/main/gips/0054-timeline-aggregation-protocol.md) (original TAP) and [GIP-0066](https://github.com/graphprotocol/graph-improvement-proposals/blob/main/gips/0066-graph-horizon.md) (Graph Horizon / TAP v2).

## How Graph Tally Scales Micropayments

The Graph aims to support all queries for blockchain data. The most active Indexers already serve on the order of 500,000+ queries per day. Settling each query with its own on-chain transfer is impractical: even at roughly $0.09 per transfer on a low-cost L2, 500,000 queries per day would cost an Indexer about $45,000 per day in fees alone. Graph Tally moves payment off-chain and settles in batches, so the dominant on-chain cost becomes verifying a single ECDSA signature (around 3,000 gas, roughly $0.015 per redemption on Arbitrum at the figures in the source post).

### Graph Tally's Core Properties

- **High-throughput:** the bottleneck is ECDSA sign and verify, one of the fastest crypto operations.
- **Stateless and scalable:** senders do not track totals, enabling distributed Gateways.
- **Trust-minimized:** Receivers configure a risk threshold and can settle on-chain without further Sender interaction.
- **Cost-efficient:** one signature verification per redemption instead of one transaction per query.

## How Graph Tally Works

Payments in The Graph Subgraph Network flows in one direction only -- from Gateways (the Sender) to Indexers (the Receiver). That one-way property is what removes the need for a dispute window: the Receiver has every incentive to post the latest, highest signed amount, so as long as receipts are validly signed there is nothing to dispute. Senders are also stateless: they sign the amount owed for each individual query rather than a running total, which lets a Gateway run distributed, horizontally-scaled senders.

```
Gateway (Sender) ──signed receipt per query──▶ Indexer (Receiver)
▲ │
│ RAV (signed aggregate) │ batches receipts
└─────────── aggregator ◀─────────────────────┘
│ redeems RAV
escrow manager ──funds──▶ Horizon escrow ◀──┘
```

The Graphy Tally payment flow goes as follows:

1. **Receipts:** the Gateway sends a signed Receipt to the Indexer alongside each query, stating the amount owed for that single request (EIP-712 / ECDSA signed).
2. **Accumulation:** the Indexer verifies and stores receipts, tallying the running total owed.
3. **RAV request:** once the accumulated total reaches a threshold the Indexer is willing to risk, it sends all receipts up to a timestamp back to the Sender and requests aggregation.
4. **RAV:** the Sender verifies the batch, computes the total, and returns a signed Receipt Aggregate Voucher (RAV), a single message representing the cumulative amount owed. Timestamps prevent double-spending: only receipts newer than the previous RAV's timestamp are added.
5. **Settlement:** the Indexer can redeem the latest RAV on-chain against the Gateway's escrow to claim payment, and may accumulate multiple RAVs before submitting a single on-chain transaction.

Because the Sender can decline to aggregate, the Receiver sets its threshold according to how much it trusts the Sender: the trust-minimized (not fully trustless) property of the system.

## What Gateway Operators Run

A Gateway Operator acts as the TAP Sender and runs two supporting services alongside the Gateway. Both are part of `graphprotocol/graph-tally`:

| Service | Binary | Role |
| --- | --- | --- |
| Aggregator | `graph_tally_aggregator` | Public endpoint where Indexers submit batches of receipts and receive signed RAVs in return |
| Escrow manager | `graph_tally_escrow_manager` | Maintains the on-chain GRT escrow balance the Gateway pays Indexers from, depositing enough to cover outstanding receipts |

### Escrow Manager in Detail

The escrow manager keeps the [Graph Horizon `PaymentsEscrow`](https://github.com/graphprotocol/contracts/blob/main/packages/horizon/contracts/payments/PaymentsEscrow.sol) funded so Indexers can always redeem. It monitors:

- the [Network Subgraph](https://thegraph.com/explorer/subgraphs/DZz4kDTdmzWLWsV373w2bSmoar3umKKH9y82SUKr5qmp?view=Query&chain=arbitrum-one) for active allocations, escrow accounts, and authorized signers, and
- Kafka topics for receipts and RAVs, which track outstanding debt to each Receiver.

It then deposits GRT to keep balances ahead of what is owed. Startup is faster when the `gateway_indexer_fees_hourly` topic (produced by [Titorelli](/gateways/subgraphs/components/titorelli/)) is available, since it can read pre-aggregated fee history instead of replaying raw data.

Key config options (single JSON file): `authorize_signers` (auto-authorize signers on startup), `dry_run` (skip contract calls for testing), `update_interval_seconds` (main loop polling), and `port_metrics` (Prometheus port, default 9090). Useful escrow metrics include `escrow_total_debt_grt`, `escrow_total_balance_grt`, `escrow_debt_grt{receiver}`, `escrow_balance_grt{receiver}`, and `escrow_deposit_ok` / `escrow_deposit_err`. See [Managing Escrow](/gateways/subgraphs/supply-side/managing-escrow/).

## Wallets Gateway Operators Must Manage

Graph Tally requires the Gateway Operator to manage at least two wallets:

- **Sender:** needs ETH for transaction gas and GRT to allocate into TAP escrow to pay Indexers.
- **Authorized signer:** used by the Gateway and aggregator to sign v2 receipts and RAVs. Signers must be authorized on-chain against the sender (the escrow manager can do this automatically when `authorize_signers` is `true`, or an Operator can call `authorizeSigner` manually on the escrow contract).

See [Operating a Gateway](/gateways/subgraphs/components/operating-a-gateway/) for the full wallet, funding, and signer-authorization setup.
Loading
Loading