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

@paychainly/sdk

v1.0.14

Published

TypeScript SDK for the Paychainly crypto payment platform

Downloads

199

Readme

@paychainly/sdk

TypeScript SDK for the Paychainly crypto payment platform. Accept USDT on BNB Smart Chain — manage customers, deposit addresses, payment links, invoices, and withdrawals without writing raw HTTP calls.

npm version TypeScript Network


Table of contents


What's new in 1.0.14

  • Retry with exponential backoffnew Paychainly({ apiKey, retries: 3, retryDelay: 500 }) automatically retries failed requests with exponential back-off.
  • Per-call timeoutpaychainly.transactions.list({}, { timeout: 5000 }) lets you override the default request timeout on any individual call.
  • Auto-paginationawait paychainly.customers.listAll() fetches every page automatically and returns a flat array.
  • Webhook signPaychainly.webhooks.sign(payload, secret) generates an HMAC-SHA256 signature for outgoing webhook payloads.
  • Webhook verifyBrowserawait Paychainly.webhooks.verifyBrowser(body, sig, secret) verifies webhook signatures using the Web Crypto API (no Node.js crypto dependency).
  • Extended tx filterspaychainly.transactions.list({ dateFrom, dateTo, minAmount, maxAmount }) allows filtering transactions by date range and amount range.
  • revokeByAddresspaychainly.addresses.revokeByAddress('0xABC...') revokes a deposit address directly by its on-chain address without needing the internal ID.
  • Sandbox creditpaychainly.sandbox.credit('0xDEPOSIT...', '50.00') simulates a USDT deposit in sandbox mode for testing your integration end-to-end.

Installation

npm install @paychainly/sdk
# or
yarn add @paychainly/sdk

Environment variables

Add these to your .env file:

PAYCHAINLY_API_KEY=your_api_key_here
WEBHOOK_SECRET=your_webhook_secret_here

Get both from your Paychainly dashboard.


Setup

Create one instance and export it — import it everywhere else.

// lib/paychainly.ts
import { Paychainly } from '@paychainly/sdk';

export const paychainly = new Paychainly({
  apiKey:  process.env.PAYCHAINLY_API_KEY!,
  baseUrl: 'https://api.paychainly.com', // optional — this is the default
  timeout: 30_000,                        // optional — ms, default 30s
});

Quick start

Not sure which pattern to use? Pick based on your use case:

| Use case | Pattern | |---|---| | User wallet / balance top-up | Pattern 1 — Wallet | | E-commerce order / SaaS subscription | Pattern 2 — Checkout (logged-in) | | One-off payment, no account needed | Pattern 2 — Guest checkout |


Pattern 1 — Wallet / deposit system

Each user gets one permanent deposit address. They send USDT to it — you credit their balance. Best for top-up flows, wallets, and balance-based systems.

Step 1 — Get or create the customer

customers.create() throws a 409 if the customer already exists. Always wrap it with a fallback to getByIdentifier() so it's safe to call on every login or page load — it will never create duplicates.

import { Paychainly, ApiError } from '@paychainly/sdk';
import { paychainly } from './lib/paychainly';

/**
 * Returns existing customer if already created, creates one if not.
 * Safe to call on every login / page load — never creates duplicates.
 */
async function getOrCreateCustomer(user: {
  id:     string;   // your unique user ID — required
  email?: string;
  name?:  string;
}) {
  try {
    return await paychainly.customers.create({
      identifier: user.id,
      email:      user.email,
      name:       user.name,
    });
  } catch (err) {
    if (err instanceof ApiError && err.status === 409)
      return await paychainly.customers.getByIdentifier(user.id);
    throw err;
  }
}

The returned customer object:

// {
//   id:          42,
//   customerUid: "dd8693ec-8b5f-43ef-b4e5-1d0a088df1a3",  ← Paychainly UUID
//   identifier:  "user_abc123",                            ← your userId
//   email:       "[email protected]",
//   name:        "John Doe",
//   metadata:    {},
//   createdAt:   "2026-06-02T10:00:00.000Z",
//   updatedAt:   "2026-06-02T10:00:00.000Z"
// }

Step 2 — Get the wallet address

