Quite some time has passed since my last post about The Graph. If you don’t know what it is and why it’s useful, go and read the earlier introduction. Its indexing concepts remain useful; its hosted-service setup is historical.
The short version: events put logs in transaction receipts, separate from contract storage. Nodes use log Blooms to filter candidate blocks, then inspect the matching logs. The logs still need to be stored and retrieved; a Bloom does not eliminate that data. Indexing selected events into a database makes repeated queries easier, and GraphQL provides a convenient query interface.
This article records the early Graph Network rollout. The planned Q1 2023 hosted-service deadline changed: the hosted service became inactive on June 12, 2024. Current Studio and network documentation should guide new deployments.

The Transition Towards Decentralization
Originally The Graph only had a centralized hosted service, but that of course is not what we want in the long term. After all, what's the point of a Dapp if you fully rely on a centralized server for querying data. Actually it's still better than a fully centralized infrastructure, but we can still do better!
To get around the issue, The Graph has its own decentralized network with its own GRT ERC-20 token.
1. Protocol Roles

- Consumers request indexed data, commonly through a gateway, and pay for query service.
- Indexers operate indexing/query infrastructure and stake GRT. Their compensation can include query fees and eligible indexing rewards.
- Curators signal on subgraphs with GRT under the curation mechanism and can receive a share of query fees. Signaling is not a guarantee of demand or correctness.
- Delegators delegate GRT to an indexer and, under Horizon, a data service, sharing the applicable rewards and risks.
Indexer selection can consider price, availability and performance. Using more than one indexer may help compare results, but it does not by itself provide a proof that either result is correct. The gateway and application still need a deliberate verification and failure policy.
2. GRT Token
Indexers stake GRT as part of the network’s economic-security model. Staking, reward collection and dispute rules have changed since this early description; they are not a direct requirement that stake always equal a measured fraction of total work.
Delegation participates in protocol staking and rewards rather than being an ordinary fixed-return loan. Under Graph Horizon it is attached to an indexer and a data service. The current documentation says SubgraphService delegation is not slashable, but the architecture permits future slashability. “Delegators can never be slashed” is therefore not a permanent guarantee. Review the current service parameters, capacity and withdrawal rules.

3. Bootstrapping New Subgraphs
Indexing rewards help support the supply of indexed data; they are not reserved only for subgraphs with very low query demand. Eligibility and distribution follow the protocol’s current reward rules.
A Proof of Indexing (POI) is a digest of the entity-store changes produced by indexing a particular deployment through a block. It is not simply a signature over the current state, and a digest alone is not a proof that an arbitrary GraphQL answer is correct. The submission, comparison and dispute mechanisms supply additional assumptions.
A subgraph also needs its manifest and referenced files to remain available, often through IPFS. The availability-oracle and arbitrator descriptions in the original article concern the early network design. Use the current indexing rules for present reward and dispute requirements rather than assuming those old operating details are unchanged.
4. Payment Channels
Per-query payments need to avoid an onchain transaction for every request. The WAVE description below records an early design. Current gateway documentation describes Graph Tally, formerly TAP, using signed off-chain receipts and periodic settlement.

