Solana payment method for the Machine Payments Protocol.
MPP is an open protocol proposal that lets any HTTP API accept payments using the 402 Payment Required flow.
Important
This repository is under active development. The Solana MPP spec is not yet finalized — APIs and wire formats are subject to change.
pnpm add @solana/mppPeer dependencies:
pnpm add @solana/kit mppx
# Optional — for Swig session authorization:
pnpm add @swig-wallet/kitCharge (one-time payments)
- Native SOL and SPL token transfers (USDC, PYUSD, Token-2022, etc.)
- Two settlement modes: pull (
type="transaction", default) and push (type="signature") - Fee sponsorship: server pays transaction fees on behalf of clients
- Replay protection via consumed transaction signatures
Session (metered / streaming payments)
- Voucher-based payment channels with monotonic cumulative amounts
- Multiple authorization modes:
unbounded,regular_budget,swig_session - Auto-open, auto-topup, and close lifecycle
- Swig smart wallet integration for on-chain spend limits
General
- Works with ConnectorKit,
@solana/kitkeypair signers, and Solana Keychain remote signers - Server pre-fetches
recentBlockhashto save client an RPC round-trip - Transaction simulation before broadcast to prevent wasted fees
- Optional
tokenProgramhint; clients resolve the mint owner and fail closed if discovery fails
mpp-sdk/
├── typescript/ # TypeScript SDK
│ └── packages/mpp/src/
│ ├── Methods.ts # Shared charge + session schemas
│ ├── constants.ts # Token programs, USDC mints, RPC URLs
│ ├── server/
│ │ ├── Charge.ts # Server: challenge, verify, broadcast
│ │ └── Session.ts # Server: session channel management
│ ├── client/
│ │ ├── Charge.ts # Client: build tx, sign, send
│ │ └── Session.ts # Client: session lifecycle
│ └── session/
│ ├── Types.ts # Session types and interfaces
│ ├── Voucher.ts # Voucher signing and verification
│ ├── ChannelStore.ts # Persistent channel state
│ └── authorizers/ # Pluggable authorization strategies
├── rust/ # Rust SDK (coming soon)
│ └── src/lib.rs
└── demo/ # Interactive playground
Exports:
@solana/mpp— shared schemas, session types, and authorizers@solana/mpp/server— server-side charge + session,Mppx,Store@solana/mpp/client— client-side charge + session,Mppx
Server:
import { Mppx, solana } from '@solana/mpp/server'
const mppx = Mppx.create({
secretKey: process.env.MPP_SECRET_KEY,
methods: [
solana.charge({
recipient: 'RecipientPubkey...',
currency: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
decimals: 6,
}),
],
})
const result = await mppx.charge({
amount: '1000000', // 1 USDC
currency: 'USDC',
})(request)
if (result.status === 402) return result.challenge
return result.withReceipt(Response.json({ data: '...' }))Client:
import { Mppx, solana } from '@solana/mpp/client'
const mppx = Mppx.create({
methods: [solana.charge({ signer })], // any TransactionSigner
})
const response = await mppx.fetch('https://api.example.com/paid-endpoint')Server:
import { Mppx, solana } from '@solana/mpp/server'
const mppx = Mppx.create({
secretKey: process.env.MPP_SECRET_KEY,
methods: [
solana.session({
recipient: 'RecipientPubkey...',
asset: { kind: 'sol', decimals: 9 },
channelProgram: 'ChannelProgramId...',
pricing: { unit: 'request', amountPerUnit: '10', meter: 'api_calls' },
sessionDefaults: { suggestedDeposit: '1000', ttlSeconds: 60 },
}),
],
})Client:
import { Mppx, solana } from '@solana/mpp/client'
import { UnboundedAuthorizer } from '@solana/mpp'
const mppx = Mppx.create({
methods: [
solana.session({
signer,
authorizer: new UnboundedAuthorizer({ signer, buildOpenTx, buildTopupTx }),
}),
],
})
const response = await mppx.fetch('https://api.example.com/metered-endpoint')The server can pay transaction fees on behalf of clients:
// Server — pass a TransactionPartialSigner to cover fees
solana.charge({
recipient: '...',
signer: feePayerSigner, // KeyPairSigner, Keychain SolanaSigner, etc.
})
// Client — no changes needed, fee payer is handled automatically- Client requests a resource
- Server returns 402 Payment Required with a challenge (
recipient,amount,currency, optionaltokenProgram, optionalrecentBlockhash) - Client builds and signs a Solana transfer transaction
- Server simulates, broadcasts, confirms on-chain, and verifies the transfer
- Server returns the resource with a
Payment-Receiptheader
With fee sponsorship, the client partially signs (transfer authority only) and the server co-signs as fee payer before broadcasting.
- First request: server returns 402, client opens a channel (deposit + voucher)
- Subsequent requests: client sends updated vouchers with monotonic cumulative amounts
- Server deducts from the channel balance per its pricing config
- When balance runs low: client tops up the channel
- On close: final voucher settles the channel
An interactive playground with a React frontend and Express backend, running against Surfpool.
- Charge flow demo:
http://localhost:5173/charges - Session flow demo:
http://localhost:5173/sessions
surfpool start
pnpm demo:install
pnpm demo:server
pnpm demo:appSee demo/README.md for full details.
# TypeScript
cd typescript && pnpm install
just ts-fmt # Format and lint
just ts-build # Build
just ts-test # Unit tests (charge + session, no network)
just ts-test-integration # Integration tests (requires Surfpool)
# Rust client SDK
cd rust && cargo build
# Anchor program (programs/mpp-channel)
# Prerequisites: Rust stable toolchain, Anchor CLI >=1.0.0-rc.2, solana-test-validator on PATH
just anchor-build # Compile the on-chain program
just anchor-test # Localnet integration tests (starts/stops validator automatically)
# Everything
just build # Build TypeScript, Rust, and Anchor
just test # Unit tests (TypeScript + Rust, no network)
just test-all # All tests including integration and Anchor localnet
just pre-commit # Full pre-commit checksThis SDK implements the Solana Charge Intent for the HTTP Payment Authentication Scheme.
Session method docs and implementation notes:
MIT
