Skip to content

Data layer (subgraph)

How Boon indexes onchain events into the entities that back board, points, profile, receipt, and graph reads.

Almost every Boon read — board, points, profile, receipt, gratitude graph — is served by the Worker reading a Goldsky-hosted subgraph that indexes the Boon contract on Base. Understanding the entity model is the fastest way to debug “data looks stale” or to integrate a new endpoint.

The contract emits four events that the subgraph consumes:

EventWhat it produces
Tip(indexed bytes32 handleHash, indexed address from, uint256 amount, string displayHandle, string note)A Tip entity (status starts as ESCROWED), Recipient + Tipper rollups, and Boon Points attributions.
Pushed(indexed bytes32 handleHash, indexed address to, uint256 amount)Flips matching Tip entities to PUSHED and updates the Recipient pushed/escrowed split.
Linked(indexed bytes32 handleHash, indexed address wallet, string canonicalHandle)Sets Recipient.linkedWallet, creates a Link audit entry, populates the HandleHashIndex, updates Stats.
Claimed(indexed bytes32 handleHash, indexed address to, uint256 amount)Creates a Claim entity and flips prior ESCROWED tips for the handle to CLAIMED.
  • Tip — every tip event, with statusESCROWED | PUSHED | CLAIMED and points-attribution fields.
  • Recipient — aggregated per canonical handle: totalReceived, pushed/escrowed/claimed splits, linkedWallet, recipient-side public points.
  • Tipper — aggregated per sender wallet: totalSent, tipCount, sender-side public points.
  • Link — discrete handle ↔ wallet binding events.
  • Claim — discrete claim sweeps for audit/history.
  • HandleHashIndex — reverse lookup from handleHash to canonical handle (needed because Claimed only carries the hash).
  • Stats — global singleton: totalTipped, tipCount, uniqueRecipients, uniqueTippers, linkedRecipients, current points policy version.
  • TipperRecipientDay, RecipientSender, RecipientEpochBonus — persistent rollups for anti-farming and independent-sender accounting. These must live in the subgraph (not in-memory AssemblyScript maps) so the rules stay deterministic.
EndpointSource
GET /api/v1/board (and /api/leaderboard alias)Recipient (top by totalReceived) + Tipper + Stats
GET /api/v1/handles/:handle/pointsRecipient (handle points) + join to Tipper via linkedWallet for sentPoints
GET /api/v1/handles/:handle/profilePoints envelope + Recipient aggregates
GET /api/v1/receipts/:txHashTip by tx hash + Recipient + Tipper
GET /api/v1/handles/:handle/boons (paid)Tip filtered by handle
GET /api/v1/graphs/gratitude (paid)Tip traversed into handle / repo-filtered graph

If a handle has no linkedWallet, the Worker reports sentPointsSource: "unlinked" rather than silently zeroing the sender-side points.

The subgraph manifest is rendered from subgraph.template.yaml plus subgraph/networks.json on every build. The build script intentionally fails on zero addresses so a placeholder subgraph cannot be deployed by accident. Mainnet and Sepolia each have their own deploy command:

Terminal window
npm --prefix subgraph run build:mainnet
npm --prefix subgraph run build:sepolia

The ABI at subgraph/abis/Boon.json is extracted from the Foundry build artifact and must be refreshed whenever the contract changes.

”Data looks stale” — diagnose before assuming contract bugs

Section titled “”Data looks stale” — diagnose before assuming contract bugs”

Index lag is the most common cause of a read that does not match the chain. Before assuming the contract is wrong:

  1. Look up the tx on BaseScan to confirm the block.
  2. Query the subgraph’s _meta { block { number } } to see how far behind it is.
  3. Wait a block or two, then re-fetch.

If the lag persists or Stats totals look wrong, that’s a subgraph issue, not a contract issue. See Troubleshooting → Data looks stale.

The full entity schema, deploy commands, and the example leaderboard query live in subgraph/README.md. Treat it as the operator-side companion to this page.