Historical WAVE terminology: Work described the requested task, Attestation added a signature, Verification considered whether the claimed work was valid, and Expiration bounded a payment’s lifetime. This acronym is retained as historical context.
In current Graph Tally, receipts are accumulated into signed aggregate vouchers and redeemed against escrow. The indexer limits how much unpaid work it risks because the sender can decline aggregation. Payment signatures establish payment authorization; they do not prove query correctness.
Query verification
The early network used attestations and an arbitration process to resolve disputes. Current Horizon documentation still describes arbitration and slashing rules. Do not treat the old prediction of eliminating arbitration as an implemented guarantee.
Cryptographic commitments and proofs can establish particular statements when their construction and verifier cover those statements. Merkle trees or polynomial commitments alone do not make every query verifiable. The queried deployment, indexed block, data availability, mapping behavior and actual verification mechanism all matter.
How to deploy to The Decentralized Graph
Subgraph Studio is used to create, deploy and test subgraphs and to manage query access. A deployment to Studio is a staging step; publishing to The Graph Network is a separate onchain action.
The deploy key authenticates the CLI deployment. A query API key has a different purpose and its own access, usage and billing controls. The old claim that all payments would become direct once queries were verifiable is a historical roadmap statement, not the current integration model.
For a new project, follow the current Studio deployment guide and select a supported source network. Rinkeby and its old faucet commands below are retired; the screenshots record the original test environment.
- Create the subgraph in Studio and use its displayed slug.
- With a compatible Graph CLI installed, initialize it with
graph init <SUBGRAPH_SLUG>. - Supply the actual contract address, chain and ABI. Edit
schema.graphql,subgraph.yamland the mapping file, typicallysrc/mapping.ts. - Authenticate using
graph auth <DEPLOY_KEY>. This is the deploy key, not the subgraph ID. - Generate and build with
graph codegenandgraph build, then deploy withgraph deploy <SUBGRAPH_SLUG>. - Inspect the staging data and logs before the separate network-publication step. Use Studio and Explorer for the actual supported network and query endpoint.
This is the current command structure, not a claim that the original repository’s old dependencies and Rinkeby manifest have been migrated or run. Keep a compatible CLI, graph-ts and mapping/test dependency set together.
New Features for Developers
- AssemblyScript: the version migration described here was new when the article was published. Match the mapping syntax and generated types to the selected graph-ts/CLI versions.
- Debug forking: the linked debugging technique can help investigate a deployed mapping from a chosen point; it does not validate every prior state or fix the mapping automatically.
- Chain support: a Graph Node integration, Studio availability and support by network indexers are different questions. Check the current supported-network and feature matrix instead of the original Cosmos/NEAR/Arweave snapshot.
- Mapping unit tests: Matchstick exercises mapping behavior in a sandbox; it does not replace checking deployment, indexing and query behavior.
Unit-testing Subgraph Mappings
To get started with unit-testing in an existing Subgraph:
The historical example uses matchstick-as and graph test. Follow the Matchstick documentation for the selected release’s native libraries, platform or container requirements. The broad old PostgreSQL commands do not guarantee a compatible binary on every current machine.
Run the project’s chosen Graph CLI, use the intended mapping/generated types, and keep the store isolated between tests. No Matchstick installation or test execution was performed for this refresh.
The test on the right is the original Matchstick example. It creates a mock event, changes its fields, invokes handleBetPlaced, checks the resulting Bet entity and clears the store.
Coverage limitation: it sets event.transaction.from to the same address as event.params.player. The linked historical mapping reads the former, so this fixture cannot detect confusing the transaction sender with the player recorded by the event. When the event defines the player, use that field in the mapping and add a test where the two addresses differ. Also test repeated bets and both outcomes, including the Player counters and relationships.
The generated event constructor and dependency versions are historical. The fixture’s assertions do not establish that the linked game contract is safe or that the whole subgraph works on a current network.
// Historical Matchstick/graph-ts example; original dependency generation.
// This fixture makes transaction.from equal event.params.player.
// See the adjacent scope note for the missing differing-sender test.
import {
assert,
describe,
clearStore,
test,
newMockEvent,
} from "matchstick-as/assembly/index";
import { BetPlaced } from "../generated/Game/Game";
import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts";
import { handleBetPlaced } from "../src/mapping";
function createBetPlacedEvent(
player: string,
value: BigInt,
hasWon: boolean
): BetPlaced {
const mockEvent = newMockEvent();
const BetPlacedEvent = new BetPlaced(
mockEvent.address,
mockEvent.logIndex,
mockEvent.transactionLogIndex,
mockEvent.logType,
mockEvent.block,
mockEvent.transaction,
mockEvent.parameters,
null
);
BetPlacedEvent.parameters = new Array();
const playerParam = new ethereum.EventParam(
"player",
ethereum.Value.fromAddress(Address.fromString(player))
);
const valueParam = new ethereum.EventParam(
"value",
ethereum.Value.fromUnsignedBigInt(value)
);
const hasWonParam = new ethereum.EventParam(
"hasWon",
ethereum.Value.fromBoolean(hasWon)
);
BetPlacedEvent.parameters.push(playerParam);
BetPlacedEvent.parameters.push(valueParam);
BetPlacedEvent.parameters.push(hasWonParam);
BetPlacedEvent.transaction.from = Address.fromString(player);
return BetPlacedEvent;
}
describe("handleBetPlaced()", () => {
test("Should create a new Bet entity", () => {
const player = "0x7c812f921954680af410d86ab3856f8d6565fc69";
const hasWon = true;
const mockedBetPlacedEvent = createBetPlacedEvent(
player,
BigInt.fromI32(100),
hasWon
);
handleBetPlaced(mockedBetPlacedEvent);
const betId =
mockedBetPlacedEvent.transaction.hash.toHex() +
"-" +
mockedBetPlacedEvent.logIndex.toString();
// fieldEquals(entityType: string, id: string, fieldName: string, expectedVal: string)
assert.fieldEquals("Bet", betId, "id", betId);
assert.fieldEquals("Bet", betId, "player", player);
assert.fieldEquals("Bet", betId, "playerHasWon", "true");
assert.fieldEquals(
"Bet",
betId,
"time",
mockedBetPlacedEvent.block.timestamp.toString()
);
clearStore();
});
});The historical example at its reviewed commit remains available. Its subgraph project is in the nested solidity/ folder and records graph-cli ^0.32.0, graph-ts ^0.27.0, Matchstick ^0.5.0 and a Rinkeby manifest. It was not executed or migrated in this review. The linked Game contract uses deliberately unsafe block-based randomness and is only event-generation context, not a wagering implementation to deploy.
What do you think of The Graph’s evolution? Have you used the hosted service in the past or the network today?




Join the conversation
Comments are hosted by Disqus and load only when you choose to enable them.