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

@sweefi/facilitator

v0.2.0

Published

Self-hostable s402 payment verification and settlement service for SweeFi — runnable as `npx @sweefi/facilitator start`

Readme

@sweefi/facilitator

Self-hostable s402 payment verification and settlement service for SweeFi — runnable in one command.

npx @sweefi/facilitator start

Part of the SweeFi platform.


What it is

The facilitator is the off-chain settlement server in SweeFi's s402 payment flow. When a client wants to pay for a resource, it builds and signs a Sui (or Solana) transaction locally, then sends that signed payload here. The facilitator verifies the signature and payment requirements, broadcasts the transaction, and returns a settlement receipt.

Open source, Apache 2.0. The source is fully auditable. You can run it as a CLI (npx), self-host it in Docker, deploy it to Fly.io, or fork and embed it in your own service. SweeFi also operates a managed facilitator at https://s402.sweefi.com@sweefi/sui and @sweefi/hono point at it by default, so most integrators get working settlement without any deployment. Override facilitatorUrl in createS402Client (@sweefi/sui) or s402Gate (@sweefi/hono) to point at your own instance.

Supported payment schemes: exact, prepaid, stream, escrow.


How it fits: the sign-first model

Client
  │
  │  1. Resource server returns HTTP 402 with payment requirements
  │     (amount, payTo address, scheme, network)
  │
  │  2. Client builds + signs a Sui PTB locally
  │     (private key never leaves the client)
  │
  │  3. Client POSTs the signed payload to the facilitator
  │
  ▼
Facilitator  ←── this service
  │
  │  4. Verifies: signature validity, amount correctness,
  │     payTo address match, scheme rules
  │
  │  5. Broadcasts the transaction to Sui
  │
  ▼
Sui Network
  │
  │  6. Move contract executes: transfers funds, emits event,
  │     enforces fee split at the contract level
  │
  ▼
Resource Server
  │
  │  7. Client presents tx digest as proof of payment
  │     Resource server verifies on-chain and unlocks content

Why sign-first? The client's private key never travels over the network. The facilitator cannot redirect funds — it can only broadcast a transaction the client already constructed and signed. Move's balance conservation rules enforce the fee split on-chain; the facilitator cannot manipulate what the contract does.


Quickstart

Run it directly with npx

# Generate a strong API key
export API_KEYS=$(openssl rand -hex 32)

# Start the facilitator (defaults to port 4022)
npx @sweefi/facilitator start

That's it. The server is now listening on http://localhost:4022. Point an s402 client at it:

import { createS402Client } from "@sweefi/sui";
const client = createS402Client({ facilitatorUrl: "http://localhost:4022", apiKey: process.env.API_KEYS });

Use npx @sweefi/facilitator help to see all subcommands. Use npx @sweefi/facilitator version to print the version.

Run from a .env file

The CLI doesn't read .env itself — Node's built-in --env-file flag does, on Node ≥20.6:

# Edit .env with your API_KEYS, FEE_MICRO_PERCENT, etc.
node --env-file=.env $(which npx) @sweefi/facilitator start

For a fully containerized workflow, see Docker / Fly.io below.

Prerequisites

  • Node.js 20+ (22+ recommended)

Embed it in your own Node service

Skip the CLI and use the createApp factory directly:

import { serve } from "@hono/node-server";
import { createApp, loadConfig } from "@sweefi/facilitator";

const { app } = createApp(loadConfig());
serve({ fetch: app.fetch, port: 4022 });

Docker build

# Build from the platform monorepo root (workspace context required)
docker build -f products/sweefi/facilitator/Dockerfile -t sweefi-facilitator .

# Run it
docker run -p 4022:4022 \
  -e API_KEYS="your-secret-key" \
  -e FEE_MICRO_PERCENT="5000" \
  sweefi-facilitator

The Dockerfile uses a multi-stage build: deps → build → slim runtime image. The final image runs node dist/cli.mjs start on port 4022.

