Deprecated 2026-05-09. Use
@solana/mpp— the official Solana Foundation implementation — instead. Package remains on npm for existing consumers; no new development. Seemppsol/sdkfor the deprecation context and migration notes.
HTTP middleware for MPP.sol — emits MPP
402 Payment Required challenges and verifies Solana payments
(solana-direct and solana-session).
Ships with a Hono adapter; the underlying primitives are framework-agnostic so adding Express, Fastify, or Cloudflare-Workers adapters is straightforward.
npm install @mppsol/server honoimport { Hono } from 'hono';
import { mppMiddleware, InMemoryNonceStore } from '@mppsol/server';
const app = new Hono();
const nonces = new InMemoryNonceStore();
app.use(
'/v1/joke',
mppMiddleware({
config: {
realm: 'api.example.com',
cluster: 'mainnet-beta',
recipient: '9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin',
mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
amount: '1000', // 0.001 USDC
rpcUrl: 'https://api.mainnet-beta.solana.com',
schemes: ['solana-direct'],
minConfirmations: 'confirmed',
deadlineSecs: 300,
nonces,
},
}),
);
app.get('/v1/joke', (c) => c.text('Why don\'t scientists trust atoms? Because they make up everything.'));
export default app;A first request to GET /v1/joke returns 402 Payment Required with a
Solana payment challenge. The client signs and submits a USDC transfer
to the recipient with a Memo program instruction binding the nonce,
then retries with the Authorization: Payment header. The middleware
verifies the on-chain payment, sets a Payment-Receipt header, and
hands off to your handler.
For solana-direct:
- The signature decodes to 64 bytes, the nonce echoes the challenge.
- The transaction is fetched at the configured commitment level.
- The transaction did not error and was included before the deadline.
- The recipient token account was credited at least the requested
amount of the configured mint (computed as
post − preso Token-2022 transfer fees are handled correctly). - A nonce-binding instruction is present — either a Memo program
instruction whose data equals base64url(nonce), or a
mppsol/pay nonce=<b64url>log line emitted by themppsol_cpiprogram. - The nonce is single-use.
For solana-session:
- The off-chain debit message decodes to the canonical 104-byte layout with the correct domain separator.
- The nonce echoes the challenge.
- The Ed25519 signature verifies against the session's
authorized_signer. - The session is
Active, not expired, with a matching cluster. - The debit's
sequence > session.lastSeenSequence(replay protection). - The debit's
amount ≤ session.remainingCap.
On success the server records the settle in the SessionStore. On-chain
batched Settle submission is the operator's responsibility — see
spec/settlement.md §5.
InMemoryNonceStore is included for dev/single-process use. Production
deployments need a durable, replicated store — implement NonceStore
against Postgres / Redis / DynamoDB. Same pattern for SessionStore.
The nonce store MUST persist consumed-nonce state for at least
deadlineSecs to prevent replay across restarts (per
spec/security.md §2.1).
createRpcClient(rpcUrl) ships a minimal fetch-based RPC client that
calls getTransaction, getSlot, getGenesisHash. No @solana/web3.js
or @solana/kit dependency. If you already have a Solana RPC client,
implement the RpcClient interface and pass it via mppMiddleware({ config, rpc }).
- On-chain Settle batch submission for sessions. Implement using your preferred Solana SDK; this is operational, not protocol.
- Production storage adapters. Bring your own database.
- Confirmation tracking (monitoring
confirmed → finalizedfor released payments). Spec recommends it; not enforced here. - Token-2022 extension validation (confidential transfers, freeze authorities). The mint allowlist is currently single-mint via config.
@mppsol/server— full surface, framework-agnostic primitives + Hono.@mppsol/server/hono— Hono middleware re-export only.
See examples/ for a runnable Hono server protected
by MPP.sol direct mode on devnet.
v0.1 draft. Direct mode shippable today on Solana mainnet. Session mode blocked on on-chain program deployment.
| Mode | Off-chain (this package) | On-chain (@mppsol/cpi) |
End-to-end usable |
|---|---|---|---|
solana-direct (one-shot HTTP 402 payment) |
✅ full impl + 28 tests | n/a (not needed) | ✅ YES |
solana-session (streamed off-chain debits) |
✅ full impl + tests | ❌ not deployed | ❌ NO |
Direct mode is real-world usable today: a client builds a USDC transfer + Memo nonce binding, this middleware verifies via raw RPC, your route handler runs. No on-chain program required.
Session mode off-chain code (debit verification, sequence/cap
checks, Ed25519 validation) is fully tested against a mocked
SessionStore, but no Session PDAs exist on-chain yet because
@mppsol/cpi — the on-chain program —
is blocked on a Solana toolchain issue (platform-tools v1.49 not yet
released).
Breaking changes possible before v1.0.
Apache-2.0. Maintained by psyto.