# Quicknode RPC via MPP (Machine Payments Protocol) > Pay-per-request blockchain RPC access using the Machine Payments Protocol (MPP). No signup, no API keys — just a wallet. Two payment methods: PathUSD or USDC.e on Tempo or USDC on Solana. ## How it works Quicknode RPC via MPP lets you access blockchain data through JSON-RPC and REST calls, paid for with stablecoin micropayments. MPP is an open protocol (IETF spec at https://paymentauth.org) for machine-to-machine payments. ### Payment Methods Two payment methods are supported. The server accepts either — choose whichever suits your stack: 1. **Tempo (PathUSD / USDC.e)** — Pay with PathUSD or USDC.e (bridged USDC) stablecoin on the Tempo blockchain. Supports both charge and session intents. Available on both Testnet (chain ID 42431) and Mainnet (chain ID 4217). 2. **Solana (USDC)** — Pay with USDC on Solana. Supports charge intent. Uses SPL token transfers verified on-chain via reference keys. ### Payment Intents 1. **Charge** ($0.001 per request) — One-time payment per request. The client signs a token transfer, the server settles it on-chain, and proxies the RPC request. Best for simple integrations and low-volume usage. 2. **Session** ($0.00001 per request) — Open a payment channel with an on-chain deposit, then send off-chain cumulative vouchers on each request. Ultra-low latency for high-volume usage ($10/million requests). ### Charge Flow 1. **Hit any endpoint** — `POST https://mpp.quicknode.com/:network` with a JSON-RPC or REST request and no `Authorization` header. 2. **Receive 402 challenge** — Response includes `WWW-Authenticate: Payment` header with a Challenge containing `method="tempo"` or `method="solana"`, `intent="charge"`, the amount, currency, and recipient. 3. **Pay** — Sign a token transfer for the specified amount and include the Credential in the `Authorization: Payment` header on retry. 4. **Access** — Server verifies the payment, settles the transaction on-chain, and proxies the request to Quicknode. Response includes a `Payment-Receipt` header with the transaction hash. ### Session Flow (pay-as-you-go) 1. **Open a session** — `POST https://mpp.quicknode.com/session/:network` with no credential. Receive 402 with session Challenge containing `intent="session"`, amount per request, and deposit requirements. 2. **Deposit** — Open a payment channel by depositing tokens into an on-chain escrow contract (~500ms first time). A unique `channelId` identifies your channel. 3. **Send vouchers** — Each subsequent request includes a cumulative EIP-712 voucher in `Authorization: Payment` (e.g., "I have now consumed up to X total"). No on-chain transaction per request — server verifies with a single `ecrecover`. 4. **Top up** — If the channel runs low, deposit additional tokens without closing. Session continues uninterrupted. 5. **Close** — Close the payment channel when done. Server settles the final balance on-chain. Unused deposit is refunded. ## Protocol Details MPP uses standard HTTP headers following the IETF Payment Authentication specification: - **Challenge:** `WWW-Authenticate: Payment id="...", realm="mpp.quicknode.com", method="tempo", intent="charge", request=""` (server -> client, on 402) - **Credential:** `Authorization: Payment ` (client -> server) - **Receipt:** `Payment-Receipt: ` (server -> client, on success) This is distinct from x402 which uses `PAYMENT-REQUIRED` and `PAYMENT-SIGNATURE` headers. Base URL: `https://mpp.quicknode.com` ### Tempo Chain **Tempo Testnet (Moderato):** Chain ID 42431. PathUSD token at `0x20c0000000000000000000000000000000000000` (6 decimals, TIP-20 enshrined token). Testnet has a lifetime cap of 10,000 requests per intent per wallet — see Pricing section. **Tempo Mainnet:** Chain ID 4217. PathUSD token at `0x20c0000000000000000000000000000000000000` (6 decimals). USDC.e (bridged USDC) token at `0x20C000000000000000000000b9537d11c60E8b50` (6 decimals, TIP-20). Both currencies are accepted for charge and session intents. No lifetime request cap on mainnet. ### Solana **Solana Network:** Mainnet only. USDC token mint: `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` (6 decimals, SPL token). ## Bootstrapping — Tempo (PathUSD) 1. **Generate a wallet** — Use `generatePrivateKey()` and `privateKeyToAccount()` from `viem/accounts` to create a fresh keypair. 2. **Fund with PathUSD** — For testnet: get testnet PathUSD on Tempo Moderato (chain ID 42431) via the Tempo testnet faucet or bridge. For mainnet: acquire PathUSD or USDC.e on Tempo Mainnet (chain ID 4217) via a supported bridge or exchange. 3. **Install mppx** — `npm install mppx viem` 4. **Create client** — Call `Mppx.create()` with a `tempo({ account })` method. This polyfills `fetch` to handle 402 challenges automatically. 5. **Make RPC calls** — Use `fetch()` normally. Payment happens transparently when the server returns 402. ```typescript import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts' // No existing key needed — generate one on the fly const privateKey = generatePrivateKey() const account = privateKeyToAccount(privateKey) // Store privateKey securely — you will need it for future sessions ``` ## Bootstrapping — Solana (USDC) 1. **Generate a wallet** — Use `generateKeyPairSigner()` from `@solana/kit` to create a fresh keypair signer. 2. **Fund with USDC** — Acquire USDC on Solana Mainnet via a supported exchange or bridge. 3. **Install @solana/mpp** — `npm install @solana/mpp mppx @solana/kit` 4. **Create client** — Call `Mppx.create()` with a `solana.charge({ signer })` method. 5. **Make RPC calls** — Use `fetch()` normally. Payment happens transparently when the server returns 402. ```typescript import { generateKeyPairSigner } from '@solana/kit' // Pass extractable=true so you can export the private key bytes for storage. const signer = await generateKeyPairSigner(true) console.log('Public key:', signer.address) // Export the raw private key bytes (32 bytes) — store securely. const privateKeyBytes = new Uint8Array( await crypto.subtle.exportKey('raw', signer.keyPair.privateKey), ) ``` ## Client Setup — Tempo Install: `npm install mppx viem` ### Polyfill mode (recommended — simplest) ```typescript import { Mppx, tempo } from 'mppx/client' import { privateKeyToAccount } from 'viem/accounts' const account = privateKeyToAccount('0xYOUR_PRIVATE_KEY') // Polyfills globalThis.fetch — all subsequent fetch() calls handle 402 automatically Mppx.create({ methods: [tempo({ account })], }) // Use fetch normally — payment happens transparently on 402 const response = await fetch('https://mpp.quicknode.com/base-sepolia', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_blockNumber', params: [] }), }) const { result } = await response.json() console.log('Block number:', BigInt(result)) ``` ### Without polyfill ```typescript import { Mppx, tempo } from 'mppx/client' import { privateKeyToAccount } from 'viem/accounts' const account = privateKeyToAccount('0xYOUR_PRIVATE_KEY') const mppx = Mppx.create({ polyfill: false, methods: [tempo({ account })], }) // Use mppx.fetch instead of global fetch const response = await mppx.fetch('https://mpp.quicknode.com/base-sepolia', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_blockNumber', params: [] }), }) ``` ## Client Setup — Solana Install: `npm install @solana/mpp mppx @solana/kit` `@solana/mpp` is a peer of `mppx` — `Mppx.create` still comes from `mppx/client`, and the Solana method from `@solana/mpp/client`: ```typescript import { Mppx } from 'mppx/client' import { solana } from '@solana/mpp/client' import { createKeyPairSignerFromBytes } from '@solana/kit' const signer = await createKeyPairSignerFromBytes( Uint8Array.from(JSON.parse(process.env.SOLANA_KEY!)), ) Mppx.create({ methods: [solana.charge({ signer })], }) // Use fetch normally — payment happens transparently on 402 const response = await fetch('https://mpp.quicknode.com/solana-mainnet', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'getSlot', params: [] }), }) const { result } = await response.json() console.log('Slot:', result) ``` ## Charge-Only Client (Tempo or Solana) The charge endpoint (`/:network`) returns a 402 challenge listing **both** Tempo and Solana payment methods. Your client only needs to support one — pick whichever suits your stack. The `Authorization: Payment` credential tells the server which method you chose. - **Tempo charge** — Use `mppx/client` with `tempo({ account })`. Requires a viem account (EVM private key). Accepts PathUSD or USDC.e — the server offers both in the 402 challenge. - **Solana charge** — Use `@solana/mpp/client` with `solana.charge({ signer })`. Requires a `@solana/kit` signer with funded USDC. Both handle the 402 → credential → retry flow automatically when using polyfill mode. No session or payment channel setup required. ### Manual payment handling ```typescript import { Mppx, tempo } from 'mppx/client' import { privateKeyToAccount } from 'viem/accounts' const mppx = Mppx.create({ polyfill: false, methods: [tempo()], // no account here — passed per-request below }) // Step 1: Make request, get 402 challenge const response = await fetch('https://mpp.quicknode.com/base-sepolia', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_blockNumber', params: [] }), }) if (response.status === 402) { // Step 2: Create credential from challenge const credential = await mppx.createCredential(response, { account: privateKeyToAccount('0xYOUR_PRIVATE_KEY'), }) // Step 3: Retry with payment credential const paidResponse = await fetch('https://mpp.quicknode.com/base-sepolia', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: credential, }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_blockNumber', params: [] }), }) } ``` ### Payment receipts On success, the server returns a `Payment-Receipt` header: ```typescript import { Receipt } from 'mppx' const response = await fetch('https://mpp.quicknode.com/base-sepolia', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_blockNumber', params: [] }), }) const receipt = Receipt.fromResponse(response) console.log(receipt.status) // "success" console.log(receipt.reference) // "0xtx789abc..." (charge: tx hash, session: channelId) ``` ## Quick testing with the mppx CLI The `mppx` CLI makes paid HTTP requests with automatic payment handling: ```bash # Install globally npm install -g mppx # Create an account (generates a wallet) mppx account create # Make a paid JSON-RPC request mppx -X POST -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}' \ https://mpp.quicknode.com/base-sepolia ``` Environment variables: `MPPX_PRIVATE_KEY` (use a key directly), `MPPX_ACCOUNT` (default account name). ## Endpoints - `POST /:network/*` — JSON-RPC/REST proxy via charge intent (MPP payment required) - `POST /session/:network/*` — JSON-RPC/REST proxy via session intent (MPP session required) - `GET /networks` — List all supported network slugs (public, rate limited) - `GET /llms.txt` — This document ### Charge endpoint: `https://mpp.quicknode.com/:network` Replace `:network` with any supported network slug (e.g., `base-sepolia`, `ethereum-mainnet`, `solana-mainnet`). Same network slugs as the x402 endpoint. Send a JSON-RPC or REST request: ``` POST /base-sepolia HTTP/1.1 Content-Type: application/json {"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]} ``` Without payment: returns 402 with `WWW-Authenticate: Payment` challenge. With valid `Authorization: Payment` credential: proxies to Quicknode and returns the response with `Payment-Receipt` header. ### Session endpoint: `https://mpp.quicknode.com/session/:network` Same format as charge but uses session vouchers for lower per-request cost. Session state (payment channel) persists across requests. ## Pricing | Intent | Cost per request | Token amount (6 decimals) | Best for | |---------|-----------------|--------------------------|----------| | Charge | $0.001 | 1,000 atomic units | Simple integrations, low volume | | Session | $0.00001 | 10 atomic units | High volume, agents, metered usage | ### Testnet Lifetime Caps Tempo testnet wallets are limited to **10,000 lifetime requests per intent** (charge and session counted separately). When the cap is reached, the server returns HTTP 403 with `{ "error": "lifetime_limit_reached" }` — no payment is taken. Mainnet wallets have no lifetime cap. If your testnet cap is exhausted, switch to a mainnet-funded wallet for unlimited access. ## Rate limits - `/networks`: 10 requests per 10 seconds per IP - `/:network`: 1,000 requests per 10 seconds per IP:network pair - `/session/:network`: 1,000 requests per 10 seconds per IP:session:network pair ## Error responses Errors return JSON with an `error` code and optional `message`: `{ "error": "", "message"?: "" }`. | Status | Error code | Description | |--------|-----------|-------------| | 402 | (MPP challenge) | Payment required — `WWW-Authenticate: Payment` header contains the Challenge | | 403 | `lifetime_limit_reached` | Testnet lifetime request cap exceeded — no payment taken. Switch to mainnet wallet for unlimited access | | 404 | `unsupported_network` | Network slug not recognized | | 429 | `rate_limit_exceeded` | Too many requests — back off and retry | | 503 | `mpp_not_configured` | MPP is not configured for this environment | ## Supported Networks Same networks as x402 — 140+ blockchain networks including all testnets. Discover all supported network slugs dynamically via `GET https://mpp.quicknode.com/networks`. Common slugs: `ethereum-mainnet`, `base-sepolia`, `base-mainnet`, `polygon-mainnet`, `solana-mainnet`, `arbitrum-mainnet`, etc. ## npm packages - `mppx` — Official TypeScript SDK for the Machine Payments Protocol. Client (`mppx/client`), server (`mppx/server`), and framework middleware (`mppx/hono`, `mppx/nextjs`, `mppx/express`, `mppx/elysia`). Includes CLI. - `@solana/mpp` — Solana payment method for MPP. Client (`@solana/mpp/client`) and server (`@solana/mpp/server`). - `viem` — Ethereum/Tempo wallet client, signing, and chain utilities (peer dependency of mppx) - `@solana/kit` — Solana SDK (peer dependency of @solana/mpp; provides TransactionSigner) ## Related - [Quicknode MPP Docs](https://www.quicknode.com/docs/build-with-ai/mpp-payments) — Quicknode guide to MPP payments - `https://x402.quicknode.com` — x402 payment protocol endpoint (SIWX auth, USDC payments on Base/Polygon/Solana) - [MPP Specification (IETF)](https://paymentauth.org) — Machine Payments Protocol spec - [MPP Documentation](https://mpp.dev) — Full protocol docs, guides, and SDK reference - [mppx npm package](https://www.npmjs.com/package/mppx) — TypeScript SDK for MPP - [@solana/mpp npm package](https://www.npmjs.com/package/@solana/mpp) — Solana payment method for MPP - [Solana MPP SDK](https://github.com/solana-foundation/mpp-sdk) — Solana Foundation MPP SDK (TypeScript, Rust, Go, Python, Lua) - [Tempo Documentation](https://docs.tempo.xyz) — Tempo blockchain docs