Fly.io deploy

The included fly.toml is pre-configured for a shared-cpu-1x 512 MB machine in sjc. It auto-stops when idle and auto-starts on incoming requests.

# One-time setup (from the platform monorepo root)
fly launch --no-deploy --config products/sweefi/facilitator/fly.toml \
  --dockerfile products/sweefi/facilitator/Dockerfile

# Set secrets (never commit these)
fly secrets set API_KEYS="your-secret-key-1,your-secret-key-2"
fly secrets set FEE_MICRO_PERCENT="5000"

# Optional: custom RPC
fly secrets set SUI_MAINNET_RPC="https://your-rpc-provider.example.com"

# Deploy
fly deploy --config products/sweefi/facilitator/fly.toml \
  --dockerfile products/sweefi/facilitator/Dockerfile

After deployment your facilitator URL will be something like https://swee-facilitator.fly.dev. Point your s402 resource server's facilitatorUrl config at that address.


Environment variables

Validated at startup with Zod. The server will refuse to start if required variables are missing or malformed.

| Variable | Required | Default | Description | |----------|----------|---------|-------------| | API_KEYS | Yes | — | Comma-separated list of bearer token API keys. Clients must present one of these in the Authorization header. Each key must be ≥ 16 characters — shorter keys are rejected at startup. Generate with openssl rand -hex 32. | | PORT | No | 4022 | Port to listen on. | | FEE_MICRO_PERCENT | No | 5000 | Protocol fee in micro-percent (5000 = 0.5%, 1000000 = 100%). Included in the /.well-known/s402.json discovery document and passed to scheme handlers for PTB construction. | | FACILITATOR_KEYPAIR | No | — | Base64-encoded Ed25519 keypair for gas sponsorship (reserved for future use). | | SUI_MAINNET_RPC | No | Mysten default | Custom RPC URL for sui:mainnet. Use this to point at a dedicated node or RPC provider. | | SUI_TESTNET_RPC | No | Mysten default | Custom RPC URL for sui:testnet. | | SWEEFI_PACKAGE_ID | No | — | SweeFi Move package ID. When set, scheme handlers verify that on-chain events originate from this package, preventing spoofed events from attacker-deployed contracts. | | LOG_LEVEL | No | info | Log verbosity: debug, info, warn, or error. |


API endpoints

All endpoints except /health and /.well-known/s402.json require an Authorization: Bearer <key> header.

Request bodies are capped at 256 KB. The server returns 429 if a client exceeds the rate limit (token bucket: 100 requests max, refills at 10 per second, keyed per API key).


GET /health

Liveness check. No authentication required.

Response

{ "status": "ok", "timestamp": "2026-02-16T00:00:00.000Z" }

GET /.well-known/s402-facilitator

Facilitator identity document. Returns a signed fee schedule describing who this facilitator is, what it charges, and the networks and schemes it supports. No authentication required.

Response

{
  "version": "1",
  "feeMicroPercent": 5000,
  "feeRecipient": "0xabc...",
  "minFeeUsd": "0.001",
  "supportedSchemes": ["exact", "prepaid", "stream", "escrow"],
  "supportedNetworks": ["sui:testnet", "sui:mainnet"],
  "validUntil": "2026-03-23T00:00:00.000Z"
}

validUntil is a rolling 30-day window updated at each deploy. The feeRecipient field is null if FACILITATOR_FEE_RECIPIENT is not configured.

Note on signatures: The signature field is reserved for a future release. Once FACILITATOR_KEYPAIR is configured, this payload will be signed over canonical JSON so clients can cryptographically verify the fee schedule before trusting it.

Difference from /.well-known/s402.json: /.well-known/s402.json describes the resource server (who charges). /.well-known/s402-facilitator describes the facilitator (who settles). These are two distinct actors in the s402 protocol — a single deployment may run both, but conceptually they serve different roles.