mode: 'reuse' returns the same address every time for this customer — call it as many times as you want.

async function getWalletAddress(userId: string) {
  const wallet = await paychainly.addresses.generate({
    tokenSymbol: 'USDT',
    network:     'BNB',
    mode:        'reuse',
    customer:    { identifier: userId },
  });

  return {
    address:     wallet.address,      // 0xABC... — show this to the user
    network:     wallet.network,      // "BNB Smart Chain"
    tokenSymbol: wallet.tokenSymbol,  // "USDT"
  };
}

Step 3 — Put it together

async function setupUserWallet(user: { id: string; email?: string; name?: string }) {
  const customer = await getOrCreateCustomer(user);
  const wallet   = await getWalletAddress(user.id);

  return {
    customerId:  customer.id,
    customerUid: customer.customerUid,
    address:     wallet.address,
    network:     wallet.network,
    tokenSymbol: wallet.tokenSymbol,
  };
}

// Call this whenever the user opens their wallet page
const wallet = await setupUserWallet({ id: 'user_abc123', email: '[email protected]' });
console.log('Deposit address:', wallet.address);
// → "Send USDT (BEP-20) to 0xABC... on BNB Smart Chain"

Step 4 — How users send USDT

After you show wallet.address to your user, they have three options:

Option A — Copy and paste (works with any wallet app) Show the address as text with a copy button. User opens Binance, Trust Wallet, MetaMask etc., pastes the address, and sends USDT.

Option B — QR code (mobile-friendly) Encode wallet.address as a QR code. User scans it with their mobile wallet app.

Option C — Connect wallet (MetaMask / Web3) Use ethers.js or wagmi on the frontend to trigger a direct USDT transfer. No copy-paste needed — the user clicks "Pay" in MetaMask.


Pattern 2 — Per-order checkout

Each order gets its own checkout: a fresh deposit address + an optional hosted payment page. Works with or without a logged-in user.

paymentLinks.create() does everything in one call: assigns a deposit address, creates the hosted page, and returns all the information you need.

Step 1 — Create the checkout link

import { paychainly } from './lib/paychainly';

async function createCheckout(params: {
  orderId:   string;
  amount:    string;                        // e.g. "49.99"
  memo?:     string;                        // shown to the payer on the checkout page
  note?:     string;                        // internal — not shown to payer
  userId?:   string;                        // omit for guest checkout
  metadata?: Record<string, unknown>;
}) {
  const link = await paychainly.paymentLinks.create({
    // Required
    uniqueId:    params.orderId,            // idempotency key — safe to retry
    tokenSymbol: 'USDT',
    network:     'BNB',

    // Optional
    amount:      params.amount,            // omit for open-amount link
    memo:        params.memo ?? `Order #${params.orderId}`,
    note:        params.note,
    expiryHours: 24,                       // omit for no expiry
    // expiresAt: '2026-12-31T23:59:59Z', // alternative: exact ISO 8601 datetime
    metadata:    { orderId: params.orderId, ...params.metadata },

    // Customer — omit entirely for guest checkout
    ...(params.userId ? { customer: { identifier: params.userId } } : {}),
  });

  return link;
}

The returned link object:

// {
//   id:          77,
//   slug:        "abc123xyz",
//   uniqueId:    "order_001",
//   address:     "0xDEPOSIT...",
//   payUrl:      "https://paychainly.com/pay/abc123xyz",
//   amount:      "49.99",
//   tokenSymbol: "USDT",
//   network:     "BNB",
//   status:      "active",
//   expiresAt:   "2026-06-03T10:00:00.000Z",
//   metadata:    { orderId: "order_001" },
//   createdAt:   "2026-06-02T10:00:00.000Z"
// }

Step 2 — Use the checkout

You have three options from the same API response — pick one:

const link = await createCheckout({
  orderId: 'order_001',
  amount:  '49.99',
  memo:    'Premium Plan',
  userId:  'user_abc123',
});

// Option 1 — redirect to Paychainly's hosted checkout page
// Includes QR code, countdown timer, MetaMask connect, and payment confirmation
window.location.href = link.payUrl!;
// → https://paychainly.com/pay/abc123xyz

