Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Eventing & indexing · Boson Protocol
Skip to content

Eventing & indexing

Watching for on-chain state changes is the second-most-common task after creating them. Boson exposes three eventing surfaces; pick by latency tolerance and throughput.

The three surfaces

SurfaceLatencyThroughputBest for
RPC logs (eth_getLogs, WebSocket)0–1 blockLimited by nodeReal-time triggers, single-app dashboards
Subgraph (The Graph)1–3 blocksUnlimited GraphQLMarketplaces, listings, history queries
Your own webhook serverSame as input (RPC or subgraph)Whatever you buildPersisting orders to your CRM / fulfillment system

The subgraph is convenient (typed GraphQL, joins across offers/exchanges/disputes), but it lags. Plan for it.

The subgraph indexing lag

After a transaction is mined, the Boson subgraph waits for the indexer to process it — typically 1–3 blocks. If you commit at block N and immediately read getExchanges(), you may get nothing for blocks N..N+3.

Don't poll the subgraph in a tight loop. Use the SDK helper:

const tx = await sdk.commitToOffer(buyer, offerId)
const receipt = await tx.wait()
 
await sdk.waitForGraphNodeIndexing(receipt.blockNumber)
 
const exchanges = await sdk.getExchanges({ where: { buyer } })

waitForGraphNodeIndexing(blockNumber) polls the subgraph's _meta until the indexed block ≥ blockNumber. Default timeout is ~30 seconds.

Inside an agent loop, treat this as mandatory. See Build for AI agents → Idempotency & retry and Troubleshooting → Subgraph indexing lag.

Reading from RPC logs

For zero-lag, listen to events directly:

import { ethers } from "ethers"
 
const diamond = new ethers.Contract(diamondAddress, diamondAbi, provider)
 
diamond.on("BuyerCommitted", (offerId, buyerId, exchangeId, exchangeStruct, /* … */) => {
  // … immediately persist or react
})

The Diamond emits events from every facet at the same contract address. Use the merged ABI from @bosonprotocol/common.

Critical events

EventEmitted byFires on
OfferCreatedOfferHandlerFacetcreateOffer
OfferVoidedOfferHandlerFacetvoidOffer
BuyerCommittedExchangeCommitFacetcommitToOffer (creates an exchange)
VoucherRedeemedExchangeHandlerFacetredeemVoucher
ExchangeCompletedExchangeHandlerFacetcompleteExchange
VoucherCanceledExchangeHandlerFacetcancelVoucher (buyer)
VoucherRevokedExchangeHandlerFacetrevokeVoucher (seller)
DisputeRaisedDisputeHandlerFacetraiseDispute
DisputeResolvedDisputeHandlerFacetresolveDispute
DisputeDecidedDisputeHandlerFacetdecideDispute
FundsDepositedFundsHandlerFacetdepositFunds
FundsWithdrawnFundsHandlerFacetwithdrawFunds

Full list at Reference → Contracts → Events.

Building a webhook server

Run an event listener that posts to your backend. See Recipes → Webhook server for state changes for a complete reference implementation.

The common shape:

RPC subscription or subgraph poll

Pull events from a chain RPC (eth_subscribe / log filter) or by polling the subgraph.

Filter and dedupe

Match the event types you care about; remember the last block + log index so you don't fire twice if the source replays.

HTTP POST to your handler

Deliver the event to your backend, with retries on non-2xx responses.

Reorg handling

Every chain Boson runs on can reorg. Defensive practices:

  • Wait 2–3 confirmations before treating an event as final on Polygon / Base / Optimism / Arbitrum; 12+ on Mainnet.
  • Make your webhook handler idempotent — keyed on (txHash, logIndex) or (exchangeId, newState).
  • For high-stakes actions, re-read state from RPC before acting (don't trust the event payload alone).

Common footguns

  • Subgraph "missing data" right after a write. Indexing lag. Use waitForGraphNodeIndexing.
  • Polygon RPC rate limits. Free-tier RPCs throttle. Use a paid provider for production.
  • Event ABIs from old versions. If you read events from a Diamond before an upgrade and decode with a post-upgrade ABI, field order can shift. Pin the ABI to the version you indexed at.

Where to look in code

Next