@peerlytics/sdk
TypeScript SDK for the Peerlytics v1 API -- analytics, explorer, and trading data for the ZKP2P P2P protocol on Base. Works in Node.js and browsers.
v1.0 (April 2026) is the first release that targets the Stripe-style v2 API: snake_case wire format, Unix-seconds timestamps, cursor pagination, and dedicated key/webhook endpoints. The SDK adapts the wire format back to a camelCase TS surface so existing TS code keeps working — see "Migration" at the end of this README for the details.
Agent bundle
If you are integrating through an agent (Claude Code, Cursor, etc.), start here:
- Developer portal: https://peerlytics.xyz/developers
- Drop-in skill: https://peerlytics.xyz/skills/peerlytics.md
- Short machine reference: https://peerlytics.xyz/llms.txt
- Full machine reference: https://peerlytics.xyz/llms-full.txt
- OpenAPI 3.1 JSON: https://peerlytics.xyz/api/openapi (also discoverable at
/.well-known/openapi.json) - Starters (Next.js / Vite / Telegram bot, plus runnable example scripts and an x402 agent): https://github.com/ADWilkinson/usdctofiat-peerlytics-starters
- Companion SDK for USDC-to-fiat deposits:
@usdctofiat/offramp(one Peerlytics API key authenticates both products)
The SDK also exports a typed resource map and prompt builder for developer portals, internal tools, and coding agents:
import {
PEERLYTICS_DEVELOPER_RESOURCES,
getPeerlyticsAgentPrompt,
getPeerlyticsDeveloperResources,
} from "@peerlytics/sdk";
PEERLYTICS_DEVELOPER_RESOURCES.authModes; // ["api-key", "x402"]
PEERLYTICS_DEVELOPER_RESOURCES.links.wellKnownOpenApi; // https://peerlytics.xyz/.well-known/openapi.json
PEERLYTICS_DEVELOPER_RESOURCES.headers.webhookSignature; // X-Peerlytics-Signature
PEERLYTICS_DEVELOPER_RESOURCES.upstreamSourceTruths.map((source) => source.label);
// ["@zkp2p/indexer-schema 0.5.0", "zkp2p-indexer", ...]
const webhookPlaybook = getPeerlyticsDeveloperResources("webhooks");
const agentPrompt = getPeerlyticsAgentPrompt("market-maker");Use upstreamSourceTruths before generating raw protocol fields, GraphQL
queries, proof payload mappings, or client behavior. It anchors agent work to
the current indexer schema, zkp2p-indexer projections, Curator API,
zkp2p-clients, and attestation service.
Install
npm install @peerlytics/sdk
# or
bun add @peerlytics/sdk
# or
pnpm add @peerlytics/sdkQuick Start
import { Peerlytics } from "@peerlytics/sdk";
const client = new Peerlytics({ apiKey: "pk_live_..." });
// protocol summary
const summary = await client.getProtocolSummary();
// protocol health view
const overview = await client.getProtocolOverview("all");
// live orderbook
const orderbook = await client.getOrderbook({ currency: "GBP" });
// deposit detail
const deposit = await client.getDeposit("8453_0x777...Ef_42");
// search (supports addresses, tx hashes, deposit IDs, ENS, .peer names)
const results = await client.search("vitalik.eth");x402 pay-per-request
Agents can skip API-key provisioning and pay each request directly with USDC on
Base. Pass a viem signer and the SDK handles the 402 Payment Required
challenge, creates the payment payload, retries with PAYMENT-SIGNATURE, and
keeps the normal camelCase response shape.
import { Peerlytics } from "@peerlytics/sdk";
import { privateKeyToAccount } from "viem/accounts";
const client = new Peerlytics({
auth: {
mode: "x402",
signer: privateKeyToAccount(process.env.AGENT_PRIVATE_KEY as `0x${string}`),
onPaymentSettled: (settlement) => {
console.log("paid", settlement.transaction);
},
},
});
const orderbook = await client.getOrderbook({ platform: "venmo" });
const summary = await client.getProtocolSummary({ range: "mtd" });For custom agent runtimes, provide paymentHandler instead of signer and
return the paid retry headers yourself:
const client = new Peerlytics({
auth: {
mode: "x402",
paymentHandler: async ({ paymentRequired }) => ({
"PAYMENT-SIGNATURE": await signWithYourAgentWallet(paymentRequired),
}),
},
});Response shapes
A few gotchas worth knowing up front — especially if you've been hitting the HTTP API directly:
{ data, ... }envelope. Every v1 endpoint wraps the payload in{ data: <payload>, ... }(siblings includemeta,linked, etc.). The SDK unwraps this for you — every method returns the innerdatadirectly. The legacysuccess: trueflag was retired with v2 — branch on HTTP status.getDepositsrequires at least one filter. The API rejects empty queries with a 400missing_filter. Provide at least one ofdepositor,delegate,platform,currency, or a date window (from,to, orrange). The SDK throws aValidationErrorbefore hitting the network so you get a clearer stack trace.getIntentsalso requires at least one filter. Provide at least one oftaker,recipient,verifier,depositId,status, or a date window (from,to, orrange). The legacyowneralias is still accepted for older callers. Same client-sideValidationErrorbehavior.getActivityreturns an envelope, not a raw array. The response is{ events, count, hasMore, limit, offset, nextCursor, filters }— iterate overresponse.events, notresponseitself. WhenhasMore=true, pass the returnednextCursorback ascursorto walk forward without offset drift.Currency codes vs hashes. On-chain, currencies are stored as
bytes32(either a keccak256 of the ISO code or an ASCII-padded encoding). EveryDepositMarketexposes both:currency(resolved, e.g."GBP") andcurrencyCode(raw hash). Entries insidedeposit.currencies[]also carry a resolvedcurrencyfield alongside the rawcurrencyCode. If you need to build your own mapping, callgetCurrencies()— each entry includes thecode,label,flag, and all hash forms.
Date filtering
Every analytics, listing, and history endpoint accepts a uniform set of
date-window parameters. Either pass from/to or a range shortcut:
// April 2026 leaderboard
await client.getLeaderboard({ from: "2026-04-01", to: "2026-05-01" });
// last 30 days
await client.getLeaderboard({ range: "last_30d" });
// month-to-date with prior-period comparison block
await client.getProtocolSummary({ range: "mtd", compare: "prior_period" });
// LP retro for a single maker, paginated across the window
await client.getMakerHistory("0xMaker...", {
range: "last_90d",
limit: 50,
offset: 0,
});
// Cash-App-only daily volume series
await client.getTimeseries({
entity: "volume",
groupBy: "platform",
platform: "cashapp",
granularity: "day",
from: "2026-04-01",
to: "2026-05-01",
});
// Vault rollup for the launch week
await client.getVaultsOverview({ from: "2026-04-22", to: "2026-04-29" });from and to accept ISO-8601 strings (2026-04-01T00:00:00Z) or unix-seconds
(numeric). Supported range shortcuts: last_7d, last_30d, last_90d,
last_365d, today, yesterday, mtd, qtd, ytd, all. Hard cap: 400 days.
Windowed responses include a window block — { from, to, fromIso, toIso, days, range, computedFor } — so you can render the resolved bounds verbatim. The
cumulative path remains the default; passing any window param opts you into a
live indexer compute that costs more credits but never returns stale data.
For activity backfills, prefer the cursor over offset:
let cursor: string | null = null;
do {
const page = await client.getActivity({
range: "last_30d",
limit: 200,
cursor: cursor ?? undefined,
});
for (const event of page.events) handle(event);
cursor = page.nextCursor;
} while (cursor);Configuration
const client = new Peerlytics({
baseUrl: "https://peerlytics.xyz", // default
apiKey: "pk_live_...", // API key for authenticated access
headers: { "X-Trace": "abc" }, // custom headers
fetch: customFetch, // custom fetch implementation
});apiKey is kept for backwards compatibility. New code can use
auth: { mode: "api-key", apiKey: "pk_live_..." } or
auth: { mode: "x402", signer }.
API Reference
Analytics
| Method | Description |
|---|---|
getProtocolSummary(params?) |
Cumulative summary by default; pass from/to/range/compare for a windowed payload with optional period delta block. |
getProtocolOverview(rangeOrOpts) |
Legacy TimeRange enum reads cached buckets; OverviewParams (from/to or non-enum range shortcut) computes live. |
getLeaderboard(params?) |
Maker and taker leaderboards. Add from/to/range to recompute every aggregate from intents inside the window. |
getTimeseries(params) |
Hour or day buckets. Pass groupBy=platform|currency|maker|verifier for multi-series + dimension filters. |
getVaultsOverview(params?) |
Cumulative vault overview by default; pass from/to/range for per-vault rollup with feesEarnedUsd, aumChangeUsd. |
Deposits
| Method | Description |
|---|---|
getDeposits(filters) |
Query deposits by depositor, delegate, platform, currency, OR a date window (from/to/range). Add status to narrow those results. sort=asc|desc. |
getDeposit(id, params?) |
Deposit detail with intents, payment methods, and linked data |
Intents
| Method | Description |
|---|---|
getIntents(filters) |
Query intents by taker, recipient, verifier, depositId, status, OR a date window (from/to/range). owner remains a legacy alias. sort=asc|desc. |
getIntent(hash) |
Intent detail with deposit and related intents |
Explorer
| Method | Description |
|---|---|
getAddress(address, params?) |
Address profile: intents, deposits, activity, stats |
getMaker(address) |
Maker portfolio: deposits, allocations, profit, APR |
getTaker(address) |
Taker portfolio: fills, tier, lock score, currency mix |
getIntegrator(code, opts?) |
ERC-8021 integrator rollup: volume, makers, top markets |
getIntegratorIntents(code, opts?) |
Recent intents attributed to an ERC-8021 integrator (up to 20) |
getIntegratorReferralFees(code, opts?) |
Recent treasury referral fees for an ERC-8021 integrator (up to 20) |
getPlatform(platform, opts?) |
Platform rollup: currencies, makers, takers, recent intents |
getDelegate(address) |
Delegate rollup: rate managers, delegated deposits, PnL |
getVerifier(address, params?) |
Verifier stats: intents, breakdown by currency/taker/maker |
search(query, opts?) |
Multi-type search (address, tx hash, deposit ID, ENS, .peer name) |
Recent integrator intents are available two ways:
const intents = await client.getIntegratorIntents("usdctofiat");
const integrator = await client.getIntegrator("usdctofiat");
const recentIntents = integrator.recentIntents ?? [];Use IntegratorData.recentIntents when you already need the full integrator rollup and want a single network call.
Recent referral fees (0.50% treasury take) follow the same pattern:
const fees = await client.getIntegratorReferralFees("usdctofiat");
const integrator = await client.getIntegrator("usdctofiat");
const recentFees = integrator.recentReferralFees ?? [];Each explorer entity also has a canonical URL at peerlytics.xyz/explorer/<entity>/<slug> — the SDK method and the page are siblings.
Orderbook & Market
| Method | Description |
|---|---|
getOrderbook(opts?) |
Live orderbook grouped by currency and rate level |
getMarketSummary(opts?) |
Rate statistics per (platform, currency) pair |
The orderbook API now prefers the indexer's denormalized OrderbookEntry projection when available, which keeps platform filters canonicalized (for example, zelle-* variants collapse into one Zelle surface) while preserving the same response shape for SDK consumers. The default book is public liquidity only; pass taker to inspect deposits whitelisted for a specific buyer wallet. Generic take links are null for whitelist-hooked liquidity.
Vaults & Delegation
| Method | Description |
|---|---|
getVaultsOverview() |
All vaults: AUM, fees, adoption rate, daily snapshots |
getVault(id, params?) |
Vault detail: rate manager, delegations, oracle/floor configs |
Activity
| Method | Description |
|---|---|
getActivity(filters?) |
Live blockchain events (signals, fulfills, deposits, rate updates) |
streamActivity(filters?, opts?) |
SSE stream of the same events (requires api key, no x402) |
Time-series
| Method | Description |
|---|---|
getTimeseries(opts) |
Bucketed volume / deposits / intents by hour or day (Pro). |
History
| Method | Description |
|---|---|
getMakerHistory(address) |
Maker historical stats, platform/currency breakdowns, recent activity |
getTakerHistory(address) |
Taker historical stats, lock score, tier progression |
Metadata
| Method | Description |
|---|---|
getCurrencies() |
Supported fiat currencies with codes, labels, flags, hashes |
getPlatforms() |
Supported payment platforms with IDs, labels, method hashes |
Account Management
| Method | Description |
|---|---|
listKeys() |
List API keys (each carries a stable opaque id) |
createKey(label?) |
Create a new API key (POST /account/keys) |
rotateKey(id) |
Rotate by opaque id (POST /account/keys/{id}/rotate) |
deleteKey(id) |
Delete by opaque id (DELETE /account/keys/{id}) |
getCredits() |
Credit balance and purchase history |
createCheckout(pkg) |
Create a credit checkout order (starter, growth, scale) |
Webhooks
| Method | Description |
|---|---|
listWebhooks() |
List registered outbound webhooks |
createWebhook({url,events}) |
Register a new endpoint; secret returned once |
updateWebhook(id, {status}) |
Toggle status (POST — PATCH was retired with v2) |
deleteWebhook(id) |
Permanently remove an endpoint |
Canonical event names: deposit.created, intent.signaled, intent.fulfilled. Legacy aliases (intent.created, intent.filled) are still accepted on registration but normalized server-side. Rate updates remain activity-feed events, not outbound webhook fanout.
Migration from v0.x
@peerlytics/sdk@1.0.0 targets the v2 wire format (April 2026 redesign). The TS surface stays largely camelCase — the SDK adapts the snake_case wire format back for you — but a few things did change:
rotateKey(...)anddeleteKey(...)now take the opaqueid(sha256 of the key, exposed on everyApiKeyInfo), not the raw key. Full secrets are returned only fromcreateKey()androtateKey(), never fromlistKeys().- Timestamp fields (
createdAt,lastUsedAt,freeCreditsResetAt) are nownumber | string— the v2 server emits Unix seconds (integer); the v1-compat path still emits ISO strings. Always coerce explicitly if you touch the values. - New methods:
listWebhooks,createWebhook,updateWebhook,deleteWebhook. freeCreditsResetAtis seconds (was milliseconds in v1).
Error Handling
import { PeerlyticsError, RateLimitError, NotFoundError, ValidationError } from "@peerlytics/sdk";
try {
await client.getDeposit("missing");
} catch (err) {
if (err instanceof RateLimitError) {
console.log(`Retry after ${err.retryAfter}s`);
} else if (err instanceof NotFoundError) {
console.log("Not found");
} else if (err instanceof ValidationError) {
console.log(`Bad request: ${err.code} - ${err.message}`);
} else if (err instanceof PeerlyticsError) {
console.log(`HTTP ${err.status}: ${err.message}`);
}
}All errors extend PeerlyticsError with status, code, message, and details properties.
Filter Arrays
Array filter values are comma-joined automatically:
// deposits on revolut or wise, in GBP or EUR
await client.getDeposits({
platform: ["revolut", "wise"],
currency: ["GBP", "EUR"],
});
// activity for multiple deposit IDs
await client.getActivity({
depositId: ["42", "43", "44"],
type: ["intent_signaled", "intent_fulfilled"],
});Related
- Dashboard: https://peerlytics.xyz
- Developer docs: https://peerlytics.xyz/developers
- Agent docs: https://peerlytics.xyz/developers?tab=agents
- LLM docs: https://peerlytics.xyz/llms.txt
- Trading: https://usdctofiat.xyz
License
MIT