// Option 2 — show in your own custom UI
console.log(`Send ${link.amount} ${link.tokenSymbol} to: ${link.address}`);
console.log(`Expires at: ${link.expiresAt}`);

// Option 3 — QR-encode link.address yourself
// (any QR library works — encode link.address as the value)

Guest checkout (no user account needed)

const guestLink = await createCheckout({
  orderId: 'order_002',
  amount:  '29.99',
  // no userId — no customer record created
});

window.location.href = guestLink.payUrl!;

Receiving payments

Webhooks (recommended)

Set your webhook URL in the dashboard. Paychainly calls your endpoint the moment USDT arrives.

Important: Use express.raw(), not express.json(). Signature verification requires the raw request body.

Wallet pattern — credit user balance:

import express from 'express';
import { Paychainly } from '@paychainly/sdk';
import { paychainly } from './lib/paychainly';

app.post('/webhooks/paychainly', express.raw({ type: '*/*' }), async (req, res) => {
  // 1. Verify the signature — rejects anything fake
  let event;
  try {
    event = Paychainly.webhooks.verify(
      req.body,
      req.headers['x-paychainly-signature'] as string,
      process.env.WEBHOOK_SECRET!,
    );
  } catch {
    return res.sendStatus(400);
  }

  if (event.event === 'deposit_detected') {
    // 2. Find which user owns this deposit address
    const customer = await paychainly.customers.getByDepositAddress(event.toAddress);
    // 3. Credit their balance in YOUR database
    await creditUserBalance(customer.identifier, event.amount);

    console.log(`+${event.amount} USDT credited to ${customer.identifier}`);
    console.log(`TX hash: ${event.txHash} — block: ${event.blockNumber}`);
  }

  res.sendStatus(200); // always 200 — prevents retries
});

Checkout pattern — fulfil an order:

if (event.event === 'deposit_detected') {
  // Fetch the payment link to get your metadata back
  const [link] = await paychainly.paymentLinks.getByAddress(event.toAddress);
  if (link) {
    const orderId = link.metadata?.orderId as string;
    await fulfillOrder(orderId, event.amount, event.txHash);
    console.log(`Order ${orderId} paid — ${event.amount} USDT (tx: ${event.txHash})`);
  }
}

The webhook event object:

// {
//   event:       "deposit_detected",
//   txHash:      "0xabc123...",
//   fromAddress: "0xUSER_WALLET...",   ← who sent the USDT
//   toAddress:   "0xDEPOSIT...",       ← your user's deposit address
//   amount:      "50.00",
//   blockNumber: 48123456,
//   timestamp:   1748862000,
//   userId:      "user_abc123"         ← the identifier you set on the customer
// }

Polling (no webhook needed)

Useful during development or when webhooks aren't set up yet. Not recommended for production.

async function checkForDeposit(address: string) {
  const { data, total } = await paychainly.transactions.listByAddress(address, { limit: 5 });
  if (total > 0) {
    const tx = data[0];
    // status: "pending" | "confirmed" | "swept" | "failed"
    console.log(`${tx.amount} USDT — ${tx.status} — ${tx.txHash}`);
    return tx;
  }
  return null;
}

Fetch a transaction by hash

Available at any time, not just inside webhooks:

const tx = await paychainly.transactions.getByHash('0xabc123...');
console.log(tx.amount, tx.status, tx.blockNumber);

// Full transaction object:
// {
//   id:          123,
//   txHash:      "0xabc123...",
//   fromAddress: "0xUSER_WALLET...",
//   toAddress:   "0xDEPOSIT...",
//   amount:      "50.00",
//   tokenSymbol: "USDT",
//   network:     "BNB",
//   blockNumber: 48123456,
//   status:      "swept",
//   mode:        "production",
//   createdAt:   "2026-06-02T10:00:00.000Z"
// }

Customer lookup reference

identifier is required when creating a customer. When referencing them later, use whichever you have:

// Using your own userId — recommended
customer: { identifier: 'user_abc123' }

// Using Paychainly's UUID (stored from create())
customer: { customerUid: 'dd8693ec-8b5f-43ef-b4e5-1d0a088df1a3' }

// Using email
customer: { email: '[email protected]' }

