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

@pollar/privy-adapter

v0.8.0

Published

Stateless HTTP proxy that lets Pollar sign Stellar transactions through your Privy account, without your Privy app secret leaving your infrastructure.

Readme

@pollar/privy-adapter

⚠️ Server-side only. This package starts an HTTP server with @hono/node-server and reads PRIVY_APP_SECRET / POLLAR_API_SECRET from the host environment. Importing it in a browser, React Native, or any other client-side bundle will leak credentials. The bundler will also blow up on node:crypto / @hono/node-server. If you need a browser-side Privy integration, use the Privy client SDK directly — not this package.

Stateless HTTP proxy that lets Pollar sign Stellar transactions through your Privy account, without your Privy APP_SECRET ever leaving your infrastructure.

You install this package in your own backend, point Pollar at your adapter's URL, and it brokers each call to Privy on demand. The adapter holds no state, has no database, and exposes a small set of HTTP endpoints authenticated with a single Bearer token issued by Pollar.

Install

npm install @pollar/privy-adapter

Requires Node 20+.

Quick start

import { createPollarPrivyAdapter } from '@pollar/privy-adapter';

const adapter = createPollarPrivyAdapter({
  getCredentials: async () => ({
    appId: process.env.PRIVY_APP_ID!,
    appSecret: process.env.PRIVY_APP_SECRET!,
  }),
  pollarApiSecret: process.env.POLLAR_API_SECRET!,
  network: 'mainnet',
  port: 3001,
});

await adapter.start();

With AWS Secrets Manager

getCredentials is async, so any secret manager works.

The secret must be stored as JSON {"appId": "...", "appSecret": "..."} — the example below calls JSON.parse directly on SecretString.

import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
import { createPollarPrivyAdapter } from '@pollar/privy-adapter';

const sm = new SecretsManagerClient({ region: 'us-east-1' });

const adapter = createPollarPrivyAdapter({
  getCredentials: async () => {
    const result = await sm.send(new GetSecretValueCommand({ SecretId: 'privy/credentials' }));
    return JSON.parse(result.SecretString!);
  },
  pollarApiSecret: process.env.POLLAR_API_SECRET!,
  network: 'mainnet',
});

await adapter.start();

The credentials are cached for 5 minutes by default. If getCredentials returns a different appId/appSecret after the cache expires, the underlying Privy client is rebuilt automatically — so you can rotate APP_SECRET without redeploying.

Graceful shutdown

const adapter = createPollarPrivyAdapter({
  /* ... */
});
await adapter.start();

const shutdown = async () => {
  await adapter.stop();
  process.exit(0);
};

process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);

Configuration

interface PollarPrivyAdapterConfig {
  // ── Required ───────────────────────────────────────────────────────────

  // Async credential resolver. Called on first request and when the cache expires.
  getCredentials: () => Promise<{ appId: string; appSecret: string }>;

  // Bearer token Pollar uses to authenticate calls. Generate it once, set it
  // here, and register it in your Pollar dashboard.
  pollarApiSecret: string;

  // Stellar network used to compute transaction hashes.
  network: 'mainnet' | 'testnet';

  // ── Optional (with defaults) ──────────────────────────────────────────

  port?: number; // 3001
  cacheTtlMs?: number; // 5 * 60 * 1000
  requestTimeoutMs?: number; // 10_000
  maxBodyBytes?: number; // 64 * 1024

  // ── Observability hooks ───────────────────────────────────────────────

  onWalletCreated?: (userId: string, address: string) => void;
  onTransactionSigned?: (walletAddress: string) => void;
  onError?: (error: Error, ctx: { endpoint: string; body: unknown }) => void;
}

Endpoints

All endpoints except /health require Authorization: Bearer <pollarApiSecret>.

Responses share the Pollar envelope:

// success
{ "content": { /* payload */ }, "code": "<SUCCESS_CODE>", "success": true }

// error
{ "code": "<ERROR_CODE>", "success": false }

// error with extra context (Zod issues or upstream reason)
{ "code": "VALIDATION_ERROR", "success": false, "issues": { /* ... */ } }
{ "code": "WALLET_CREATION_FAILED", "success": false, "reason": "Privy API: ..." }

| Method | Path | Body | Success code | HTTP | | ------ | ---------------------------- | ---------------------------------- | ------------------------------ | ---- | | GET | /health | — | PRIVY_ADAPTER_HEALTH_OK | 200 | | POST | /wallets/create | { userId } | PRIVY_ADAPTER_WALLET_CREATED | 201 | | POST | /wallets/create (existing) | { userId } | PRIVY_ADAPTER_WALLET_EXISTS | 200 | | POST | /wallets/sign | { userId, walletAddress, txXdr } | PRIVY_ADAPTER_TX_SIGNED | 200 | | GET | /wallets/:userId/address | — | PRIVY_ADAPTER_WALLET_ADDRESS | 200 |

/wallets/create is idempotent: if the user already has a Stellar wallet, the existing address is returned with code PRIVY_ADAPTER_WALLET_EXISTS.

/wallets/sign accepts a base64 transaction XDR. The adapter parses it, computes the transaction hash for the configured network, asks Privy to sign the hash (Stellar Tier 2: Ed25519 raw sign), assembles the DecoratedSignature, and returns the fully signed XDR.

Error codes

| Code | HTTP | When | | ------------------------ | ---- | ---------------------------------------------------------------------------- | | FORBIDDEN | 401 | Missing or wrong Bearer token; response carries WWW-Authenticate: Bearer … | | VALIDATION_ERROR | 400 | Body schema mismatch or invalid JSON | | VALIDATION_ERROR | 413 | Body exceeded maxBodyBytes — response carries reason: "body too large" | | WALLET_NOT_FOUND | 404 | User has no Stellar wallet | | WALLET_CREATION_FAILED | 502 | Privy upstream error during create | | WALLET_LOOKUP_FAILED | 502 | Privy upstream error during wallet lookup | | TX_INVALID_SIGNED_XDR | 400 | XDR could not be parsed, or transaction is a fee-bump (unsupported) | | TX_SIGN_FAILED | 502 | Privy upstream error during sign | | INTERNAL_SERVER_ERROR | 500 | Unexpected failure |

Security notes

  • The adapter is the sole holder of PRIVY_APP_SECRET in the request path. Pollar only ever sees the Bearer token you issue it.
  • Bearer comparison uses crypto.timingSafeEqual (constant time).
  • The adapter holds no persistent state. An in-memory LRU caches walletAddress → walletId (10 min TTL, max 1000 entries) to avoid extra Privy round-trips on hot paths; nothing else is retained.
  • Logs are off by default. Pipe onError to your own logger.

License

MIT