GET /.well-known/s402.json

Discovery document. Clients and resource servers can query this to learn what schemes and networks this facilitator supports, and what fee rate it charges. No authentication required.

Response

{
  "s402Version": "0.1.0",
  "schemes": ["exact", "prepaid", "stream", "escrow"],
  "networks": ["sui:testnet", "sui:mainnet"],
  "assets": [],
  "directSettlement": true,
  "mandateSupport": false,
  "protocolFeeMicroPercent": 5000
}

GET /supported

Lists supported networks and their available schemes. Authentication required.

Response

{
  "s402Version": "0.1.0",
  "networks": {
    "sui:testnet": ["exact", "prepaid", "stream", "escrow"],
    "sui:mainnet": ["exact", "prepaid", "stream", "escrow"]
  }
}

POST /verify

Verifies a signed payment payload against requirements without broadcasting to Sui. Use this to pre-check a payment before committing.

Request body

{
  "paymentPayload": {
    "scheme": "exact",
    "payload": {
      "transaction": "<base64-encoded Sui PTB bytes>",
      "signature": "<base64-encoded signature>"
    }
  },
  "paymentRequirements": {
    "network": "sui:testnet",
    "amount": "1000000000",
    "payTo": "0xb0b..."
  }
}

Response — shape depends on the scheme implementation, but always includes a success boolean and an optional error field on failure.


POST /settle

Verifies and broadcasts the transaction to Sui. This is the primary settlement endpoint.

Same request body shape as /verify. On success, returns the Sui transaction digest and settlement details. The facilitator records the settlement in its usage tracker keyed by API key.

Error responses

| Status | Reason | |--------|--------| | 400 | Missing or invalid fields in the request body | | 401 | Missing or malformed Authorization header | | 403 | Invalid API key | | 429 | Rate limit exceeded | | 500 | Sui RPC error or scheme-level failure |


POST /s402/process

Atomic verify + settle in a single call. Equivalent to calling /verify then /settle, but the facilitator does both in one round trip. Preferred for production use — fewer network hops, and the verification and broadcast are coupled so a successful verify cannot race with a concurrent settle.

Same request body and response shape as /settle.


Security

API key authentication

All settlement endpoints are protected by bearer token authentication. The middleware uses constant-time comparison (SHA-256 hash both sides, then crypto.timingSafeEqual) to prevent timing side-channel attacks. It also iterates all valid keys without short-circuiting, so key position and set size are not leaked via response time.

Minimum key entropy: Each key in API_KEYS must be at least 16 characters. The server refuses to start with shorter keys — they are trivially brute-forceable. The recommendation is 32+ hex characters from a CSPRNG:

openssl rand -hex 32

Rotate keys by updating API_KEYS and redeploying. The comma-separated format lets you do zero-downtime rotation by temporarily including both the old and new key.

Rate limiting

