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

mpp-solana

v0.1.2

Published

Solana SPL token payments for the Machine Payments Protocol

Readme

mpp-solana

Solana SPL token payments for the Machine Payments Protocol (MPP) via HTTP 402.

Enables servers to charge for API access in any SPL token, and clients (browsers, Node.js apps, AI agents) to pay automatically — no manual invoicing, no subscriptions.

Client                           Server
  │                                 │
  │── POST /api/data ───────────────▶│
  │                                 │ 402 Payment Required
  │◀── WWW-Authenticate: MPP ───────│ (challenge + mint + amount)
  │                                 │
  │  [pays on Solana devnet/mainnet] │
  │                                 │
  │── POST /api/data ───────────────▶│ (credential in header)
  │                                 │ 200 OK + Payment-Receipt
  │◀────────────────────────────────│

Installation

npm install mpp-solana mppx @solana/web3.js @solana/spl-token

Quick Start

Server (Node.js / Bun / Edge)

import { Hono } from 'hono'
import { solana, Store, Mppx } from 'mpp-solana/server'
import { PublicKey } from '@solana/web3.js'

const chargeMethod = solana.charge({
  recipient: new PublicKey('YOUR_WALLET_ADDRESS'),
  mint: new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'), // USDC
  network: 'mainnet-beta',
  store: Store.memory(), // use a persistent store in production
})

const mppx = Mppx.create({
  secretKey: process.env.MPP_SECRET_KEY!,
  methods: [chargeMethod],
})

const app = new Hono()

app.all('/api/data', async (c) => {
  const result = await mppx['solana/charge']({ amount: '0.10' })(c.req.raw)

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

  return result.withReceipt(
    new Response(JSON.stringify({ data: 'your protected content' }), {
      headers: { 'Content-Type': 'application/json' },
    }),
  )
})

Client (Browser / Node.js)

import { solana, Mppx } from 'mpp-solana/client'

// Works with any Solana wallet (Phantom, Backpack, Solflare, keypair, agent wallet...)
const chargeClient = solana.charge({
  wallet: window.solana, // or any WalletLike
  network: 'mainnet-beta',
})

const mppxClient = Mppx.create({ methods: [chargeClient] })

// Automatically handles the 402 → pay → retry flow
const response = await mppxClient.fetch('https://yourapi.com/api/data')
const data = await response.json()

Payments

One-time Charge

Charge per request. Each call requires a fresh on-chain transaction.

Server:

import { solana, Store, Mppx } from 'mpp-solana/server'

const method = solana.charge({
  recipient: new PublicKey('...'),
  mint: new PublicKey('...'),  // any SPL token
  decimals: 6,                 // optional — auto-detected if omitted
  network: 'mainnet-beta',
  store: Store.memory(),
  verifyTimeout: 60_000,
  receiptSecret: mySecret,     // optional — HMAC-hashes receipt references for privacy
})

Client:

import { solana, Mppx } from 'mpp-solana/client'

const method = solana.charge({
  wallet: myWallet,
  network: 'mainnet-beta',
  priorityFee: 'dynamic',  // 'fixed' | 'dynamic' | { microLamports: 5000 }
  onPayment: (sig) => console.log('Paid:', sig),  // optional — capture tx signature
})

Sessions (Deposit-based)

Client deposits a lump sum upfront. Server deducts per request from the balance. Refunds unused balance on close. Ideal for AI agents making many calls.

Server:

import { solana, Store, Mppx } from 'mpp-solana/server'
import { Keypair } from '@solana/web3.js'

const method = solana.session({
  recipient: new PublicKey('...'),
  mint: new PublicKey('...'),
  serverKeypair: Keypair.fromSecretKey(bs58.decode(process.env.SERVER_KEYPAIR!)),
  network: 'mainnet-beta',
  store: Store.memory(), // required — sessions need persistent state
})

Client:

import { solana, Mppx } from 'mpp-solana/client'

const method = solana.session({
  wallet: agentWallet,
  network: 'mainnet-beta',
  onPayment: (sig, action) => console.log(`${action}:`, sig),  // optional
})

const mppxClient = Mppx.create({ methods: [method] })

// First request: deposits funds and opens session
const r1 = await mppxClient.fetch('/api/endpoint')
method.setSessionFromResponse(r1) // capture sessionId + bearer

// Subsequent requests: deduct from session balance (no new tx)
const r2 = await mppxClient.fetch('/api/endpoint')
const r3 = await mppxClient.fetch('/api/endpoint')

// Top up when balance runs low
method.topUp()
const r4 = await mppxClient.fetch('/api/endpoint') // sends new deposit tx

// Close session and receive refund
method.close()
await mppxClient.fetch('/api/endpoint')

Multi-token Router

Accept multiple tokens simultaneously. One endpoint, any token the client wants to pay with.

import { solana, Store, Mppx } from 'mpp-solana/server'
import { PaymentRouter } from 'mpp-solana/router'

const store = Store.memory() // shared — prevents cross-token replay

const router = new PaymentRouter({
  methods: [
    solana.charge({ recipient, mint: USDC_MINT, store, network: 'mainnet-beta' }),
    solana.charge({ recipient, mint: USDT_MINT, store, network: 'mainnet-beta' }),
  ],
})

Wallet Compatibility

Any object that implements WalletLike works as a wallet:

interface WalletLike {
  publicKey: PublicKey
  signTransaction<T extends Transaction | VersionedTransaction>(tx: T): Promise<T>
}

This includes:

  • Phantom / Backpack / Solflarewindow.solana directly
  • @solana/wallet-adapter-reactuseWallet() adapter
  • Solana Agent Kit — agent wallets
  • Keypair — wrap with a simple adapter for server-side signing:
const wallet = {
  publicKey: keypair.publicKey,
  async signTransaction<T extends Transaction | VersionedTransaction>(tx: T): Promise<T> {
    (tx as VersionedTransaction).sign([keypair])
    return tx
  },
}

RPC Configuration

// Single endpoint
solana.charge({ connection: new Connection('https://rpc.example.com'), ... })

// Multiple endpoints — automatic failover with exponential backoff
solana.charge({ endpoints: ['https://rpc1.example.com', 'https://rpc2.example.com'], ... })

// Named network (uses public endpoints)
solana.charge({ network: 'devnet', ... })

Replay Protection

Pass a persistent Store to prevent the same transaction being accepted twice:

import { Store } from 'mpp-solana/server'

// In-memory (development)
const store = Store.memory()

// Production: use a persistent store adapter
// e.g. KV stores, Redis, Cloudflare KV, Durable Objects

A console warning is emitted if store is omitted from solana.charge().

Environment Variables

For the server only — the SDK itself reads nothing from the environment:

MPP_SECRET_KEY=<32-byte hex>   # for Mppx.create() — sign/verify challenges

License

HEHEHEHEH