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

@spacecathy/rampkit

v0.6.0

Published

Etherfuse ramp kit for Stellar — MXN (SPEI) and BRL (PIX) onramp/offramp with a wallet-agnostic signer. Community package, not official.

Downloads

1,874

Readme

Rampkit — Etherfuse ramp kit for Stellar (community package, not official)

Fiat on/off-ramps on Stellar — MXN via SPEI and BRL via PIX ⇄ stablebonds like CETES — via the Etherfuse ramp API, as an installable package instead of files you copy: a server route handler, a browser client, React hooks with full flow state machines, and a wallet-agnostic signer contract.

What you get

  • Onramp (fiat → tokens): quote → order → deposit instructions (SPEI CLABE or PIX code/key — a typed spei | pix union) → delivery, including the claimable-balance flow for first-time wallets (sign one claim transaction to add the trustline and receive tokens).
  • Offramp (tokens → fiat): quote → wallet preflight → order → sign the prebuilt burn transaction → payout tracking to finalized.
  • Expiry handled for you: Etherfuse XDRs live ~1–2 minutes; the hooks proactively regenerate the burn transaction (~50s) while the user has it open, and recover from tx_too_late on submit.
  • Resume, never recreate: order IDs are client-generated UUIDs; a 409 is resolved structurally (the order exists → fetch it), and any flow can resume(orderId) after a refresh or crash.
  • Zero runtime dependencies. No wallet SDK, no @stellar/stellar-sdk.

Requirements

  • Node ≥ 18.17 (server side).
  • An Etherfuse account with an API key (sandbox is self-serve and free).
  • Each end user ramps as their own approved Etherfuse customer (customerId) with a registered bank account — and Rampkit ships that whole journey in-app: customer creation at signup (createCustomer / the customer.create operation) and hosted KYC (useKyc().launch). See Onboarding users inside your app.

Try it in 2 minutes (sandbox bootstrap)

You don't need to touch the Etherfuse dashboard to get a testable identity. With a sandbox API key in hand (sandbox.etherfuse.com: create account → Approve KYB → API key):

export ETHERFUSE_API_KEY="api_sand:..."      # PowerShell: $env:ETHERFUSE_API_KEY="..."
npx @spacecathy/rampkit setup-sandbox [email protected]

The CLI creates a customer, generates and funds a Stellar testnet wallet, and hands you ONE hosted link where you click through KYC + bank registration (sandbox auto-approves). It then prints the customerId / publicKey / wallet secret your app's getSession needs — or writes them with --write .env.local.

The bootstrap rides an Etherfuse endpoint deprecated with sunset 2026-08-16; after that date use the dashboard/JWT onboarding instead. The runtime library never touches that endpoint.

Quickstart

npm install @spacecathy/rampkit

1 · Mount the server handler (the only place the API key exists):

// app/api/ramp/[...op]/route.ts  (Next.js App Router — any Request/Response
// framework works; Express via toNodeHandler)
import { EtherfuseClient, createRampHandler } from '@spacecathy/rampkit/server';

const client = new EtherfuseClient({
    apiKey: process.env.ETHERFUSE_API_KEY!, // api_sand:… or api_prod:…
    // environment, baseUrl, horizonUrl are inferred from the key prefix
});

const handler = createRampHandler({
    client,
    // REQUIRED: map YOUR authenticated user to their Etherfuse identity.
    // Identity always comes from here — never from the browser request.
    getSession: async (request) => {
        const user = await yourAuth(request);
        if (!user) return null;
        return { customerId: user.etherfuseCustomerId, publicKey: user.stellarAddress };
    },
});

export const GET = handler;
export const POST = handler;

2 · Wire the provider with your wallet (any wallet — you own the signer):

'use client';
import { createRampClient } from '@spacecathy/rampkit/client';
import { RampProvider, type RampSigner } from '@spacecathy/rampkit/react';

const client = createRampClient(); // talks to /api/ramp, carries no secrets

// Example: Freighter. Rampkit never imports a wallet SDK.
const signer: RampSigner = {
    address: userAddress,
    async signTransaction({ xdr, networkPassphrase }) {
        const { signedTxXdr } = await freighterApi.signTransaction(xdr, { networkPassphrase });
        return { signedXdr: signedTxXdr };
        // A wallet that signs AND submits returns { hash } instead — both
        // branches are first-class.
    },
};

export function App({ children }) {
    return <RampProvider client={client} signer={signer}>{children}</RampProvider>;
}

3 · Use the flow hooks:

import { useOfframp, useOnramp, useRampAssets } from '@spacecathy/rampkit/react';

const { assets } = useRampAssets({ currency: 'MXN' }); // or 'BRL' — via GET /ramp/assets

const { phase, quote, burnXdr, preflightIssues, txHash, actions } = useOfframp();
// actions.requestQuote({ asset: 'CETES', fiat: 'MXN', amount: '5' });
// actions.start({ bankAccountId });   ← preflight runs first, order is
//                                       created as late as possible
// actions.sign();                     ← signer → Horizon submit
// actions.resume(orderId);            ← re-enter after refresh/crash

const onramp = useOnramp();
// requestQuote → start → deposit (SPEI CLABE or PIX code, by rail) →
// [simulateDeposit in sandbox] → signClaim (first-time wallets) → completed

phase walks an explicit state machine (quote_ready, awaiting_transaction, transaction_ready, regenerating, preflight_failed, …) so your UI can render every step, including expiry and regeneration, without guessing.

Onboarding users inside your app (v0.3)

The full lifecycle — user signs up → app creates their customer → user completes KYC in-app → user ramps — without dashboards or CLIs:

