Skip to main content

Payments

Dispatch uses GraphTally (TAP v2) — the same payment infrastructure used by The Graph’s SubgraphService. GRT moves off-chain per request via signed receipts, then settles on-chain in batches.

End-to-end flow

1. Consumer deposits GRT into PaymentsEscrow (keyed by their own address as payer)
2. Consumer includes X-Consumer-Address header on every gateway request
3. Per request: gateway signs a TAP receipt (EIP-712 ECDSA, random nonce, CU-weighted value)
   — consumer address is encoded in receipt metadata so the correct escrow is charged
4. Receipt sent in TAP-Receipt header alongside JSON-RPC request to dispatch-service
5. dispatch-service extracts consumer address from metadata, checks their escrow balance,
   validates signature, persists receipt to PostgreSQL
6. dispatch-service TAP aggregator batches receipts per consumer → sends to /rav/aggregate
7. Gateway returns a signed RAV (Receipt Aggregate Voucher) with payer = consumer address
8. dispatch-service collector submits RAV on-chain every hour: RPCDataService.collect()
                               → GraphTallyCollector (verifies EIP-712, tracks cumulative value)
                               → PaymentsEscrow (draws GRT from consumer's escrow)
                               → GraphPayments (distributes: protocol tax → delegators → provider)
                               → GRT lands at paymentsDestination
valueAggregate in a RAV is cumulative and never resets. Each collect() call computes the delta from the last collected value. This means a lost RAV doesn’t lose funds — the next RAV covers the gap.

EIP-712 domain

All receipts and RAVs are signed against this domain on Arbitrum One:
name: protocol-configured
version: "1"
chainId: 42161
verifyingContract: 0x8f69F5C07477Ac46FBc491B1E6D91E2bb0111A9e  // GraphTallyCollector
The data_service field in every receipt is set to RPCDataService’s address, preventing cross-service receipt replay.

CU-weighted pricing

Request value is proportional to compute units (CUs) — a weight assigned per method.
MethodCU
eth_chainId, net_version, eth_blockNumber1
eth_getBalance, eth_getTransactionCount, eth_getCode, eth_getStorageAt5
eth_sendRawTransaction, eth_getBlockByHash/Number5
eth_call, eth_estimateGas, eth_getTransactionReceipt, eth_getTransactionByHash10
eth_getLogs (bounded)20
debug_traceTransaction500+
Receipt value = CU x base_price_per_cu. Default base_price_per_cu is 4_000_000_000_000 GRT wei (~$40/million requests at $0.09 GRT).

TAP receipt overhead

Receipt processing must not slow down requests. In practice:
OperationLatency
ECDSA signature verification~0.1ms
Receipt storage (async, not on critical path)~0ms
Total overhead<1ms

Stake locking

On each collect(), RPCDataService locks fees x stakeToFeesRatio in a stake claim via DataServiceFees._createStakeClaim(). The claim releases after thawingPeriod. This ensures providers maintain sufficient economic stake relative to fees collected. Default stakeToFeesRatio is 5 — consistent with SubgraphService.

Payments destination

The GRT recipient on collect() is paymentsDestination[serviceProvider], not necessarily the provider’s staking address. Providers can separate their operator key from their payment wallet via setPaymentsDestination(address). Defaults to the provider address on registration.