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

@zettapay/sdk

v0.1.4

Published

ZettaPay TypeScript SDK — typed client, on-chain helpers, and Node-only webhook verifier (@zettapay/sdk/server) for merchants integrating the ZettaPay BTC + EVM USDC payment protocol.

Readme

@zettapay/sdk

Typed TypeScript client for the ZettaPay merchant + X-402 payments API.

Install

npm install @zettapay/sdk

Quickstart — pubkey lives in your code

ZettaPay is a P2P confirmation-tracking protocol. Your wallet addresses live in your env vars, not on our servers. Sign up at zettapay.io/signup for email + shop name to receive api_key + webhook_secret, then configure pubkeys client-side:

# .env — stays on your servers, never on ours
ZETTAPAY_API_KEY=sk_live_...
ZETTAPAY_WEBHOOK_SECRET=whsec_...

# Wallet addresses you control — set any subset
MERCHANT_BTC_PUBKEY=bc1qx5...e92
MERCHANT_ETH_PUBKEY=0x7a3...4F2
MERCHANT_SOL_PUBKEY=7Np41oeYqPefeNQEHSv1UDhYrehxin3NStpSyab9YVhT
import { ZettaPay } from '@zettapay/sdk';

const zp = new ZettaPay({
  apiKey:        process.env.ZETTAPAY_API_KEY!,
  webhookSecret: process.env.ZETTAPAY_WEBHOOK_SECRET!,
  pubkeys: {
    btc: process.env.MERCHANT_BTC_PUBKEY,
    eth: process.env.MERCHANT_ETH_PUBKEY,
    sol: process.env.MERCHANT_SOL_PUBKEY,
  },
  webhookUrl: 'https://my-app.com/webhooks/zettapay',
});

// Idempotent — registers the pubkeys with the ZettaPay chain listener.
// Call on boot. Re-running with new env vars rotates keys (no dashboard edit).
await zp.register();

Rotate any key by editing your .env and redeploying — the next zp.register() call swaps the address the chain listener is watching. No login, no support ticket. dev / staging / prod are just three different env files.

Low-level client (advanced)

import { ZettaPayClient, ZettaPayError } from '@zettapay/sdk';

const client = new ZettaPayClient({ baseURL: 'https://api.zettapay.dev' });

// Register a merchant
const merchant = await client.registerMerchant({
  name: 'Acme Coffee',
  walletPubkey: '7Np41oeYqPefeNQEHSv1UDhYrehxin3NStpSyab9YVhT',
  usdcAta: 'EhpbDdUDKv2Ah6yyhyqz7n9zUQqvmW1qzPKNaqgQ4kZK',
});

// Submit an X-402 payment (base64-encoded signed Solana tx)
const receipt = await client.pay({ transaction: signedTransactionBase64 });

// Look up a payment
const record = await client.getPayment(receipt.paymentId);

Errors thrown by the SDK are ZettaPayError instances exposing code, status, and details mirroring the API error envelope.

API surface

| Method | HTTP | Description | | --- | --- | --- | | pay(input) | POST /pay | Submit a signed transaction via the x-402-payment header. | | registerMerchant(input) | POST /merchants | Create a merchant. | | getMerchant(id) | GET /merchants/:id | Fetch a merchant. | | listMerchants(opts) | GET /merchants | Paginated merchant list. | | updateMerchant(id, patch) | PATCH /merchants/:id | Patch a merchant. | | deleteMerchant(id) | DELETE /merchants/:id | Remove a merchant. | | getPayment(id) | GET /payments/:id | Fetch a recorded payment. | | listPayments(opts) | GET /payments | Paginated payment list. | | health() | GET /healthz | Liveness probe. | | invoices.create(input) | POST /api/invoices | Multi-chain invoice (BTC / Base / Polygon / Ethereum). | | invoices.get(id) | GET /api/invoices/:id | Fetch a multi-chain invoice. |

Multi-chain invoices