Token bucket limiter keyed by API key (100 requests max, refills at 10/sec). For authenticated requests the API key is used as the bucket identifier, which is not spoofable. For unauthenticated routes (health, discovery), the limiter falls back to x-forwarded-for / x-real-ip headers — reliable behind a trusted reverse proxy (Cloudflare, nginx, Fly.io's proxy) but trivially spoofable if the service is directly exposed to the internet without a proxy in front.

Package ID anti-spoofing

Set SWEEFI_PACKAGE_ID in production. Without it, scheme handlers that verify on-chain events cannot confirm those events came from the legitimate SweeFi Move package. An attacker could deploy a contract that emits structurally identical events and fool a facilitator that does not check the originating package. With the package ID set, the scheme handlers reject events from any other contract address.

Warning at startup: If SWEEFI_PACKAGE_ID is not set, the facilitator logs a prominent warning on startup explaining that event anti-spoofing is disabled. This is intentional — the variable is optional for local development but should always be set before handling real payments on mainnet.

Body size limit

All requests are capped at 256 KB. Oversized payloads are rejected before any parsing occurs.

Trust boundary

Unknown fields in paymentRequirements are stripped at the route layer (pickRequirementsFields from s402/http) before being passed to scheme handlers. This prevents injection of unexpected fields that could influence downstream Move PTB construction.

Fee enforcement

The facilitator does not independently verify that protocolFeeMicroPercent in the payment requirements matches what the Sui PTB actually collects. This is intentional: each scheme's settle handler builds the PTB with the fee split baked in, and Sui's execution enforces balance conservation. A dishonest resource server cannot reduce fees by lying about protocolFeeMicroPercent in the requirements, because the facilitator — not the resource server — controls PTB construction. Operators deploying custom scheme implementations must ensure their Move contracts enforce fee collection.


Architecture

products/sweefi/facilitator/
├── src/
│   ├── index.ts              # Library entry — re-exports createApp + loadConfig
│   ├── cli.ts                # CLI entry — `start | version | help` subcommands
│   ├── app.ts                # createApp() — middleware stack and route mounting
│   ├── config.ts             # Zod-validated environment config
│   ├── facilitator.ts        # createFacilitator() — registers Sui scheme handlers
│   ├── routes.ts             # HTTP route handlers (verify, settle, s402/process, supported)
│   ├── auth/
│   │   └── api-key.ts        # Constant-time bearer token auth middleware
│   ├── middleware/
│   │   ├── rate-limiter.ts   # Token bucket rate limiter per API key
│   │   └── logger.ts         # Request logging middleware
│   └── metering/
│       ├── usage-tracker.ts  # In-memory settlement tracking (Phase 1)
│       ├── hooks.ts          # recordSettlement() — called after successful settle
│       ├── fee-calculator.ts # Fee math utilities
│       └── metrics.ts        # IMetrics interface + NoopMetrics (seam for Prometheus/OTEL)
├── Dockerfile                # Multi-stage build: deps → build → slim runtime
└── fly.toml                  # Fly.io config (shared-cpu-1x, 512mb, sjc, auto-stop)

Middleware execution order (from app.ts):

  1. bodyLimit — rejects oversized requests before any parsing
  2. requestLogger — structured request/response logging
  3. apiKeyAuth — validates bearer token for protected routes
  4. meteringContext — propagates API key for inline settlement recording
  5. rateLimiter — token bucket, keyed by API key or IP fallback

Scheme registration (facilitator.ts): The s402Facilitator from the s402 package manages a registry of scheme implementations per network. At startup, ExactSuiFacilitatorScheme, PrepaidSuiFacilitatorScheme, StreamSuiFacilitatorScheme, and EscrowSuiFacilitatorScheme are registered for both sui:testnet and sui:mainnet. Each scheme knows how to verify a signed PTB and broadcast it to the appropriate Sui RPC endpoint.

Metering — Phase 1: Successful settlements are recorded in memory keyed by API key (UsageTracker). This is a Phase 1 design — the IUsageTracker interface makes it straightforward to swap in a persistent backend (Redis, Postgres, or an on-chain contract) without touching the route handlers.

Observability seam: The IMetrics interface in metering/metrics.ts defaults to NoopMetrics (zero overhead). Pass a PrometheusMetrics or OtelMetrics implementation to createApp() when you need instrumentation.

Testability: app.ts exports createApp(config) separately from cli.ts so the application can be constructed in tests without starting a real server. Integration tests can pass a custom config and call app.fetch directly. The library entry (index.ts) re-exports createApp and loadConfig for embedders.


Running tests

# Unit tests
pnpm --filter @sweefi/facilitator test

# Integration tests (requires Sui testnet RPC access)
pnpm --filter @sweefi/facilitator test:integration

# Type check only
pnpm --filter @sweefi/facilitator typecheck

License

Apache 2.0 — see LICENSE.

Source: github.com/sweeinc/platform