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:
| Variable | Default | Description |
|---|---|---|
DISPATCH_SIGNER_KEY | (auto-generated) | Consumer private key. If unset, loaded from ./consumer.key or generated fresh |
DISPATCH_CHAIN_ID | 1 | Chain to proxy (1 = Ethereum, 42161 = Arbitrum One, etc.) |
DISPATCH_PORT | 8545 | Local port to listen on |
DISPATCH_BASE_PRICE_PER_CU | 4000000000000 | GRT 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