const invoice = await zp.invoices.create({
  amount_usd: 29,
  chain: 'base', // 'btc' | 'base' | 'polygon' | 'ethereum'
  metadata: { order_id: 'xyz' },
});
console.log(invoice.receive_address, invoice.amount_native);

Webhook payloads on multi-chain invoices include a chain field. Legacy events emit chain: 'unknown' — use normalizeWebhookChain() for safe parsing.

On-chain helpers (Z9 — Anchor program)

The SDK ships PDA derivation and Anchor-encoded instruction builders for the ZettaPay merchant binding program (programs/zettapay). The program is deployed at:

Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS  // devnet + localnet
import {
  Connection,
  Keypair,
  clusterApiUrl,
} from '@solana/web3.js';
import { randomBytes } from 'node:crypto';
import {
  registerMerchantOnChain,
  recordPayment,
  deriveMerchantBindingPda,
  derivePaymentPda,
  PAYMENT_ID_LEN,
} from '@zettapay/sdk';

const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');
const owner = Keypair.generate();
const usdcTokenAccount = /* merchant's USDC ATA */ owner.publicKey;

// 1) Bind a handle on-chain (immutable PDA = [handle, owner])
const { signature, pda } = await registerMerchantOnChain({
  connection,
  owner: owner.publicKey,
  payer: owner.publicKey,
  merchantHandle: 'acme-store',
  usdcTokenAccount,
  signers: [owner],
});

// 2) Record an already-settled USDC transfer (immutable PDA = [binding, paymentId])
const paymentId = randomBytes(PAYMENT_ID_LEN);
const txSignature = randomBytes(64); // signature of the underlying SPL transfer
await recordPayment({
  connection,
  merchantBinding: pda,
  payer: owner.publicKey,
  paymentId,
  amount: 1_500_000n, // 1.5 USDC (6 decimals)
  txSignature,
  signers: [owner],
});

| Helper | Returns | Purpose | | --- | --- | --- | | deriveMerchantBindingPda(handle, owner) | { pda, bump } | Off-chain PDA derivation matching the Rust seed contract. | | derivePaymentPda(merchantBinding, paymentId) | { pda, bump } | Off-chain payment receipt PDA derivation. | | buildRegisterMerchantInstruction(params) | TransactionInstruction | Compose the register_merchant ix without sending. | | buildRecordPaymentInstruction(params) | TransactionInstruction | Compose the record_payment ix without sending. | | registerMerchantOnChain(params) | Promise<{ signature, pda }> | End-to-end build → sign → confirm. | | recordPayment(params) | Promise<{ signature, pda }> | End-to-end build → sign → confirm. |

The IDL is exposed as ZETTAPAY_IDL for callers that want to wire it through @coral-xyz/anchor directly.

To redeploy the program (devnet), see scripts/deploy-devnet.sh at the repo root.

High-level helpers (Z27.1 — no backend required)

These call Solana RPC + the ZettaPay program directly. No API keys, no ZettaPayClient.

import {
  Connection,
  Keypair,
  clusterApiUrl,
} from '@solana/web3.js';
import {
  createMerchant,
  createInvoice,
  getInvoiceStatus,
  listenPaymentEvents,
  sweep,
  USDC_DEVNET_MINT,
} from '@zettapay/sdk';

const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');
const owner = Keypair.generate();

// 1) Bind merchant on-chain (creates the USDC ATA if missing)
const { merchantBinding } = await createMerchant({
  connection,
  owner,
  merchantHandle: 'acme-store',
  mint: USDC_DEVNET_MINT,
});

// 2) Off-chain: derive the payment PDA the payer must settle
const invoice = createInvoice({
  merchantHandle: 'acme-store',
  merchantOwner: owner.publicKey,
  amount: 1_500_000n, // 1.5 USDC
  expiresAt: Math.floor(Date.now() / 1000) + 600,
});

// 3) Poll status (pending | paid | expired)
const status = await getInvoiceStatus({ connection, invoice });