// 1 · At signup, in YOUR backend (server-to-server; not a browser op):
const { customerId } = await client.createCustomer({
    email: user.email,
    publicKey: user.stellarAddress, // binds their wallet (idempotent)
});
await db.users.update(user.id, { etherfuseCustomerId: customerId });

Or, if the route handler is your only backend surface, expose signup as the customer.create operation (v0.5) — no custom route needed. Sessions without an Etherfuse identity yet may call ONLY this operation; identity (email, wallet) still comes from your getSession, and the browser can never choose the customerId:

const handler = createRampHandler({
    client,
    getSession: async (request) => {
        const user = await yourAuth(request);
        if (!user) return null;
        return {
            customerId: user.etherfuseCustomerId ?? undefined, // absent pre-signup
            publicKey: user.stellarAddress ?? undefined,
            email: user.email,
            name: user.name,
        };
    },
    // Persist the id — the browser response must never be the only copy.
    onCustomerCreated: async ({ request, customer }) => {
        const user = await yourAuth(request);
        await db.users.update(user!.id, { etherfuseCustomerId: customer.customerId });
    },
});
// Browser side: await client.createCustomer();
// 2 · In the app: gate on KYC and launch the hosted flow when needed
import { useKyc } from '@spacecathy/rampkit/react';

const { isApproved, kyc, launch } = useKyc();
// !isApproved → <button onClick={() => launch()}>Verify identity</button>
// The user completes document + liveness + agreements at Etherfuse and
// returns; sandbox auto-approves.

Migrating from the starter-pack files / zero-setup alternative: the legacy presigned flow is kept as createHostedOnboarding() — no issuer registration needed, with the documented "see org" 409 recovery built in — but it is @deprecated: Etherfuse sunsets that endpoint on 2026-08-16. There is no fully-programmatic KYC approval — not even in sandbox: Etherfuse removed the old /ramp/agreements/* endpoints (410) and email confirmation, the liveness selfie, and the customer agreement have no API; the customer completes them once inside /idv (sandbox auto-approves with fake data). Use submitVerificationData() (the documented KYC API) to push identity data first so that click-through only asks for what's left. Wallet-scoped status (getWalletKycStatus()) is also available, with its own enum (proposed, approved_chain_deploying, rejected).

One-time platform setup (per environment, required for launch): Etherfuse verifies a JWT your server signs, against a JWKS you host.

  1. Generate the keypair — npx @spacecathy/rampkit keygen writes the private key to a file and prints the JWKS + this checklist filled in (or do it yourself: openssl genrsa -out private.pem 2048).
  2. Serve the JWKS — mount createJwksHandler({ privateKey, keyId }) at a stable HTTPS URL (e.g. /.well-known/jwks.json).
  3. Send your issuer and JWKS URL to your Etherfuse representative — registration is not self-serve on their side yet.
  4. Configure the client: new EtherfuseClient({ apiKey, onboarding: { issuer, privateKey, keyId } })

Until step 3 is done (or for quick sandbox testing), the npx @spacecathy/rampkit setup-sandbox bootstrap remains the shortcut.

Where do customerIds live? (not in env vars!)

One customerId per end user of your platform — it's their verified Etherfuse identity (fiat is regulated; each person KYCs once, exactly like a stripe_customer_id). It lives in your database, next to your user record, and flows per request through getSession:

user signs up      →  your backend creates their customer (one API call,
                      you generate the UUID) and stores it: users.etherfuse_customer_id
first ramp usage   →  the user completes Etherfuse's hosted KYC once
ever after         →  getSession(req) returns { customerId, publicKey }
                      from YOUR session/DB — fully automatic

The DEMO_CUSTOMER_ID env var you'll see in the demo app exists only because the demo has no auth or database — it fakes a one-user platform. Real integrations never configure customer ids by hand.

Why the offramp preflight matters

If the selling wallet doesn't exist on-chain, lacks XLM for reserves, lacks the asset's trustline, or holds less than the quoted amount, Etherfuse silently never produces a burn transaction — no error, no webhook; the order hangs in created forever. Rampkit checks all four against Horizon before creating the order and reports machine-readable issues (preflightIssues) your UI can turn into "send XLM first" instead of an infinite spinner.

Security model

  • The API key lives only in rampkit/server, which is Node-conditioned: importing it from browser code fails at build time.
  • The browser calls your route with named operations from a frozen map — there is no path from a browser string to an Etherfuse URL.
  • getSession is required; customerId/publicKey always come from it and order reads verify ownership. For local prototyping only, there is a deliberately loud escape hatch: unsafeTrustClient.

Etherfuse behaviors baked in

  • Quotes expire in 2 minutes → hooks track expiry; create orders late.
  • Error bodies are strings (no machine codes) → errors are switched on HTTP status, never parsed from messages.
  • Asset issuers differ between sandbox and production → always resolved at runtime via GET /ramp/assets, never hardcoded.
  • Unfunded orders auto-cancel after 24h server-side → recovery is resume-only; there is no cancel operation.
  • Sandbox: MXN onramps are capped at 500 MXN; deposits are funded via sandbox.simulateDeposit (hidden in production).

Scope

Etherfuse only, Stellar only, default burn flow, MXN/SPEI and BRL/PIX rails. In since v0.3: in-app onboarding (customer creation + hosted-KYC launch; since v0.5 also exposed as the customer.create handler operation). Out of scope: anchor mode, embedded wallets, swaps, webhooks (polling covers freshness; a WebSocket source is planned behind the same TransactionSource interface).

License & attribution

Apache-2.0. Derived from the Etherfuse integration in the Stellar Regional Starter Pack by Elliot Friend — see NOTICE and CHANGES-FROM-UPSTREAM.md for what changed.

Not affiliated with, or endorsed by, Etherfuse or the Stellar Development Foundation.