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

mppx-solana

v0.2.0

Published

Solana payment method for the Machine Payments Protocol (mppx)

Downloads

218

Readme

mppx-solana

Solana payment method for the Machine Payments Protocol. Accept SOL and SPL token payments on any HTTP endpoint.

Built on top of mppx — the official MPP SDK.

Install

bun add mppx-solana mppx @solana/web3.js @solana/spl-token viem
npm install mppx-solana mppx @solana/web3.js @solana/spl-token viem
pnpm add mppx-solana mppx @solana/web3.js @solana/spl-token viem

How it works

Client                          Server
  │                               │
  │  GET /api/resource            │
  │ ────────────────────────────► │
  │                               │
  │  402 + Challenge              │
  │  (amount, recipient, mint)    │
  │ ◄──────────────────────────── │
  │                               │
  │  Signs & sends Solana tx      │
  │  Retries with tx signature    │
  │ ────────────────────────────► │
  │                               │
  │  Verifies tx on-chain         │
  │  200 + Resource + Receipt     │
  │ ◄──────────────────────────── │
  1. Client hits a paid endpoint → server returns 402 with a Solana payment challenge
  2. Client builds, signs, and submits a Solana transaction
  3. Client retries the request with the transaction signature as proof
  4. Server verifies the transaction on-chain and returns the resource with a receipt

Server

Gate any endpoint behind a Solana payment. Works with Bun.serve, Hono, Express, and Next.js.

Bun

import { Mppx } from "mppx/server";
import { server as solanaServer, NATIVE_SOL_CURRENCY } from "mppx-solana";

const mppx = Mppx.create({
  methods: [
    solanaServer({
      recipient: "YourWalletPublicKeyBase58",
      currency: NATIVE_SOL_CURRENCY,
      cluster: "mainnet-beta",
      decimals: 9,
    }),
  ],
  secretKey: process.env.MPP_SECRET_KEY!,
});

Bun.serve({
  async fetch(request) {
    const result = await mppx.charge({
      amount: "1000000", // 0.001 SOL in lamports
      description: "API access",
    })(request);

    if (result.status === 402) return result.challenge;

    return result.withReceipt(
      Response.json({ data: "your paid content here" }),
    );
  },
});

Hono

import { Hono } from "hono";
import { Mppx } from "mppx/hono";
import { server as solanaServer, NATIVE_SOL_CURRENCY } from "mppx-solana";

const app = new Hono();

const mppx = Mppx.create({
  methods: [
    solanaServer({
      recipient: "YourWalletPublicKeyBase58",
      currency: NATIVE_SOL_CURRENCY,
      cluster: "mainnet-beta",
      decimals: 9,
    }),
  ],
  secretKey: process.env.MPP_SECRET_KEY!,
});

app.get(
  "/api/resource",
  mppx.charge({ amount: "1000000", description: "API access" }),
  async (c) => c.json({ data: "your paid content here" }),
);

Express

import express from "express";
import { Mppx } from "mppx/express";
import { server as solanaServer, NATIVE_SOL_CURRENCY } from "mppx-solana";

const app = express();

const mppx = Mppx.create({
  methods: [
    solanaServer({
      recipient: "YourWalletPublicKeyBase58",
      currency: NATIVE_SOL_CURRENCY,
      cluster: "mainnet-beta",
      decimals: 9,
    }),
  ],
  secretKey: process.env.MPP_SECRET_KEY!,
});

app.get(
  "/api/resource",
  mppx.charge({ amount: "1000000", description: "API access" }),
  async (req, res) => res.json({ data: "your paid content here" }),
);

Next.js

// app/api/resource/route.ts
import { Mppx } from "mppx/nextjs";
import { server as solanaServer, NATIVE_SOL_CURRENCY } from "mppx-solana";

const mppx = Mppx.create({
  methods: [
    solanaServer({
      recipient: "YourWalletPublicKeyBase58",
      currency: NATIVE_SOL_CURRENCY,
      cluster: "mainnet-beta",
      decimals: 9,
    }),
  ],
  secretKey: process.env.MPP_SECRET_KEY!,
});

export const GET = mppx.charge({
  amount: "1000000",
  description: "API access",
})(async () => Response.json({ data: "your paid content here" }));