// 4) Push-based — subscribe to new payments for this merchant
const sub = await listenPaymentEvents({
  connection,
  merchantBinding,
  onEvent: (e) => console.log('settled', e.paymentIdHex, e.amount),
});
// later: await sub.close();

// 5) Drain merchant ATA into a treasury wallet
await sweep({
  connection,
  owner,
  mint: USDC_DEVNET_MINT,
  destination: new PublicKey('...treasury wallet...'),
});

| Helper | Returns | Purpose | | --- | --- | --- | | createMerchant(params) | { signature, merchantBinding, payoutTokenAccount, createdPayoutAta } | One-shot on-chain merchant registration. Creates the payout ATA if missing. | | createInvoice(params) | Invoice | Pure off-chain. Generates 32-byte invoice id + derives the payment receipt PDA the payer must settle. | | getInvoiceStatus({ connection, invoice }) | { status: 'pending' \| 'paid' \| 'expired', receipt } | Polls the receipt PDA. Returns parsed amount + tx signature when paid. | | listenPaymentEvents(params) | { id, close() } | WebSocket subscription filtered to a single merchant's receipts. | | sweep(params) | { signature, amount, source, destinationTokenAccount, noop } | Drain an SPL token ATA to a destination wallet/account using transferChecked. |

Receiving webhooks

Imported from @zettapay/sdk/server (Node-only). Verifies the HMAC-SHA256 signature in X-ZettaPay-Signature, enforces a 5-minute timestamp tolerance (replay protection), and returns a typed ZettaPayEvent you can branch on by event.type.

Pass the raw request body. Re-serializing JSON changes byte order and breaks the signature.

Next.js (App Router)

// app/api/zettapay/webhook/route.ts
import {
  verifyWebhookSignature,
  WebhookSignatureError,
} from '@zettapay/sdk/server';

export async function POST(req: Request) {
  const body = await req.text();
  const sig = req.headers.get('x-zettapay-signature');
  const ts = req.headers.get('x-zettapay-timestamp');
  if (!sig || !ts) return new Response('missing headers', { status: 400 });

  try {
    const event = verifyWebhookSignature(
      body,
      sig,
      ts,
      process.env.ZETTAPAY_WEBHOOK_SECRET!,
    );

    if (event.type === 'invoice.confirmed') {
      await markInvoicePaid(event.data.invoice_id, event.data.tx_hash);
    }

    return Response.json({ ok: true });
  } catch (err) {
    if (err instanceof WebhookSignatureError) {
      return Response.json({ ok: false, code: err.code }, { status: 401 });
    }
    throw err;
  }
}

Express

import express from 'express';
import {
  verifyWebhookSignature,
  WebhookSignatureError,
} from '@zettapay/sdk/server';

const app = express();
// raw body is required for HMAC — do not use express.json() on this route
app.post(
  '/api/zettapay/webhook',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const sig = req.header('x-zettapay-signature');
    const ts = req.header('x-zettapay-timestamp');
    if (!sig || !ts) return res.status(400).send('missing headers');

    try {
      const event = verifyWebhookSignature(
        (req.body as Buffer).toString('utf8'),
        sig,
        ts,
        process.env.ZETTAPAY_WEBHOOK_SECRET!,
      );
      // ...handle event by event.type
      res.json({ ok: true });
    } catch (err) {
      if (err instanceof WebhookSignatureError) {
        return res.status(401).json({ ok: false, code: err.code });
      }
      throw err;
    }
  },
);

Event types

| event.type | Shape of event.data | | --- | --- | | invoice.confirmed | { invoice_id, address, amount_sats, tx_hash, confirmations, paid_at } | | invoice.pending | { invoice_id, address, amount_sats, tx_hash, confirmations, seen_at } | | invoice.expired | { invoice_id, address, amount_sats, expired_at } | | invoice.underpaid | { invoice_id, address, amount_sats, received_sats, tx_hash, seen_at } |

WebhookSignatureError.code is one of invalid_signature, timestamp_too_old, or malformed.