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

@moon-x/node-sdk

v0.8.0

Published

MoonX server-side SDK for Node.js. Verify MoonX-issued access + identity tokens with zero runtime dependencies.

Readme

@moon-x/node-sdk

MoonX server-side SDK for Node.js. Verify MoonX-issued access and identity tokens with zero runtime dependencies.

Works in Node 18+, Cloudflare Workers, Vercel Edge, Bun, and Deno — anywhere globalThis.crypto.subtle and fetch are available.

Install

npm i @moon-x/node-sdk
# or
pnpm add @moon-x/node-sdk

Quick start

import { MoonXClient } from "@moon-x/node-sdk";

const moonx = new MoonXClient({
  publishableKey: process.env.MOONX_PUBLISHABLE_KEY!,
  issuer: process.env.MOONX_AUTH_ISSUER, // optional, defaults to https://api.moonx-dev.com
});

// In a Next.js route / Express handler / etc.
const session = await moonx.auth.verifySession({
  accessToken: req.headers["x-moonx-access-token"]!,
  identityToken: req.headers["x-moonx-identity-token"],
});

// session.access.sub        — user id
// session.access.sid        — session id
// session.identity?.email   — OIDC profile claim (present when identity token forwarded)
// session.identity?.name
// session.identity?.picture

What it does

When a MoonX-authenticated user calls your backend, the request carries one or both of:

| Header | Token | Carries | |---|---|---| | X-MoonX-Access-Token | Access token | Minimal authorization claims (sub, sid, aud, iss, exp) | | X-MoonX-Identity-Token | Identity token | OIDC profile claims (email, name, picture, etc.) |

verifySession() runs the full canonical check on both:

  1. Pin audience to your app id (rejects tokens from other MoonX apps even if cryptographically valid).
  2. Pin issuer to your configured MoonX deployment.
  3. Fetch the per-app public JWKS from MoonX (cached 5 min by default).
  4. Verify the ES256 signature against the fetched key.
  5. Check the token hasn't expired.
  6. (Identity token path) Verify the identity token's sub matches the access token's sub — closes the swap-in-someone-else's-identity-token attack.

No shared secret with MoonX is required. The verification runs entirely against the public JWKS.

API

new MoonXClient(config)

Construct once per process. Holds in-memory caches; reuse across requests.

| Field | Type | Required | Default | |---|---|---|---| | publishableKey | string | Yes | — | | secretKey | string | For ephemeralSigners | n/a | | issuer | string | No | https://api.moonx-dev.com | | baseUrl | string | No | Same as issuer | | jwksTtlMs | number | No | 300_000 (5 min) | | appResolveTtlMs | number | No | 3_600_000 (1 hour) | | fetch | typeof fetch | No | globalThis.fetch |

The publishable key (moon_pk_*) is the same one your iframe / browser SDK uses; it resolves your app id and scopes JWKS lookups. The secret key (moon_sk_*) is required to use ephemeralSigners (the wallets server-side ephemeral-signer routes are secret-key-only) and by the v0.2 data + swaps modules. Read both from server-only env vars; never expose the secret key to the browser.

client.auth.verifyAccessToken(token)

Returns Promise<AccessTokenClaims>. Throws a MoonXError subclass on any failure.

client.auth.verifyIdentityToken(token)

Returns Promise<IdentityTokenClaims>. Throws a MoonXError subclass on any failure.

client.auth.verifySession({ accessToken, identityToken? })

Returns Promise<{ access: AccessTokenClaims, identity: IdentityTokenClaims | null }>. Identity is null when the caller didn't forward an identity token.

Canonical entry point — use this from request handlers. Cross-checks the two tokens' subjects when both are present.

client.funding.sign(params)

Request a signed MoonPay Buy-widget URL for a user's wallet, so your backend can drive the card-funding flow without holding MoonPay keys. Returns Promise<MoonPaySignResponse>.

The route is publishable-key acceptable but signs only for an address that belongs to the authenticated user, so you forward that user's access token per call:

