What we collect, what we don't.
The SDK + docs site can emit events to analytics. By default they don't. When they do, two scrubbers strip anything that could identify a wallet, signature, or ciphertext.
SDK telemetry vs docs telemetry
SdkTelemetry
Lives in the SDK itself (@tokenops/sdk/telemetry). Default is NoopTelemetry, consumers who don't wire anything emit zero events. Wrap an SDK client with withTelemetry(client, adapter) to plug in Plausible / PostHog / Datadog / your own webhook.
DocsTelemetry
Lives in the docs site itself. Fires docs.<surface>.<action> events (pageview, story.chapter.run, runner.tx.mined, search.no_results). Same scrubbing rules; default is also no-op unless an adapter is wired.
Two scrubbers + a flat schema
scrubProps belt-and-suspenders runs at the adapter boundary. It drops any prop key matching /^(address|wallet|account|signature|sig|tx|hash|handle|key|pk)$/i. It also drops 0x-prefixed values longer than 20 hex characters, defensive against a future call site that puts a handle in a non-suspicious key name.
Flat schema. Event props are string | number | boolean | undefined. No arbitrary objects, keeps the wire format Plausible / PostHog / GA compatible without per-sink shims.
What CAN ship:chainId, story slug, chapter id, durationMs, blockNumber (cast to plain Number), gasUsed, error category (revert / network / user-rejected), search.no_results query strings (the docs DO want to see what you couldn't find).
On this docs site
The docs site mounts a DocsTelemetryProvider at the root. The env-driven resolver picks the adapter: NoopTelemetry in production unless NEXT_PUBLIC_PLAUSIBLE_DOMAIN is set, then routes to a Plausible-compatible sink. Source: src/telemetry/.