Managed Solana settlement and streaming sessions for the Machine Payments Protocol
dexter-mpp is an early-stage TypeScript project in the AI payments / x402 ecosystem. It currently has 1 GitHub stars and 0 forks.
Managed Solana settlement for the Machine Payments Protocol. Accept MPP payments without running blockchain infrastructure.
An MPP payment method that lets any seller accept Solana USDC payments through the Machine Payments Protocol — with Dexter handling all settlement infrastructure.
Two payment modes:
Sellers install this package and get:
mppx client. Supports HTTP and MCP transports.The method name is dexter. Clients that support custom MPP methods will discover and use it automatically.
npm install @dexterai/mpp
Peer dependencies: mppx, @solana/kit, @solana-program/token, @solana-program/compute-budget
Add Solana payments to any endpoint in a few lines:
import { Mppx } from 'mppx/server';
import { charge } from '@dexterai/mpp/server';
const mppx = Mppx.create({
methods: [
charge({
recipient: 'YourSolanaWalletAddress...',
}),
],
});
export async function handler(request: Request) {
const result = await mppx.charge({
amount: '10000', // 0.01 USDC (6 decimals)
currency: 'USDC',
})(request);
if (result.status === 402) return result.challenge;
return result.withReceipt(Response.json({ data: 'premium content' }));
}
That's it. No RPC URL, no private keys, no SOL balance. Dexter's settlement API handles everything.
import { Mppx } from 'mppx/client';
import { charge } from '@dexterai/mpp/client';
import { createKeyPairSignerFromBytes } from '@solana/kit';
const signer = await createKeyPairSignerFromBytes(yourKeypairBytes);
Mppx.create({
methods: [charge({ signer })],
});
// 402 responses are handled automatically.
const response = await fetch('https://api.example.com/paid-endpoint');
The client reads the fee payer and blockhash from the challenge — no RPC access needed.
Client Seller Server Dexter Settlement API Solana
│ │ │ │
│ GET /resource │ │ │
│──────────────────────────>│ POST /mpp/prepare │ │
│ │────────────────────────────>│ │
│ │ { feePayer, blockhash } │ │
│ │<────────────────────────────│ │
│ 402 + Challenge │ │ │
│<──────────────────────────│ │ │
│ │ │ │
│ Build + sign tx │ │ │
│ │ │ │
│ GET /resource + cred │ │ │
│──────────────────────────>│ POST /mpp/settle │ │
│ │────────────────────────────>│ validate │
│ │ │ co-sign │
│ │ │ simulate ─────────>│
│ │ │ broadcast ────────>│
│ │ │ confirm <──────────│
│ │ { success, signature } │ │
│ │<────────────────────────────│ │
│ 200 + Receipt + content │ │ │
│<──────────────────────────│ │ │
/mpp/prepare to get the fee payer public key and a fresh blockhashTransferChecked transaction, sets Dexter as fee payer, partially signs/mpp/settleRecipients must have an existing USDC token account (ATA). Dexter's security policy does not allow transaction-time ATA creation — this prevents a class of rent-drain attacks on the fee payer. In practice, any wallet that has ever held USDC already has an ATA. If the recipient's ATA doesn't exist, the settlement will fail with a clear simulation error.
Buyers need only USDC. No SOL required — Dexter sponsors all transaction fees.
charge({
recipient: string; // Required. Solana wallet to receive payments.
apiUrl?: string; // Dexter API URL. Default: https://x402.dexter.cash
network?: string; // Solana network. Default: mainnet-beta
splToken?: string; // SPL token mint. Default: USDC
decimals?: number; // Token decimals. Default: 6
})
charge({
recipient: 'YourDevnetWallet...',
network: 'devnet',
})
charge({
recipient: 'YourWallet...',
apiUrl: 'http://localhost:4072',
})
charge({
signer: TransactionSigner; // Required. Any @solana/kit TransactionSigner.
computeUnitPrice?: bigint; // Priority fee in micro-lamports. Default: 1
computeUnitLimit?: number; // Compute unit limit. Default: 50,000
onProgress?: (event) => void // Optional. Called at each step of the payment flow.
})
The onProgress callback receives events as the transaction is built and signed: { type: "building" }, { type: "signing" }, { type: "signed", transaction }.
Works with:
createKeyPairSignerFromBytes() from @solana/kit for headless agentsuseTransactionSigner() for browser walletsTransactionSigner implementationOne-shot charge works for individual API calls. But if an agent is making hundreds or thousands of requests in a session — streaming tokens, polling data, orchestrating multi-step workflows — paying per-call means an on-chain transaction every time.
Sessions fix this. The buyer deposits once, pays per-request with signed vouchers (off-chain, microseconds), and settles on-chain only when the session closes. 10,000 API calls = 2 on-chain transactions instead of 10,000.
Buyer Agent Seller Server Dexter Solana
│ │ │ │
│ onboard() │ │ │
│────────────────────────────────────────────────────>│ provision Swig wallet │
│ │ │ grant session role ───>│
│ { swigAddress, roleId }│ │<────────────────────────│
│<────────────────────────────────────────────────────│ │
│ │ │ │
│ open(seller, deposit) │ │ │
│────────────────────────────────────────────────────>│ create channel │
│ │ │ deposit USDC ─────────>│
│ { channelId } │ │ │
│<────────────────────────────────────────────────────│ │
│ │ │ │
│ GET /api/data │ │ │
│────────────────────────>│ │ │
│ 402 + session challenge│ │ │
│<────────────────────────│ │ │
│ │ │ │
│ pay(channelId, amount) │ │ │
│────────────────────────────────────────────────────>│ sign voucher │
│ { voucher, signature } │ │ (no chain interaction) │
│<────────────────────────────────────────────────────│ │
│ │ │ │
│ GET /api/data │ │ │
│ + x-mpp-voucher header │ │ │
│────────────────────────>│ verifyVoucher() │ │
│ │ (local Ed25519, μs) │ │
│ 200 + data │ │ │
│<────────────────────────│ │ │
│ │ │ │
│ ... repeat pay → request → verify ... │ │
│ │ │ │
│ close(channelId) │ │ │
│────────────────────────────────────────────────────>│ settle final voucher │
│ │ │ refund remainder ─────>│
│ { settled, refund } │ │<────────────────────────│
│<────────────────────────────────────────────────────│ │
Accept streaming micropayments on any endpoint:
import { createSessionServer } from '@dexterai/mpp/server/session';
const sessions = createSessionServer({
recipient: 'YourSolanaWalletAddress...',
pricePerUnit: '10000', // 0.01 USDC per request
meter: 'api_calls', // usage label
});
app.get('/api/data', async (req, res) => {
const voucher = req.headers['x-mpp-voucher'];
if (!voucher) {
// No voucher — return 402 challenge telling the buyer how to open a session
return res.status(402).json(sessions.getChallenge());
}
const result = sessions.verifyVoucher(JSON.parse(voucher));
if (!result.valid) {
return res.status(402).json({ error: result.error });
}
// Paid — result.amountPaid is the delta for this request
res.json({ data: 'your paid content', paid: result.amountPaid });
});
The seller's server never touches the Solana network. verifyVoucher() checks the Ed25519 signature, enforces monotonic cumulative amounts and sequences, detects underpayment, and rejects signer changes — all locally in microseconds.
createSessionServer({
recipient: string; // Required. Solana wallet that receives payments.
pricePerUnit: string; // Required. Price per unit in atomic USDC (e.g., "10000" = 0.01 USDC).
apiUrl?: string; // Dexter API URL. Default: https://x402.dexter.cash
network?: string; // Solana network. Default: mainnet-beta
meter?: string; // Usage label (e.g., "api_calls", "tokens"). Default: "request"
suggestedDeposit?: string; // Suggested deposit in atomic USDC. Default: 100x pricePerUnit.
})
Full lifecycle for buyer agents — open, pay, close:
import { createSessionClient } from '@dexterai/mpp/client/session';
const session = createSessionClient({
buyerWallet: 'YourWallet...',
buyerSwigAddress: 'YourSwigWallet...',
onProgress: (event) => console.log(event.type, event),
});
// 1. Open a session with a seller
const channel = await session.open({
seller: 'SellerWallet...',
deposit: '1000000', // 1 USDC
});
// 2. Pay for each API call (voucher signed by Dexter, returned instantly)
const voucher = await session.pay(channel.channel_id, {
amount: '10000', // 0.01 USDC cumulative
serverNonce: nonceFromSeller,
});
// 3. Include voucher in request to seller
const response = await fetch('https://api.seller.com/data', {
headers: { 'x-mpp-voucher': JSON.stringify(voucher) },
});
// 4. Close when done — Dexter settles to seller, refunds remainder to buyer
const settlement = await session.close(channel.channel_id);
// settlement.amount_settled = what the seller received
// settlement.buyer_refund = what the buyer got back
createSessionClient({
buyerWallet: string; // Required. Buyer's Solana wallet address.
buyerSwigAddress: string; // Required. Buyer's Swig smart wallet address.
apiUrl?: string; // Dexter API URL. Default: https://x402.dexter.cash
network?: string; // Solana network. Default: mainnet-beta
onProgress?: (event) => void; // Session lifecycle events.
})
Progress events: opening, opened, voucher, closing, closed.
Before opening sessions, a buyer agent needs a Swig smart wallet with a delegated session role. The onboard() method handles this automatically — wallet provisioning, role granting, the multi-round flow for new wallets, and wallet ownership proof (CAIP-122 / SIWS).
The onboard endpoint requires the buyer to cryptographically prove they own the wallet being onboarded. onboard() constructs and sends this proof automatically using the signer you provide.
// With @solana/kit v2 (preferred — handles everything):
import { generateKeyPair } from '@solana/kit';
const signer = await generateKeyPair();
await session.onboard({ signer });
// With @solana/web3.js v1:
import { Keypair, VersionedTransaction } from '@solana/web3.js';
import nacl from 'tweetnacl';
const keypair = Keypair.generate();
await session.onboard({
signTransaction: async (txBase64) => {
const tx = VersionedTransaction.deserialize(Buffer.from(txBase64, 'base64'));
tx.sign([keypair]);
return Buffer.from(tx.serialize()).toString('base64');
},
signMessage: async (message) => {
return nacl.sign.detached(message, keypair.secretKey);
},
publicKey: keypair.publicKey.toBase58(),
});
onboard() returns { swigAddress, roleId, status: 'ready' }. If the buyer already has an active role, it returns immediately. If a new Swig wallet is needed, it handles both rounds (create wallet, then grant role) automatically.
The signer path (kit v2 CryptoKeyPair) handles both transaction signing and message signing. The callback path requires three things: signTransaction for onboard transactions, signMessage for wallet ownership proof, and publicKey for address verification.
// Shared schema (method name, intent, Zod schemas)
import { charge } from '@dexterai/mpp';
// Server charge method (one-shot, delegates settlement to Dexter API)
import { charge } from '@dexterai/mpp/server';
// Client charge method (builds + signs Solana transactions)
import { charge } from '@dexterai/mpp/client';
// Server session handler (accept streaming micropayments)
import { createSessionServer } from '@dexterai/mpp/server/session';
// Client session manager (open → pay → close lifecycle)
import { createSessionClient } from '@dexterai/mpp/client/session';
// HTTP client and types (for direct API access)
import { DexterSettlementClient, SettlementError } from '@dexterai/mpp/api';
// Constants (USDC mints, token programs, default API URL)
import { USDC_MINTS, TOKEN_PROGRAM, DEFAULT_DEXTER_API_URL } from '@dexterai/mpp/constants';
The settlement endpoints are open — no API keys, no accounts. Backpressure is tracked per recipient wallet address.
| Method | Path | Description |
|---|---|---|
| POST | /mpp/prepare |
Returns fee payer pubkey, recent blockhash, lastValidBlockHeight, and token config |
| POST | /mpp/settle |
Full settlement: validate, co-sign, simulate, broadcast, confirm |
| Method | Path | Description |
|---|---|---|
| POST | /mpp/session/open |
Open a payment channel — creates channel, deposits USDC into escrow |
| POST | /mpp/session/voucher |
Sign a voucher for a cumulative payment within an active channel |
| POST | /mpp/session/close |
Close a channel — settle to seller, refund remainder to buyer |
| POST | /api/sessions/onboard |
Provision a Swig smart wallet and grant a delegated session role. Requires SIGN-IN-WITH-X header. |
| POST | /api/sessions/onboard/confirm |
Submit signed onboarding transactions for co-signing and broadcast |
| GET | /api/sessions/onboard/status |
Check onboarding status for a buyer wallet. Requires SIGN-IN-WITH-X header. |
Settlement errors include typed error codes (e.g., policy:program_not_allowed, no_transfer_instruction, backpressure codes). The SettlementError class from @dexterai/mpp/api preserves these for programmatic handling.
Production: https://x402.dexter.cash
These endpoints run on the same infrastructure as Dexter's x402 facilitator — the same security validation, backpressure controls, and Sentry observability.
The Solana MPP SDK lets you embed settlement directly in your server. That works, but it means you operate:
@dexterai/mpp delegates all of that to Dexter. You install a package and set your recipient address.
| Error code | Cause | Fix |
|---|---|---|
policy:program_not_allowed |
Transaction contains a program not in the facilitator's allowlist (e.g., ATA creation, System program) | Remove non-standard instructions. Only ComputeBudget, SPL Token, Token-2022, Lighthouse, and Memo are allowed. |
no_transfer_instruction |
Transaction has no TransferChecked instruction |
The transaction must contain a USDC TransferChecked. Check your client is building the payment correctly. |
policy:fee_payer_not_isolated |
Fee payer address appears in an instruction's accounts | The fee payer can only sign for gas — it must not be referenced in any instruction. This prevents rent drain attacks. |
invalid_transaction_encoding |
The base64 transaction couldn't be deserialized | Verify the client is sending a valid base64-encoded Solana VersionedTransaction. |
settlement_recipient_mismatch |
Facilitator settled to a different address than the challenge specified | This indicates a facilitator bug. Contact Dexter support. |
settlement_amount_mismatch |
Facilitator settled a different amount than the challenge specified | Same as above — facilitator-side issue. |
global_settle_cap_exceeded |
Too many settlements globally — backpressure triggered | Wait and retry. The facilitator rate-limits to protect fee payer balance. |
seller_settle_cap_exceeded |
Too many settlements for this recipient — backpressure triggered | Your endpoint is receiving high traffic. Contact Dexter about tier upgrades. |
| Timeout after 10s on prepare | Facilitator unreachable or slow | Check that the facilitator is running and the apiUrl is correct. Default: https://x402.dexter.cash |
| Timeout after 30s on settle | Settlement took too long (Solana congestion, RPC issues) | Retry. If persistent, check Solana network status. |
| Simulation failure | The transaction would fail on-chain (insufficient USDC, ATA doesn't exist, etc.) | Verify the buyer has USDC and the recipient has a USDC token account (ATA). |
Dexter managed settlement is a trust-delegated model — similar to using Stripe for card payments. The seller trusts Dexter to settle payments correctly.
Two verification layers protect against facilitator bugs:
Settlement proof (default): Every successful settlement response includes the verified recipient, amount, asset, and feePayer. The SDK checks these match the original challenge before issuing a receipt.
On-chain verification (opt-in): Pass verifyRpcUrl to independently fetch and verify the transaction on-chain after settlement. Adds ~1-2s latency but is fully trustless.
Sessions use Swig smart wallets for non-custodial delegation. Dexter never has custody of buyer funds.
Vouchers are Ed25519-signed messages verified locally by the seller — no network calls, no Dexter involvement in the per-request verification.
The settlement API is HTTPS in production (https://x402.dexter.cash). In local development over HTTP, ensure your network is trusted.
npm install
npm run build # Compile to dist/
npm run typecheck # TypeScript checks
npm test # Unit tests
npm run test:devnet # Real payment on Solana devnet (requires facilitator running)
The devnet test exercises the complete flow with real on-chain transactions: ephemeral buyer and seller keypairs, real USDC transfers, full settlement through the live facilitator, and on-chain verification that the correct amount moved to the correct recipient with the correct fee payer.
MIT — see LICENSE
TypeScript Interface for Machine Payments Protocol
Alephant is an open-source AI Agent Gateway for routing, tracking, and controlling LLM usage across AI agents, members, and workflows, and for publishing agent capabilities as paid endpoints with x402 and MPP payment rails.
Specifications for the Machine Payments Protocol - powered by the "Payment" HTTP authentication scheme
Rust SDK for the Machine Payments Protocol
Website for the Machine Payments Protocol
Building blocks for Agentic payments (x402, MPP, AP2) for TypeScript, Rust, Go, Python, Ruby, PHP, Lua, Kotlin and Swift.