| You have | Use | |---|---| | Your own user ID | { identifier: userId } ← recommended | | Paychainly UUID from create() | { customerUid: "dd8693ec-..." } | | User's email | { email: "[email protected]" } |


Error handling

import { ApiError } from '@paychainly/sdk';

try {
  await paychainly.customers.create({ identifier: 'user_123' });
} catch (err) {
  if (err instanceof ApiError) {
    console.log(err.status);   // HTTP status code, e.g. 409
    console.log(err.code);     // machine-readable code, e.g. "DUPLICATE_IDENTIFIER"
    console.log(err.message);  // human-readable description
  }
}

Common error codes:

| Status | Code | Cause | |---|---|---| | 409 | DUPLICATE_IDENTIFIER | Customer already exists. Use getByIdentifier() instead. | | 400 | INVALID_SIGNATURE | Webhook signature mismatch. Check WEBHOOK_SECRET. | | 401 | — | Missing or invalid API key. Check PAYCHAINLY_API_KEY. | | 404 | — | Resource not found — double-check identifiers or addresses. |


API reference

paychainly.customers

| Method | Description | |---|---| | create(data) | Create a new customer. Throws 409 if identifier already exists. | | list(options?) | List all customers with optional pagination. | | get(id) | Get customer by Paychainly numeric ID. | | getByIdentifier(identifier) | Get customer by your own user ID. | | getByEmail(email) | Get customer by email address. | | getByUid(uid) | Get customer by Paychainly UUID. | | getByDepositAddress(address) | Look up which customer owns a deposit address. | | updateByIdentifier(identifier, data) | Update customer fields by your user ID. | | updateByEmail(email, data) | Update customer fields by email. |

paychainly.addresses

| Method | Description | |---|---| | generate(params) | Generate a deposit address. Use mode: 'reuse' for permanent wallets. | | list(options?) | List all deposit addresses. | | get(id) | Get address by numeric ID. | | getByAddress(address) | Get address record by on-chain address string. | | revoke(id) | Revoke a deposit address. |

paychainly.transactions

| Method | Description | |---|---| | list(options?) | List all transactions. | | listByAddress(address, options?) | List transactions for a specific deposit address. | | get(id) | Get transaction by numeric ID. | | getByHash(txHash) | Get transaction by on-chain hash. |

paychainly.paymentLinks

| Method | Description | |---|---| | create(params) | Create a payment link with a fresh deposit address and hosted page. | | list(options?) | List all payment links. | | get(id) | Get payment link by numeric ID. | | getBySlug(slug) | Get payment link by URL slug. | | getByAddress(address) | Get payment links associated with a deposit address. | | createForAddress(address, params?) | Create a payment link for an existing deposit address. |

paychainly.withdrawals

| Method | Description | |---|---| | create(params) | Initiate a USDT withdrawal. | | list(options?) | List all withdrawals. | | listByAddress(address, options?) | List withdrawals for a specific address. | | get(id) | Get withdrawal by numeric ID. | | cancel(id) | Cancel a pending withdrawal. |

paychainly.invoices

| Method | Description | |---|---| | get(id) | Get invoice by numeric ID. | | getById(invoiceId) | Get invoice by invoice identifier. | | getByHash(txHash) | Get invoice by on-chain transaction hash. |

paychainly.system

| Method | Description | |---|---| | getHealth() | Check API health status. |

Paychainly.webhooks (static)

| Method | Description | |---|---| | verify(body, signature, secret) | Verify webhook signature. Throws if invalid. |


Pattern comparison

| # | Pattern | Customer | Address mode | Payment link | Best for | |---|---|---|---|---|---| | 1 | Wallet — address only | Required | reuse — permanent | Not needed | Open-ended deposits, balance top-ups | | 2 | Wallet — with payment link | Required | reuse — same address | createForAddress() per top-up | Fixed-amount top-ups with hosted page | | 3 | Order checkout — logged-in user | Required | generate_new per order | paymentLinks.create() per order | E-commerce, invoices, subscriptions | | 4 | Guest checkout | Not needed | generate_new per order | paymentLinks.create() per order | One-off payments, anonymous checkout |


Links