diff --git a/src/components/cards.tsx b/src/components/cards.tsx
index a8a68099..d8e1113c 100644
--- a/src/components/cards.tsx
+++ b/src/components/cards.tsx
@@ -344,10 +344,10 @@ export function SolanaChargeCard() {
export function SolanaSessionCard() {
return (
);
}
diff --git a/src/pages.gen.ts b/src/pages.gen.ts
index e5030005..cdac0345 100644
--- a/src/pages.gen.ts
+++ b/src/pages.gen.ts
@@ -55,6 +55,7 @@ type Page =
| { path: '/payment-methods/redotpay'; render: 'static' }
| { path: '/payment-methods/solana/charge'; render: 'static' }
| { path: '/payment-methods/solana'; render: 'static' }
+ | { path: '/payment-methods/solana/session'; render: 'static' }
| { path: '/payment-methods/stellar/charge'; render: 'static' }
| { path: '/payment-methods/stellar'; render: 'static' }
| { path: '/payment-methods/stellar/session'; render: 'static' }
diff --git a/src/pages/intents/session.mdx b/src/pages/intents/session.mdx
index ae4300de..d0e752f9 100644
--- a/src/pages/intents/session.mdx
+++ b/src/pages/intents/session.mdx
@@ -6,7 +6,7 @@ imageDescription: "Meter high-frequency usage with sessions"
import { Card, Cards } from 'vocs'
import { MermaidDiagram } from '../../components/MermaidDiagram'
-import { LightningSessionCard, StellarChannelCard } from '../../components/cards'
+import { LightningSessionCard, SolanaSessionCard, StellarChannelCard } from '../../components/cards'
# Session [Metered pay-as-you-go payments]
@@ -78,6 +78,7 @@ Each payment method defines how session setup, incremental authorization, verifi
title="Session"
to="/payment-methods/tempo/session"
/>
+
diff --git a/src/pages/payment-methods/solana/index.mdx b/src/pages/payment-methods/solana/index.mdx
index b14c619b..aabf06c0 100644
--- a/src/pages/payment-methods/solana/index.mdx
+++ b/src/pages/payment-methods/solana/index.mdx
@@ -54,11 +54,11 @@ Solana enables several useful capabilities for MPP:
Solana charge
One-time payments with signed transactions or confirmed signatures
-
+
Solana session
- Coming soon: Solana sessions with off-chain vouchers and on-chain settlement
+ Pay-as-you-go metered payments with off-chain vouchers and on-chain settlement
diff --git a/src/pages/payment-methods/solana/session.mdx b/src/pages/payment-methods/solana/session.mdx
new file mode 100644
index 00000000..4206f666
--- /dev/null
+++ b/src/pages/payment-methods/solana/session.mdx
@@ -0,0 +1,228 @@
+---
+description: "Meter pay-as-you-go Solana payments with off-chain vouchers backed by an on-chain escrow channel."
+imageDescription: "Pay-as-you-go Solana sessions with off-chain vouchers"
+---
+
+import { Cards } from 'vocs'
+import { MermaidDiagram } from '../../../components/MermaidDiagram'
+import { SpecCard } from '../../../components/SpecCard'
+
+# Solana session [Metered pay-as-you-go payments on Solana]
+
+The Solana implementation of the [session](/intents/session) intent.
+
+A session opens a unidirectional **payment channel** once, then meters usage through **off-chain signed vouchers** backed by an on-chain escrow. The client deposits funds into a channel program, signs a cumulative voucher for each unit of service consumed, and the server verifies each voucher with a fast signature check—no RPC round-trip per request. The server settles the highest accepted voucher on-chain whenever it chooses, batching many off-chain updates into a single transaction.
+
+This makes sessions the right intent when per-request on-chain settlement would be too slow or too expensive: streaming LLM tokens, metered APIs, or any high-frequency, sub-cent billing.
+
+## How it works
+
+### Overview
+
+>Server: (1) GET /resource
+ Server-->>Client: (2) 402 + session Challenge
+ Client->>Solana: (3) Open channel (deposit)
+ Client->>Server: (4) Open Credential (signed open tx)
+ Note over Server: verify deposit on-chain
+ Server-->>Client: 200 OK (session established)
+ loop Metered usage
+ Client->>Server: (5) Request + voucher (cumulative)
+ Note over Server: verify signature (~µs)
+ Server-->>Client: 200 OK + Receipt
+ end
+ Note over Server: (6) Periodic settlement
+ Server->>Solana: settle(channelId, highest voucher)
+ Client->>Server: (7) Close
+ Server->>Solana: settleAndFinalize + distribute
+ Solana-->>Client: Refund unused deposit
+`} />
+
+A payment session has four phases:
+
+::::steps
+
+### Open
+
+The client deposits funds into a channel account (a PDA derived from the payer, payee, mint, authorized signer, and a salt) managed by the channel program. The deposit is the hard cap the channel can ever spend. The server verifies the open transaction on-chain before metering against it.
+
+### Session
+
+The client signs vouchers with monotonically increasing cumulative amounts as service is consumed. Each voucher means "I have now authorized up to X total." The server verifies the Ed25519 signature, checks that the new cumulative amount exceeds the previously accepted one, and serves the resource based on the delta. This step is entirely off-chain.
+
+### Top up
+
+If the channel runs low, the client tops up the escrow without closing the channel. The session continues uninterrupted.
+
+### Close
+
+The server cooperatively closes by submitting `settleAndFinalize` with the highest voucher—typically bundled with `distribute` so the merchant payout, payer refund, treasury sweep, and channel tombstone all land atomically. Unused deposit is refunded to the client.
+
+::::
+
+## Server
+
+Register `solana.session` with `Mppx.create` to meter access behind a Solana payment channel. The server needs an `operator` and `recipient`, a spend `cap`, the `currency`, and a `pricing` hint. Supply a `signer` and `rpc` so the server can settle and close channels on-chain.
+
+```ts
+import { Mppx } from 'mppx/server'
+import { solana } from '@solana/mpp/server'
+import { createSolanaRpc } from '@solana/kit'
+
+const mppx = Mppx.create({
+ methods: [solana.session({
+ operator: '9xAXssX9j7vuK99c7cFwqbixzL3bFrzPy9PUhCtDPAYJ',
+ recipient: '9xAXssX9j7vuK99c7cFwqbixzL3bFrzPy9PUhCtDPAYJ',
+ cap: 10_000_000n, // 10 USDC max channel deposit
+ currency: 'USDC',
+ decimals: 6,
+ network: 'devnet',
+ pricing: { perDelivery: 100n }, // base units charged per metered unit
+ signer, // settles + closes the channel on-chain
+ rpc: createSolanaRpc('https://api.devnet.solana.com'),
+ })],
+ secretKey: process.env.MPP_SECRET_KEY!,
+})
+```
+
+The in-memory session store works for local development. For multi-instance deployments, pass a shared `store` so channel accounting (accepted vouchers, spent amount, settlement watermark) survives restarts and is consistent across processes.
+
+### Metered delivery routes
+
+For streaming or reserve-then-commit billing, mount the session control plane with `solana.session.routes()`. This exposes a `deliveries` endpoint that reserves a metered unit and a `commit` endpoint that records the voucher once service is delivered, so the server never charges beyond authorized value.
+
+```ts
+// Share one parameters object so the method handler and the routes
+// meter against the same channel store.
+const sessionParams = {
+ operator: '9xAXssX9j7vuK99c7cFwqbixzL3bFrzPy9PUhCtDPAYJ',
+ recipient: '9xAXssX9j7vuK99c7cFwqbixzL3bFrzPy9PUhCtDPAYJ',
+ cap: 10_000_000n,
+ currency: 'USDC',
+ decimals: 6,
+ network: 'devnet',
+ pricing: { perDelivery: 100n },
+}
+
+const mppx = Mppx.create({ methods: [solana.session(sessionParams)] })
+
+const routes = solana.session.routes(sessionParams)
+// routes.deliveries — POST: reserve a metered delivery
+// routes.commit — POST: commit a reserved delivery's voucher
+```
+
+## Client
+
+Use `createSessionFetch` for the high-level client flow. It answers the `402` session Challenge, opens the channel through your `opener`, then signs and submits a cumulative voucher on each request.
+
+```ts
+import { createSessionFetch } from '@solana/mpp/client'
+
+const session = createSessionFetch({
+ opener: myWalletOpener, // performs the real deposit / channel-open transaction
+})
+
+const response = await session.fetchWithSession('https://api.example.com/v1/chat/completions')
+// Opens the channel on the first 402, then meters subsequent requests off-chain
+```
+
+As usage accrues—for example, while streaming—advance the authorized amount and let the client batch commits:
+
+```ts
+// Authorize up to a new cumulative total as tokens stream in
+session.recordCumulative('250')
+
+// Force a commit immediately instead of waiting for the live-commit interval
+await session.commitCumulative('250')
+```
+
+The `opener` is where wallet approval and the on-chain deposit happen. For local gateways and demos, `createEphemeralSessionOpener()` fabricates push/pull open proofs with a generated key—never use it in production.
+
+```ts
+import { createSessionFetch, createEphemeralSessionOpener } from '@solana/mpp/client'
+
+const session = createSessionFetch({
+ opener: createEphemeralSessionOpener(), // dev only
+})
+```
+
+## Funding modes
+
+A challenge advertises one or more funding `modes`. The client picks the one it can satisfy.
+
+| Mode | How the channel is funded | When to use |
+|---|---|---|
+| `push` (default) | Client deposits into a channel program PDA; the deposit is the hard spend cap | Most sessions; trustless escrow with client-side forced close |
+| `pull` | Client approves a token delegation; the server draws vouchers against the delegated allowance | Wallets that prefer an approval to an escrow deposit |
+
+Pull mode requires the server to declare a `pullVoucherStrategy`. Push mode is the recommended default because funds sit in program-controlled escrow and the client can always recover them via forced close.
+
+## Fee sponsorship
+
+When the challenge sets `feePayer`, the server sponsors the cooperative on-chain operations it submits—open, top-up, settle, and close—so the client never needs SOL for transaction fees during the normal session lifecycle. The client partially signs the open transaction (deposit authority only) and the server co-signs as fee payer before broadcasting. Client-submitted escape routes (forced close, finalize, payer withdrawal) remain self-funded.
+
+## Session Receipts
+
+Session Receipts differ from charge Receipts. The `reference` field contains the payment channel ID, not a transaction hash. The on-chain settlement signature is only available after the channel is settled or closed.
+
+```ts
+type SolanaSessionReceipt = {
+ method: 'solana'
+ intent: 'session'
+ reference: string // channel ID
+ status: 'success'
+ timestamp: string // RFC 3339
+ acceptedCumulative: string // highest voucher amount accepted
+ spent: string // total charged so far
+ challengeId?: string
+ txHash?: string // settlement signature (close)
+ refunded?: string // unused deposit refunded to the client (close)
+}
+```
+
+| Field | Charge Receipt | Session Receipt |
+|-------|---------------|-----------------|
+| `reference` | Transaction signature | Channel ID |
+| `status` | `"success"` | `"success"` |
+| `method` | `"solana"` | `"solana"` |
+
+To get the settlement transaction signature, close the channel and read the `txHash` field from the returned Receipt.
+
+## Escrow safety and forced close
+
+Funds are held by the channel program, not the server. The server can only claim value by presenting valid voucher signatures on-chain, and the channel enforces that settlements never exceed the deposit. If the server becomes unresponsive, the client recovers unspent funds through forced close:
+
+1. The client submits `requestClose` directly to RPC, starting a **grace period** (recommended: 15 minutes).
+2. During the grace period the server may still settle outstanding vouchers via `settleAndFinalize`.
+3. After the grace period, anyone can permissionlessly `finalize` and `distribute`—the merchant side is paid, the payer is refunded, and the channel is tombstoned.
+
+The grace period is what prevents a client from using the service and then withdrawing before the server can settle accepted vouchers.
+
+:::warning
+Channels do not close automatically. Until a channel is closed (or force-closed after the grace period), the client's deposit stays reserved in escrow.
+:::
+
+## Solana-specific request fields
+
+The Solana session request extends the base session schema with `methodDetails` such as:
+
+- `network`
+- `channelProgram`
+- `channelId` (to resume an existing channel)
+- `decimals`
+- `tokenProgram`
+- `feePayer` / `feePayerKey`
+- `gracePeriodSeconds`
+- `minVoucherDelta`
+- `distributionSplits`
+
+Native SOL is not supported as a channel currency. Clients paying in SOL must wrap to wSOL (`So11111111111111111111111111111111111111112`) before opening a channel.
+
+## Specification
+
+
+
+
diff --git a/vocs.config.ts b/vocs.config.ts
index 569346d6..20e10811 100644
--- a/vocs.config.ts
+++ b/vocs.config.ts
@@ -440,6 +440,7 @@ export default defineConfig({
items: [
{ text: "Overview", link: "/payment-methods/solana" },
{ text: "Charge", link: "/payment-methods/solana/charge" },
+ { text: "Session", link: "/payment-methods/solana/session" },
],
},
{