Privacy Pool metrics and note evidence
Integrate public activity counters, prepared series, events, commitments, and nullifiers without inferring private state.
Privacy Pool metrics and note evidence
Use these beta routes when an application needs the public metrics shown on Starkscan's Privacy Pool page. The routes read indexed or prepared serving tables. They do not call Starknet RPC, reconstruct history, fetch prices, or link private notes on the request path.
For finalized per-token public-flow amounts and prepared hourly amount points, see Privacy Pool data API.
Route map
| Need | Route | Important meaning |
|---|---|---|
| Lifetime counters and freshness | GET /v1/{chain}/privacy-pool/status | Public event and note-fact counters at the latest decoded cursor. |
| Daily activity | GET /v1/{chain}/privacy-pool/metric-buckets | Prepared daily deposits, withdrawals, note-use events, and viewing-key registrations. |
| Chart series | GET /v1/{chain}/privacy-pool/metrics/series | Prepared wallet-growth, token public-flow, and privacy-fee series. |
| Aggregate analysis | GET /v1/{chain}/privacy-pool/analytics | Event, visibility, token-flow, attribution, and typed unavailable-metric summaries. |
| Public event evidence | GET /v1/{chain}/privacy-pool/events | Cursor-paginated raw keys/data plus decoded public fields. |
| Commitment evidence | GET /v1/{chain}/privacy-pool/commitments | Public commitment facts. No owner or spend inference. |
| Nullifier evidence | GET /v1/{chain}/privacy-pool/nullifiers | Public nullifier facts. No commitment linkage. |
All seven operations are published in the
interactive API reference and
starkscan-openapi.yaml with
x-starkscan-certification: beta. Generated clients can use the operation IDs
getPrivacyPoolStatus, listPrivacyPoolMetricBuckets,
getPrivacyPoolMetricSeries, getPrivacyPoolAnalytics,
listPrivacyPoolEvents, listPrivacyPoolCommitments, and
listPrivacyPoolNullifiers.
Set the API host
External integrations use the API host, not the explorer's same-origin
/v1/* proxy.
export STARKSCAN_BASE_URL=https://api.starkscan.co
export STARKSCAN_API_KEY=<your-read-key>
export STARKSCAN_CHAIN=SN_MAINSend X-Starkscan-Api-Key on every request. Log X-Request-Id when a
request fails, respect Retry-After, and treat compatible additions to these
beta response objects as possible.
What is public and what remains private
Deposits and withdrawals are public on-chain transfers. When the event shape
exposes them, /events can return the public depositor in actorAddress, the
public withdrawal recipient in toAddress, the token, and amountRaw. Do not
describe those public fields as hidden.
The privacy boundary is unlinkability. Starkscan does not link a deposit to a withdrawal, link a spent note to the note that created it, reveal a note's private balance or owner, compute an anonymity set, or turn opaque commitment and nullifier hashes into a private relationship.
Read lifetime counters safely
curl --fail-with-body --silent --show-error \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"$STARKSCAN_BASE_URL/v1/$STARKSCAN_CHAIN/privacy-pool/status"The response includes lifetime public-event counters such as
depositCount, withdrawalCount, openNoteCreatedCount,
encNoteCreatedCount, noteUsedCount, and viewingKeySetCount.
noteState provides public commitment/nullifier proxies.
viewingKeySetCount is the number of public viewing_key_set event rows.
registeredUserCount is the distinct count of public addresses that registered
a viewing key. They answer different questions and can differ.
Before publishing a current total, inspect:
latestRawEventCursorandlatestDecodedEventCursor;eventLagBlocks; andmaterializationFresh.
When materializationFresh is not true, describe the data as indexing or
unavailable. Do not silently present a lagging counter as current.
The note-state fields are deliberately narrow:
| Field | Meaning |
|---|---|
commitmentCount | Public commitment facts indexed by Starkscan. |
nullifierCount | Public nullifier facts indexed by Starkscan. |
spentNoteProxyCount | Public proxy equal to the nullifier count. |
unspentNoteProxyCount | Commitments minus nullifiers, floored at zero. |
These fields are not private balances, unique-user counts, ownership claims, or an exact anonymity set. One transaction can emit more than one note-related event.
Availability vocabulary
Different routes expose the smallest status vocabulary their data can prove:
| Field | Values | Publication rule |
|---|---|---|
TVL top-level status | complete, degraded, unavailable | Publish only complete. |
TVL coverage.status | complete, partial, unavailable | Publish only complete; partial is not a zero. |
Analytics privateActivity.coverageStatus | complete, unavailable | Only complete contains a lifetime total. |
Event publicFields.visibility | public, partial, hidden_by_design | Use only the fields actually present. |
The metric-bucket and metric-series routes are prepared chart reads and do not
carry an independent continuity certificate. Check /status freshness, retain
each item's latestBlockNumber, and do not infer missing buckets.
Build daily activity cards
Use prepared buckets instead of paging the event history.
curl --fail-with-body --silent --show-error \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"$STARKSCAN_BASE_URL/v1/$STARKSCAN_CHAIN/privacy-pool/metric-buckets?limit=30"The response is chronological and contains at most 90 daily buckets. Each item includes:
newViewingKeyWallets;cumulativeViewingKeyWallets;visibleDeposits;visibleWithdrawals;noteUsedEvents; andlatestBlockNumber.
These are public product-activity counts, not token amounts or accounting
history. Today's bucket is still moving; use latestBlockNumber when recording
a snapshot.
Build prepared charts
TOKEN_ADDRESS=0x...
curl --fail-with-body --silent --show-error --get \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
--data-urlencode 'range=30d' \
--data-urlencode 'granularity=day' \
--data-urlencode "token=$TOKEN_ADDRESS" \
"$STARKSCAN_BASE_URL/v1/$STARKSCAN_CHAIN/privacy-pool/metrics/series"Supported ranges are 24h, 7d, 30d, and all. Supported
granularities are hour, day, and week. The response stays bounded at
720 points per series.
walletSeriescontains viewing-key wallet growth.shieldedSupplySeriescontains deposited, withdrawn, and net public-flow raw amounts for the requested token.privacyFeeSeriescontains public fee-transfer amounts and counts.
Pass a token address whenever token identity matters. If token is omitted,
the route uses Starkscan's tracked strkBTC token. Raw amounts are decimal
strings; do not parse them as JavaScript number values.
Read analytics without inventing unavailable metrics
curl --fail-with-body --silent --show-error \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"$STARKSCAN_BASE_URL/v1/$STARKSCAN_CHAIN/privacy-pool/analytics"Useful fields include totalEvents, uniqueTransactionCount,
eventBreakdown, visibilityBreakdown, tokenFlows,
missingAmountEventCount, and unavailableMetrics.
Treat token address as identity. tokenFlows[] metadata such as symbol, name,
or decimals can be null; do not join or group flows by symbol. Use the TVL
token metadata or your own address-keyed registry when display metadata is
required.
For token-flow publication, require:
tokenFlowsTruncated === false;tokenFlowSourceEventsTruncated === false; andmissingAmountEventCount === 0.
privateActivity is fail-closed. Use it only when
coverageStatus === "complete". When it is "unavailable", keep the metric
unavailable and display coverageReasonCode. Do not convert an empty
metrics array into a zero. An empty private-activity array is not zero. In
particular, Starkscan does not publish an AVNU
lifetime private-route count while historical materialization is unreconciled.
unavailableMetrics is part of the contract. It explains why values such as
USD TVL, private-swap volume, exact anonymity set size, or private activity are
not currently publishable.
Page public event evidence
curl --fail-with-body --silent --show-error --get \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
--data-urlencode 'event=deposit' \
--data-urlencode 'limit=100' \
"$STARKSCAN_BASE_URL/v1/$STARKSCAN_CHAIN/privacy-pool/events"The supported server-side filter is event. Pass its indexed snake_case
value, such as deposit, withdrawal, or note_used. Contract,
transaction, and block filters shown in the explorer are client-side filters
over loaded rows and are not accepted by this endpoint.
Each row retains raw keys and data as audit evidence. Decoded
publicFields.visibility can be:
public: the supported public fields were decoded;partial: only part of the public shape is known; orhidden_by_design: the protocol does not expose the field publicly.
Treat absent decoded fields as unavailable. Do not reconstruct them from unrelated events.
Page commitments and nullifiers
curl --fail-with-body --silent --show-error \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"$STARKSCAN_BASE_URL/v1/$STARKSCAN_CHAIN/privacy-pool/commitments?limit=100"
curl --fail-with-body --silent --show-error \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"$STARKSCAN_BASE_URL/v1/$STARKSCAN_CHAIN/privacy-pool/nullifiers?limit=100"Both endpoints accept an optional pool contract address. Preserve
nextCursor exactly and send it with the same query to fetch the next page.
Do not derive a cursor from a block number.
A commitment row and a nullifier row are independent public facts. The API does not identify the owner of a commitment and does not say which commitment a nullifier spent.
Cursor loop
const baseUrl = "https://api.starkscan.co";
const chain = "SN_MAIN";
const apiKey = process.env.STARKSCAN_API_KEY;
let cursor;
do {
const url = new URL(`/v1/${chain}/privacy-pool/commitments`, baseUrl);
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("cursor", cursor);
const response = await fetch(url, {
headers: { "X-Starkscan-Api-Key": apiKey },
});
if (!response.ok) {
throw new Error(`Starkscan ${response.status}: ${await response.text()}`);
}
const page = await response.json();
for (const commitment of page.items) {
console.log(commitment.blockNumber, commitment.txHash, commitment.commitment);
}
cursor = page.nextCursor ?? undefined;
} while (cursor);Amounts and USD presentation
Use GET /v1/{chain}/privacy-pool/tvl for the current finalized per-token
public-flow ledger. Use GET /v1/{chain}/privacy-pool/tvl/series only for
prepared hourly points that actually exist. An empty or sparse hourly response
is unavailable history, not a zero or a continuity claim.
Raw token amounts, token addresses, and decimals are the integration inputs. USD values displayed by Starkscan's website are presentation estimates, not a Privacy Pool API pricing contract. Apply an address-keyed price source in the consumer when a USD presentation is required.
Routes intentionally excluded
Two explorer routes remain outside the public integration contract:
GET /v1/{chain}/privacy-pool/dashboardis a UI-composition payload. Use the dedicated routes above so a frontend layout change cannot break an integration.GET /v1/{chain}/privacy-pool/rootsis not published while root facts are not materially available. An empty root page is not an anonymity-set claim.
Production checklist
- Use
https://api.starkscan.co, nothttps://starkscan.co/v1/*. - Send
X-Starkscan-Api-Key; never place keys in URLs. - Treat every route's OpenAPI
x-starkscan-certificationandx-starkscan-stabilityas authoritative. - Preserve cursor strings exactly and keep the original filter set.
- Keep felt addresses and raw integer amounts as strings.
- Gate current counters on
materializationFresh === true. - Fail closed on unavailable, truncated, partial, empty, or sparse data.
- Do not infer any hidden recipient beyond the public event fields, private ownership, note linkage, balances, or exact anonymity-set size.