Skip to content

Latest commit

 

History

History
243 lines (182 loc) · 7.48 KB

File metadata and controls

243 lines (182 loc) · 7.48 KB

MPP

@solana/mpp

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.

Install

pnpm add @solana/mpp

Peer dependencies:

pnpm add @solana/kit mppx
# Optional — for Swig session authorization:
pnpm add @swig-wallet/kit

Features

Charge (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/kit keypair signers, and Solana Keychain remote signers
  • Server pre-fetches recentBlockhash to save client an RPC round-trip
  • Transaction simulation before broadcast to prevent wasted fees
  • Optional tokenProgram hint; clients resolve the mint owner and fail closed if discovery fails

Architecture

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

Quick Start

Charge (one-time payment)

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')

Session (metered payments)

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')

Fee Sponsorship (charge)

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

How It Works

Charge Flow

  1. Client requests a resource
  2. Server returns 402 Payment Required with a challenge (recipient, amount, currency, optional tokenProgram, optional recentBlockhash)
  3. Client builds and signs a Solana transfer transaction
  4. Server simulates, broadcasts, confirms on-chain, and verifies the transfer
  5. Server returns the resource with a Payment-Receipt header

With fee sponsorship, the client partially signs (transfer authority only) and the server co-signs as fee payer before broadcasting.

Session Flow

  1. First request: server returns 402, client opens a channel (deposit + voucher)
  2. Subsequent requests: client sends updated vouchers with monotonic cumulative amounts
  3. Server deducts from the channel balance per its pricing config
  4. When balance runs low: client tops up the channel
  5. On close: final voucher settles the channel

Demo

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:app

See demo/README.md for full details.

Development

# 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 checks

Spec

This SDK implements the Solana Charge Intent for the HTTP Payment Authentication Scheme.

Session method docs and implementation notes:

License

MIT