npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@peerlytics/sdk

v4.0.0

Published

Lightweight TypeScript SDK for the Peerlytics v1 API - ZKP2P P2P protocol analytics

Readme

@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.

The API speaks a Stripe-style wire format: snake_case fields, Unix-seconds timestamps, cursor pagination, and dedicated key endpoints. The SDK adapts it back to a camelCase TypeScript surface, so you work in camelCase and never touch the raw wire shape.

Agent bundle

If you are integrating through an agent (Claude Code, Cursor, etc.), start here:

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.upstreamSourceTruths.map((source) => source.label);
// ["Production ZKP2P indexer live GraphQL schema", "@zkp2p/indexer-schema", ...]

const dashboardPlaybook = getPeerlyticsDeveloperResources("dashboard");
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 releases/prod GraphQL schema, typed indexer package, Curator API, zkp2p-clients, and zkp2p/attestation-service.

Install

npm install @peerlytics/sdk
# or
bun add @peerlytics/sdk
# or
pnpm add @peerlytics/sdk

Quick 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" });

// executable route plan
const plan = await client.planRoutes({ currency: "GBP", amountUsd: 500 });
const best = plan.routes[0];
if (best?.executionMode === "single" && best.actions.take) {
  // The link preserves the planned amount and exact deposit/payment pair.
  console.log(best.actions.take);
} else if (best?.executionMode === "multi_intent") {
  // Each leg is a separate intent; the current buy sheet is not atomic.
  console.log(best.allocation.legs);
}

// 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 include meta, linked, etc.). The SDK unwraps this for you — every method returns the inner data directly. There is no success flag — branch on HTTP status.

  • getDeposits accepts empty filter sets. Unfiltered calls return a bounded page (default limit 50, hard-capped at 200). Optional filters: depositor, delegate, platform, currency, or a date window (from, to, or range).

  • getIntents accepts empty filter sets. Same page cap. Optional filters: taker, recipient, verifier, depositId, status, or a date window (from, to, or range). The owner alias is also accepted.

  • getActivity returns an envelope, not a raw array. The response is { events, count, hasMore, limit, offset, nextCursor, filters } — iterate over response.events, not response itself. When hasMore=true, pass the returned nextCursor back as cursor to 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). Every DepositMarket exposes both: currency (resolved, e.g. "GBP") and currencyCode (raw hash). Entries inside deposit.currencies[] also carry a resolved currency field alongside the raw currencyCode. If you need to build your own mapping, call getCurrencies() — each entry includes the code, 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 is 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. Empty calls return a bounded page (default 50, max 200). Optional filters: depositor, delegate, platform, currency, date window (from/to/range), status. sort=asc\|desc. | | getDeposit(id, params?) | Deposit detail with intents, payment methods, and linked data |

Intents

| Method | Description | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | getIntents(filters?) | Query intents. Empty calls return a bounded page (default 50, max 200). Optional filters: taker, recipient, verifier, depositId, status, date window (from/to/range). owner is also accepted as an 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, cancelled volume, and 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("galleonlabs");
const integrator = await client.getIntegrator("galleonlabs");
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("galleonlabs");
const integrator = await client.getIntegrator("galleonlabs");
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 | | ------------------------- | ---------------------------------------------------------- | | planRoutes(opts?) | Deterministic single, multi-intent, or partial allocations | | getOrderbook(opts?) | Live orderbook with exact per-deposit bounds and pairs | | getMarketSummary(opts?) | Rate statistics per (platform, currency) pair |

The orderbook API prefers the indexer's denormalized OrderbookEntry projection when available, which keeps platform filters canonicalized (for example, zelle-* variants collapse into one Zelle surface). Each level retains every deposit's own available amount, single-intent range, and exact payment pairs. Every pair carries isPublic, disputeProtectionOptedOut, and disputeProtectionRequiresStake. The opt-out field records an explicit exit from default-on protection; a non-opted-out pair remains public when it requires a taker to prepare stake before signalling. planRoutes uses those facts to produce allocation legs; only a currently valid public single leg exposes actions.take. Multi-intent routes must be submitted as separate intents and partial routes report their unallocated remainder. Without amountUsd, executionMode is null and no route is counted as a successful fill. The default book is public liquidity only; pass taker to inspect restricted deposit/payment-method tuples available to a specific buyer wallet. The response does not expose why access was granted.

getDeposit() and getIntent() expose the same canonical protection pair on linked.quoteCandidates. Use those fields as route metadata only: the transaction client must refresh authoritative access, protection, and stake state before signing or funding. getVerifier() also returns proof and linkedDeposits, whose restricted-access fields are provenance-free.

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, cancelled volume, and recent activity |

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) |

Keys & timestamps

  • rotateKey(...) and deleteKey(...) take the opaque id (sha256 of the key, exposed on every ApiKeyInfo), not the raw key. Full secrets are returned only from createKey() and rotateKey(), never from listKeys().
  • Timestamp fields (createdAt, lastUsedAt, freeCreditsResetAt) are typed number | string — the server emits Unix seconds (integer); the compatibility path emits ISO strings. Always coerce explicitly if you touch the values.
  • freeCreditsResetAt is expressed in seconds.

Version and release history live in package.json and CHANGELOG.md.

Error Handling

import {
  PeerlyticsError,
  RateLimitError,
  NotFoundError,
  ValidationError,
  InsufficientCreditsError,
} 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 InsufficientCreditsError) {
    // Out of credits — send the developer to a one-click top-up.
    console.log(`Buy more: ${err.checkoutUrl}`);
    for (const pkg of err.packages) {
      console.log(`  ${pkg.id}: ${pkg.credits} credits for $${pkg.priceUsd}`);
    }
  } else if (err instanceof PeerlyticsError) {
    console.log(`HTTP ${err.status}: ${err.message}`);
  }
}

All errors extend PeerlyticsError with status, code, message, and details properties. InsufficientCreditsError (a 402) additionally carries checkoutUrl and packages parsed from the error body.

Credit & rate-limit telemetry

Every request stamps the rate-limit and credit budget onto response headers. The SDK captures the latest snapshot so you can read it without touching the raw Response:

await client.getOrderbook({ currency: "GBP" });

const credits = client.getLastCredits();
// { remaining, source: "free" | "paid" | "none", cost, warning, buyUrl, paymentMethod }
if (credits?.warning === "free_tier_near_limit") {
  console.log(`Running low — top up at ${credits.buyUrl}`);
}

const rate = client.getLastRateLimit();
// { limit, remaining, resetInSeconds }
if (rate && rate.remaining !== null && rate.remaining < 5) {
  console.log(`Throttle: ${rate.remaining} left, resets in ${rate.resetInSeconds}s`);
}

Both accessors return null until a request surfaces the relevant headers, and individual fields are null when a specific header is absent. Credit fields are populated on the api-key path; x402 requests report paymentMethod: "x402" with no balance. Reacting to getLastCredits().warning lets you nudge a top-up before the hard 402 that throws InsufficientCreditsError.

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

License

MIT