Method.tempo.session
Sessions server method
Choose a signing account
Create a server-only wallet.ts module, then import account wherever an example creates a local signing account.
import { privateKeyToAccount } from 'viem/accounts'
export const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)Creates a Tempo Sessions server method for voucher verification, channel accounting, top-ups, and settlement.
Usage
import { , , } from 'mppx/server'
import { } from 'viem/accounts'
const = ('0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef')
const = .({
: [
.({
,
: '0x20c0000000000000000000000000000000000000', // pathUSD on Tempo
: .(),
}),
],
})With charge and session
Use the tempo() convenience function to register both one-time charges and Sessions.
import { , , } from 'mppx/server'
import { } from 'viem/accounts'
const = ('0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef')
const = .({
: [
({
,
: '0x20c0000000000000000000000000000000000000', // pathUSD on Tempo
: .(),
}),
],
})With Legacy Sessions
Use tempo.sessionLegacy() only for clients that still send Legacy Sessions Credentials. Pin mppx to 0.8.15 or earlier when maintaining a Legacy Sessions server.
import { Mppx, Store, tempo } from 'mppx/server'
import { privateKeyToAccount } from 'viem/accounts'
const account = privateKeyToAccount('0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef')
const mppx = Mppx.create({
methods: [
tempo.sessionLegacy({
account,
currency: '0x20c0000000000000000000000000000000000000', // pathUSD on Tempo
store: Store.memory(),
}),
],
})Migrate from Legacy Sessions
Register tempo.session() beside tempo.sessionLegacy() during migration so existing clients keep working while new clients use Sessions. This server-side migration configuration requires mppx 0.8.15 or earlier.
| Server methods | Compatible client methods |
|---|---|
tempo.session() only | tempo.session() or tempo() |
tempo.sessionLegacy() only | tempo.sessionLegacy() or tempo.sessionLegacy.method() |
tempo.session() and tempo.sessionLegacy() | tempo.session() plus tempo.sessionLegacy.method() when clients may see both Challenge types |
Current Sessions Challenges include sessionProtocol: "v2" and use TIP-1034 reserve channels. Legacy Sessions use the contract-backed Sessions v1 flow. Channel state is not portable between them, so close or settle Legacy channels with tempo.sessionLegacy and open new channels with tempo.session.
import { Mppx, Store, tempo } from 'mppx/server'
import { privateKeyToAccount } from 'viem/accounts'
const account = privateKeyToAccount('0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef')
const mppx = Mppx.create({
methods: [
tempo.session({
account,
chainId: 4217,
currency: '0x20c0000000000000000000000000000000000000', // pathUSD on Tempo
store: Store.memory(),
}),
tempo.sessionLegacy({
account,
currency: '0x20c0000000000000000000000000000000000000', // pathUSD on Tempo
store: Store.memory(),
}),
],
})Use tempo.sessionLegacy() only while clients still send Legacy Sessions Credentials.
With same-route bootstrap
Set bootstrap: true and provide resolveChannelId when clients should recover a previous channel before opening a new one. Bootstrap uses the protected route itself: the client sends HEAD, answers a $0 Tempo charge Challenge, and the server uses the verified source plus request metadata to look up an existing channel.
import { , , } from 'mppx/server'
import { } from 'viem/accounts'
const = ('0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef')
const = .()
const = new <string, string>()
const = .({
: [
.({
,
: true,
: '0x20c0000000000000000000000000000000000000', // pathUSD on Tempo
,
({ , }) {
return ( ? .() : ) ?? ?..('Payment-Session') ??
},
}),
],
})If the resolver returns a known channel ID, the server responds with Payment-Session and Payment-Session-Snapshot. If it returns undefined, the server returns 204 without a snapshot and the client falls back to opening a new channel.
Validate before broadcast
Tempo Sessions support the split Credential lifecycle used by mppx.validateCredential and mppx.broadcastCredential. Validation checks the Credential without changing channel state. Broadcasting validates again, accepts the voucher or management action, updates accounting, and returns a Receipt.
Use the split lifecycle for custom transports or background workflows. Normal route handlers invoke it automatically.
Return type
import type { Method } from 'mppx'
type ReturnType = Method.ServerParameters
account
- Type:
Account
Account used for server-driven settlement and close transactions. The account address is also used as the default recipient.
Server-driven Session transactions use Tempo expiring nonce lanes, so independent processes can share this signing account without relying on process-local nonce coordination.
amount (optional)
- Type:
string
Default amount to charge per unit.
bootstrap (optional)
- Type:
boolean
Enables same-route HEAD bootstrap using a zero-amount identity proof. Clients can use this to recover a session snapshot before opening a new channel.
canOffer (optional)
- Type:
Method.CanOfferFn
Returns whether this configured Sessions offer is available when an HTTP handler composes multiple offers. The hook receives the normalized, immutable payment request and a clone of the incoming Request.
chainId (optional)
- Type:
number
Tempo chain ID used for Sessions Challenges. Use 4217 for mainnet and 42431 for Moderato testnet.
channelStateTtl (optional)
- Type:
number - Default:
5000
TTL in milliseconds for cached on-chain channel state. After this duration, the server re-queries on-chain state during voucher handling to detect forced close requests.
currency (optional)
- Type:
Address
Default TIP-20 token address for the payment currency.
decimals (optional)
- Type:
number - Default:
6
Decimal places for amount parsing.
escrowContract (optional)
- Type:
Address
Reserve contract advertised in each Challenge. Defaults to the canonical TIP-1034 precompile. Clients reject a noncanonical address unless they explicitly trust custom reserve contracts or pin this exact address.
feePayer (optional)
- Type:
Account | { fetch?: typeof globalThis.fetch; headers?: Readonly<Record<string, string>>; url: string } | string | true
Account or remote service for sponsoring open, top-up, settlement, and close transaction fees. Pass a viem Account to co-sign locally, a URL string to use a remote fee payer service, an object with fetch, headers, and url to customize remote requests, or true when the account parameter doubles as the fee payer. The custom fetch handles only fee-payer JSON-RPC traffic. mppx reserves the Content-Type header for JSON.
A remote fee payer service sponsors server-driven scheduled settlement and cooperative close transactions in addition to client-driven open and top-up transactions.
feePayerPolicy (optional)
- Type:
Partial<{ allowKeyAuthorization: boolean; maxFeePerGas: bigint; maxGas: bigint; maxPriorityFeePerGas: bigint; maxTotalFee: bigint; maxValidityWindowSeconds: number }>
Override the local fee-sponsor policy used for sponsored open and top-up transactions and server-driven close transactions.
allowKeyAuthorization defaults to true. Set it to false when the sponsor must reject transactions that install a new access key.
feeToken (optional)
- Type:
Address
Fee token used for Session management, scheduled settlement, and cooperative close transactions. Hosted fee payers preserve this configured token when they complete server-driven transactions.
getClient (optional)
- Type:
(parameters: { chainId?: number }) => MaybePromise<Client>
Function that returns a viem client for the given chain ID.
minVoucherDelta (optional)
- Type:
string - Default:
"0"
Minimum voucher delta to accept as a numeric string. Rejects vouchers where the increment over the previous highest voucher is below this threshold.
onPaymentSuccess (optional)
- Type:
Method.OnPaymentSuccessFn<typeof tempo.Methods.session>
Runs after this Sessions method returns a successful result. The hook receives the optional associated Challenge, canonical request, its Receipt, the HTTP input when available, and the resolved pre-transform requestInput when route options are available. Errors don't change payment handling.
operator (optional)
- Type:
Address
Payee-side operator authorized for channel operations.
recipient (optional)
- Type:
Address
Default recipient address for payments. Defaults to the account address.
resolveChannelId (optional)
- Type:
ResolveSessionChannelId
Function that resolves a reusable channel ID from request identity when no Credential or request channel ID is present.
type ResolveSessionChannelId = (
parameters: ResolveSessionChannelIdParameters,
) => Promise<string | null | undefined> | string | null | undefined
type ResolveSessionChannelIdParameters = {
request?: {
readonly headers: Headers
readonly hasBody?: boolean
readonly method: string
readonly url?: URL
}
credential: Credential | null | undefined
source?: string
paymentRequest: SessionPaymentRequestInput
store: ChannelStore
}Use source for identity bootstrap. It is only set after the server verifies the client's zero-amount proof. Use request.headers for app session cookies or explicit Payment-Session hints. The returned channel ID is treated as a hint; the server still verifies that the channel exists and matches the current payment request.
settlementSchedule (optional)
- Type:
{ amount?: string | bigint; intervalMs?: number; units?: number }
Server-owned automatic settlement cadence. Clients don't receive or control this schedule.
sse (optional)
- Type:
boolean | { poll?: boolean; pollingInterval?: number }
Enable SSE streaming. Pass true to enable with defaults, or pass an options object to configure SSE.
When poll is enabled, the stream checks the store at pollingInterval instead of waiting on store notifications. Use this for runtimes where promises cannot be resolved across request contexts.
store (optional)
- Type:
Store.AtomicStore - Default:
Store.memory()
Atomic store backend for channel state. Session mutations must be linearizable across instances so voucher, top-up, spend, and close updates cannot race.
import { , } from 'mppx/server'
import { } from 'viem/accounts'
const = .({
: ('0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'),
: .(),
})Use Store.memory() for local development and tests. For multi-instance deployments, use Store.redis(), Store.upstash(), or Store.cloudflare().
suggestedDeposit (optional)
- Type:
string
Suggested deposit amount communicated to clients in the Challenge.
unitType (optional)
- Type:
string
Unit type label, such as "token", "byte", or "request".
Related
mppx.session.serveWebSocket
Serves a WebSocket route with the store and automatic settlement schedule configured by this Session method.
await mppx.session.serveWebSocket({
generate,
route,
socket,
url: 'wss://api.example.com/stream',
})See mppx.session.serveWebSocket for the full API.
mppx.session.settleScheduled
Applies this method's settlementSchedule to a committed channel charge. The session-bound WebSocket and SSE transports call it automatically. Pass it to a custom streaming adapter's post-charge hook when you manage the transport yourself.
Snapshot helpers
Use these helpers when you need to read or write the Payment-Session-Snapshot header yourself.
import { tempo } from 'mppx/server'
const header = tempo.session.serializeSnapshot(snapshot)
const snapshot = tempo.session.deserializeSnapshot(header)type SessionSnapshot = {
acceptedCumulative: string
chainId: number
channelId: Hex
closeRequestedAt?: string
deposit: string
descriptor: ChannelDescriptor
escrow: Address
requiredCumulative: string
settled: string
spent: string
units?: number
}tempo.session.charge
Charges against a precompile-backed channel's stored balance outside an HTTP handler.
import { tempo } from 'mppx/server'
const state = await tempo.session.charge(store, channelId, 10_000n)This returns the updated channel state and throws if the channel is missing, closed, or does not have enough unspent deposit.
tempo.session.settle
Settles a single precompile-backed channel on-chain.
import { tempo } from 'mppx/server'
const txHash = await tempo.session.settle(store, client, channelId)The sender must be the channel payee or a nonzero operator. Pass transaction options as the fourth argument. Set feePayer to an account for local co-signing, or true when the client transport uses a configured hosted fee payer service.
type SettlementTransactionOptions = {
account?: Account
candidateFeeTokens?: readonly Address[]
escrowContract?: Address
feePayer?: Account | true
feePayerPolicy?: Partial<FeePayerPolicy>
feeToken?: Address
onSessionSettlement?: OnSessionSettlement
}tempo.session.settleBatch
Settles multiple precompile-backed channels on-chain.
import { tempo } from 'mppx/server'
const txHashes = await tempo.session.settleBatch(store, client, channelIds)settleBatch applies the same validation as settle to each channel and returns the settlement transaction hashes in order.