Client

Pay for any MPP-gated endpoint automatically. The SDK handles the 402 → pay → retry flow.

import { Connection, Keypair } from "@solana/web3.js";
import { Mppx } from "mppx/client";
import { client as solanaClient } from "mppx-solana";

const mppx = Mppx.create({
  methods: [
    solanaClient({
      connection: new Connection("https://api.mainnet-beta.solana.com"),
      signer: Keypair.fromSecretKey(/* your key */),
    }),
  ],
  polyfill: false,
});

// Automatically pays when the server returns 402
const response = await mppx.fetch("https://api.example.com/resource");
const data = await response.json();

Custom signer

Any object with publicKey and signTransaction works — use this to integrate wallet adapters.

solanaClient({
  connection,
  signer: {
    publicKey: wallet.publicKey,
    signTransaction: (tx) => wallet.signTransaction(tx),
  },
});

Dynamic connection

Use getConnection when you need per-request RPC routing:

solanaClient({
  signer: myKeypair,
  getConnection: (cluster) => {
    if (cluster === "devnet") return new Connection("https://api.devnet.solana.com");
    return new Connection("https://my-rpc.example.com");
  },
});

Payment options

Native SOL

solanaServer({
  currency: NATIVE_SOL_CURRENCY, // "solana:native"
  decimals: 9,
  recipient: "YourWalletPublicKeyBase58",
  cluster: "mainnet-beta",
})

// amount is in lamports: "1000000" = 0.001 SOL

SPL tokens (USDC, USDT, etc.)

solanaServer({
  currency: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC mint
  decimals: 6,
  recipient: "YourWalletPublicKeyBase58",
  cluster: "mainnet-beta",
})

// amount is in smallest unit: "10000" = 0.01 USDC

The client automatically creates the recipient's associated token account if it doesn't exist.

Memo verification

Attach a memo to bind payments to specific invoices or orders:

mppx.charge({
  amount: "1000000",
  memo: "invoice-abc-123",
})

The server will reject transactions that don't contain the expected memo.

Sponsored fees (gasless for users)

Enable gasless transactions so users only need USDC (or any SPL token) — no SOL required. The server sponsors the Solana transaction fee and the user reimburses the server in the payment token.

Client                          Server
  │                               │
  │  GET /api/resource            │
  │ ────────────────────────────► │
  │                               │
  │  402 + Challenge              │
  │  (sponsored=true, sponsorPath)│
  │ ◄──────────────────────────── │
  │                               │
  │  POST /sponsor                │
  │  { publicKey, request }       │
  │ ────────────────────────────► │
  │                               │
  │  Partially signed tx          │
  │  (feePayer=server)            │
  │ ◄──────────────────────────── │
  │                               │
  │  Co-signs & sends tx          │
  │  Retries with tx signature    │
  │ ────────────────────────────► │
  │                               │
  │  Verifies tx on-chain         │
  │  200 + Resource + Receipt     │
  │ ◄──────────────────────────── │

How it works

  1. Server returns a 402 challenge with sponsored: true and a sponsorPath
  2. Client POSTs its public key to the sponsor endpoint
  3. Server builds the transaction with itself as feePayer, includes the payment transfer + a small fee reimbursement transfer from user → server
  4. Server partially signs as fee payer and returns the serialized transaction
  5. Client co-signs as the payment authority and submits
  6. Server verifies the payment on-chain as usual

The user pays the API cost + a small fee surcharge (e.g., 0.01 USDC) in the same token. The server pays the Solana network fee in SOL and receives the token reimbursement.

Server setup

import { Keypair } from "@solana/web3.js";
import { Mppx } from "mppx/server";
import {
  createSponsorHandler,
  server as solanaServer,
} from "mppx-solana";

const sponsorKeypair = Keypair.fromSecretKey(/* server fee payer key */);

const mppx = Mppx.create({
  methods: [
    solanaServer({
      recipient: "MerchantWalletPublicKeyBase58",
      currency: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC
      decimals: 6,
      cluster: "mainnet-beta",
      sponsor: {
        feePayer: sponsorKeypair,
        feeTokenAmount: "10000", // 0.01 USDC reimbursement
        sponsorPath: "/sponsor",
      },
    }),
  ],
  realm: "api.example.com",
  secretKey: process.env.MPP_SECRET_KEY!,
});

