Using the Network

Two ways to consume the Dispatch network: hit the gateway directly or use the consumer SDK (trustless, signs receipts locally). Both require GRT in your escrow — there are no free queries.


Via the Gateway

The gateway handles provider selection and TAP receipt signing. You must include your Ethereum address in every request via the X-Consumer-Address header — the gateway encodes it into the TAP receipt so GRT is drawn from your escrow on-chain, not the gateway's.

Live gateway: https://gateway.lodestar-dashboard.com

# curl
curl -s -X POST https://gateway.lodestar-dashboard.com/rpc/42161 \
  -H "Content-Type: application/json" \
  -H "X-Consumer-Address: 0xYOUR_ADDRESS" \
  -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'

# Check the attestation header
curl -si -X POST https://gateway.lodestar-dashboard.com/rpc/42161 \
  -H "Content-Type: application/json" \
  -H "X-Consumer-Address: 0xYOUR_ADDRESS" \
  -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
  | grep -E "x-drpc-attestation|result"

With ethers.js or viem:

import { createPublicClient, http } from "viem";
import { arbitrum } from "viem/chains";

const client = createPublicClient({
  chain: arbitrum,
  transport: http("https://gateway.lodestar-dashboard.com/rpc/42161", {
    fetchOptions: {
      headers: { "X-Consumer-Address": "0xYOUR_ADDRESS" },
    },
  }),
});

const block = await client.getBlockNumber();

Missing the X-Consumer-Address header returns 402 Payment Required. Requests from addresses with no funded escrow are rejected by the provider. See Funding the escrow below.

Routes:

POST /rpc/{chain_id}     # chain ID in path
POST /rpc                # chain ID via X-Chain-Id header

Currently live: Arbitrum One (42161) — Standard and Archive tiers.


dispatch-proxy (drop-in local server)

The easiest way to point any existing app at the Dispatch network without changing application code. Starts a standard JSON-RPC HTTP server on localhost; MetaMask, Viem, Ethers.js, and curl all work against it without modification.

cd proxy
npm install
npm start

On first run the proxy auto-generates a consumer keypair, saves it to ./consumer.key, and tells you where to fund escrow. No key needed upfront.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
dispatch-proxy v0.1.0
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Chain:     Ethereum Mainnet (1)
Listening: http://localhost:8545
Consumer:  0xABCD...1234
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠  New consumer key generated → ./consumer.key
Fund escrow at:  https://lodestar-dashboard.com/dispatch
Consumer address: 0xABCD...1234
Or use an existing funded key: DISPATCH_SIGNER_KEY=0x...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Add to MetaMask  →  Settings → Networks → Add a network
  RPC URL:  http://localhost:8545
  Chain ID: 1
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[12:34:56] ✓ eth_blockNumber      42ms  0.000004 GRT   total: 0.000004 GRT
[12:34:57] ✓ eth_getBalance       38ms  0.000008 GRT   total: 0.000012 GRT

Configuration:

VariableDefaultDescription
DISPATCH_SIGNER_KEY(auto-generated)Consumer private key. If unset, loaded from ./consumer.key or generated fresh
DISPATCH_CHAIN_ID1Chain to proxy (1 = Ethereum, 42161 = Arbitrum One, etc.)
DISPATCH_PORT8545Local port to listen on
DISPATCH_BASE_PRICE_PER_CU4000000000000GRT wei per compute unit

The proxy handles provider discovery, TAP receipt signing, QoS-scored provider selection, CORS, and JSON-RPC batch requests. On exit (Ctrl+C) it prints a session summary of total requests and GRT spent.

Unlike the gateway, the proxy runs locally and signs receipts with your own key — you pay providers directly from your own escrow. See Funding the escrow below.


Consumer SDK

For trustless access — signs receipts locally and talks directly to providers, no gateway in the loop.

npm install @lodestar-dispatch/consumer-sdk
import { DISPATCHClient } from "@lodestar-dispatch/consumer-sdk";

const client = new DISPATCHClient({
  chainId: 42161,                                               // Arbitrum One (only live chain)
  dataServiceAddress: "0xA983b18B8291F0c317Ba4Fe0dc0f7cc9373AF078",
  graphTallyCollector: "0x8f69F5C07477Ac46FBc491B1E6D91E2bb0111A9e",
  subgraphUrl: "https://api.studio.thegraph.com/query/1747796/rpc-network/v0.2.0",
  signerPrivateKey: process.env.CONSUMER_KEY as `0x${string}`,
  basePricePerCU: 4_000_000_000_000n,  // GRT wei per compute unit
});

const blockNumber = await client.request("eth_blockNumber", []);
const balance = await client.request("eth_getBalance", ["0x...", "latest"]);

The client discovers providers via the subgraph, selects one by QoS score, signs a TAP receipt per request, and tracks latency with an EMA.

Low-level utilities

import {
  discoverProviders,
  selectProvider,
  buildReceipt,
  signReceipt,
} from "@lodestar-dispatch/consumer-sdk";

// Discover active providers for a chain + tier
const providers = await discoverProviders(
  "https://api.studio.thegraph.com/query/1747796/rpc-network/v0.2.0",
  42161,  // chainId
  0,      // tier: 0 = Standard, 1 = Archive
);

const provider = selectProvider(providers);

// Build and sign a receipt
const receipt = buildReceipt(
  "0xA983b18B8291F0c317Ba4Fe0dc0f7cc9373AF078",  // dataService
  provider.address,                                // serviceProvider
  4_000_000_000_000n,                             // value (GRT wei)
);
const signed = await signReceipt(
  receipt,
  { verifyingContract: "0x8f69F5C07477Ac46FBc491B1E6D91E2bb0111A9e" },
  privateKey,
);

Funding the escrow

Before GRT flows to providers you need to deposit into PaymentsEscrow on Arbitrum One. This is required regardless of which access method you use — the gateway, proxy, and consumer SDK all charge from your own escrow.

Via the Lodestar dashboard (easiest)

Go to lodestar-dashboard.com/dispatch. Connect MetaMask, paste your consumer address, and deposit GRT. The dashboard calls depositTo() on the PaymentsEscrow contract so you can fund any address's escrow directly — the consumer wallet itself needs no ETH or GRT. Useful for funding dispatch-proxy from a separate hot wallet.

Manually (cast / ethers)

// 1. Approve the escrow contract
GRT.approve(0xf6Fcc27aAf1fcD8B254498c9794451d82afC673E, amount);

// 2a. Deposit from your own address
PaymentsEscrow.deposit(
    0x8f69F5C07477Ac46FBc491B1E6D91E2bb0111A9e,  // collector: GraphTallyCollector
    providerAddress,                                // receiver: the indexer you're paying
    amount
);

// 2b. Or fund any address's escrow (useful for the proxy key)
PaymentsEscrow.depositTo(
    consumerAddress,                                // payer: the consumer key you're funding
    0x8f69F5C07477Ac46FBc491B1E6D91E2bb0111A9e,  // collector: GraphTallyCollector
    providerAddress,                                // receiver
    amount
);

Deposits are keyed by (payer, collector, receiver). dispatch-service draws down automatically on each collect() cycle (hourly by default). Providers reject requests from addresses with zero escrow balance (checked on-chain every 30 seconds). Check your balance with:

cast call 0xf6Fcc27aAf1fcD8B254498c9794451d82afC673E \
  "getBalance(address,address,address)(uint256)" \
  <YOUR_ADDRESS> \
  0x8f69F5C07477Ac46FBc491B1E6D91E2bb0111A9e \
  <PROVIDER_ADDRESS> \
  --rpc-url https://arb1.arbitrum.io/rpc