const funding = await moonx.funding.sign({
  accessToken: req.headers["x-moonx-access-token"]!, // the end user's token
  publicAddress: "0xUserWalletAddress",
  useSandbox: true, // omit or false for production
  currencyCode: "eth", // optional MoonPay presets
  quoteCurrencyAmount: 50,
});

// funding.signed_url               — open this for the user
// funding.external_transaction_id  — correlate the purchase
// funding.provider_publishable_key — MoonPay pk the URL was signed with

| Param | Type | Required | Notes | |---|---|---|---| | accessToken | string | Yes | The end user's MoonX access token | | publicAddress | string | Yes | Wallet address to fund; must belong to the user | | useSandbox | boolean | No | true → MoonPay sandbox; defaults to false | | currencyCode | string | No | MoonPay currency to preselect | | quoteCurrencyAmount | number | No | Pin the fiat amount; omit to let the user choose | | paymentMethod | string | No | MoonPay payment method to preselect | | uiConfig | { theme?, accentColor? } | No | Widget theming |

Failures throw a typed MoonXError subclass: IntegrationNotConfiguredError (400), UnauthorizedError (401), WalletOwnershipError (403), RateLimitedError (429).

Errors

All thrown errors extend MoonXError, which carries a .code property for programmatic handling:

import {
  MoonXError,
  ExpiredTokenError,
  InvalidTokenError,
  AudienceMismatchError,
  SubjectMismatchError,
  // ...
} from "@moon-x/node-sdk";

try {
  await moonx.auth.verifySession({ accessToken, identityToken });
} catch (err) {
  if (err instanceof ExpiredTokenError) {
    return new Response("session expired", { status: 401 });
  }
  if (err instanceof MoonXError) {
    return new Response(err.message, { status: 401 });
  }
  throw err;
}

Error codes

| Class | .code | Meaning | |---|---|---| | MalformedTokenError | malformed_token | Not a well-formed JWT | | InvalidTokenError | invalid_signature | Signature didn't verify against the JWKS | | ExpiredTokenError | expired_token | exp claim is in the past | | AudienceMismatchError | audience_mismatch | Token's aud ≠ this client's app id | | IssuerMismatchError | issuer_mismatch | Token's iss ≠ configured issuer | | SubjectMismatchError | subject_mismatch | Identity token's sub ≠ access token's sub | | KidMismatchError | kid_mismatch | Token's kid ≠ current JWKS key id (likely rotation) | | UnsupportedAlgorithmError | unsupported_algorithm | Token uses something other than ES256 | | JwksFetchError | jwks_fetch_failed | Couldn't fetch the JWKS from MoonX | | AppResolutionError | app_resolution_failed | Publishable-key → app-id lookup failed | | ConfigurationError | configuration_error | Bad MoonXClient config, or a missing required call param | | IntegrationNotConfiguredError | integration_not_configured | MoonPay funding is not configured or is disabled for this app (400) | | WalletOwnershipError | wallet_ownership | The wallet address does not belong to the user (403) | | UnauthorizedError | unauthorized | Missing or invalid credentials (401) | | RateLimitedError | rate_limited | Request was rate limited (429) | | RequestFailedError | request_failed | Unexpected non-2xx response (carries .status) |

Type imports

AccessTokenClaims, IdentityTokenClaims, BaseTokenClaims, VerifiedSession, and MoonPaySignResponse are also exported from @moon-x/core/types — the single source of truth shared with the browser + RN SDKs. Either import path works:

// From node-sdk (convenience re-export):
import type { AccessTokenClaims } from "@moon-x/node-sdk";

// From core directly (matches the browser SDK call sites):
import type { AccessTokenClaims } from "@moon-x/core/types";

Why no JWT library?

Auth providers ship server SDKs partly to abstract away jose-style JWT libraries, but every dependency is a supply-chain surface. This SDK uses only Web Crypto (globalThis.crypto.subtle) and fetch, both Node 18+ standard library. Result: a single npm install line on your audit.

Roadmap

  • Current: auth (verifyAccessToken, verifyIdentityToken, verifySession), ephemeralSigners (secret-key gated server-side signing), and funding (MoonPay onramp signing).
  • Next: adds client.tokens.* and client.swaps.*.