const handleSponsor = createSponsorHandler({
  feePayer: sponsorKeypair,
  feeTokenAmount: "10000",
  connection: new Connection("https://api.mainnet-beta.solana.com"),
});

Bun.serve({
  routes: {
    "/api/resource": {
      GET: async (request) => {
        const result = await mppx.charge({
          amount: "100000", // 0.10 USDC
        })(request);

        if (result.status === 402) return result.challenge;
        return result.withReceipt(Response.json({ data: "paid content" }));
      },
    },
    "/sponsor": {
      POST: handleSponsor,
    },
  },
});

Client

No changes needed — the client automatically detects sponsored: true in the challenge and uses the sponsor endpoint.

import { Connection, Keypair } from "@solana/web3.js";
import { Mppx } from "mppx/client";
import { client as solanaClient } from "mppx-solana";

const mppx = Mppx.create({
  methods: [
    solanaClient({
      connection: new Connection("https://api.mainnet-beta.solana.com"),
      signer: Keypair.fromSecretKey(/* user key */),
    }),
  ],
  polyfill: false,
});

// User pays 0.11 USDC (0.10 payment + 0.01 fee) — zero SOL needed
const response = await mppx.fetch("https://api.example.com/api/resource");

Cost breakdown

For a 0.10 USDC API call with sponsored fees:

| | Without sponsorship | With sponsorship | |---|---|---| | User pays (USDC) | 0.10 | 0.11 (0.10 + 0.01 fee) | | User pays (SOL) | ~0.002 SOL (tx fee + ATA rent) | 0 | | Server pays (SOL) | 0 | ~0.004 SOL (tx fee + ATA rent) | | Server receives (USDC) | 0 | 0.01 (fee reimbursement) |

The server accumulates USDC reimbursements and needs to periodically swap USDC → SOL to stay funded. Initial SOL seed required (~0.1 SOL ≈ 20,000+ transactions).

Sponsor configuration

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | sponsor.feePayer | Keypair | Yes | Server keypair that pays Solana tx fees | | sponsor.feeTokenAmount | string | Yes | Amount in token smallest unit to reimburse (e.g., "10000" = 0.01 USDC) | | sponsor.sponsorPath | string | Yes | HTTP path for the sponsor endpoint (e.g., "/sponsor") |

Server configuration

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | recipient | string | Yes | Wallet address receiving payments | | currency | string | Yes | "solana:native" for SOL, or SPL mint address | | decimals | number | Yes | Token decimals (9 for SOL, 6 for USDC) | | cluster | string | No | "mainnet-beta", "devnet", "testnet", "localnet" | | commitment | string | No | "confirmed" (default) or "finalized" | | connection | Connection | No | Custom RPC connection | | getConnection | function | No | Dynamic connection factory | | memo | string | No | Required memo string on transactions | | description | string | No | Human-readable description | | externalId | string | No | External reference ID for receipts | | sponsor | SolanaSponsorConfig | No | Enable sponsored/gasless transactions |

Client configuration

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | signer | Keypair \| SolanaSigner | Yes | Signs transactions | | connection | Connection | No | RPC connection | | getConnection | function | No | Dynamic connection factory |

Running the examples

Full end-to-end examples using a local Solana validator are included. This project uses Solforge as the local validator.

# Install solforge
bun add -g solforge

# Start the local validator (picks up sf.config.json automatically)
solforge

# In another terminal, run the e2e tests
bun run example:e2e                  # SOL payment
bun run example:usdc-e2e             # USDC payment
bun run example:sponsored-e2e        # Sponsored SOL payment
bun run example:sponsored-usdc-e2e   # Sponsored USDC payment (gasless)

Or run server and client separately:

# Terminal 1 — server
MPP_SECRET_KEY=dev-secret \
EXAMPLE_RECIPIENT=<wallet> \
EXAMPLE_CLUSTER=localnet \
bun run example:server

# Terminal 2 — client
EXAMPLE_PAYER_SECRET_KEY='[1,2,3,...]' \
EXAMPLE_CLUSTER=localnet \
bun run example:client

Type checking

bun run check

License

MIT