Skip to content
LogoLogo

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.

wallet.ts
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

Creates OUSD-first offers with USDC.e as the mainnet fallback, or pathUSD with testnet: true. Set currencies to accept a different list.

import { , ,  } from 'mppx/server'
import {  } from 'viem/accounts'
 
const  = ('0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef')
 
const  = .({
  : [
    .({
      ,
      : .(),
    }),
  ],
})

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  = .({
  : [
    ({
      ,
      : ['0x20c0000000000000000000006a37DA5C996874BE'], // OUSD on Tempo
      : .(),
    }),
  ],
})

Migrate from Legacy Sessions

Legacy Sessions used a contract-backed channel flow. Close or settle those channels with the older SDK before upgrading. Then register tempo.session() on the server and tempo.session() or tempo() on clients. New Sessions advertise sessionProtocol: "v2" and use TIP-1034 reserve channels.

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,
      : ['0x20c0000000000000000000006a37DA5C996874BE'], // OUSD 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.

Content requests require a voucher that advances the accepted cumulative amount before the server charges for another response. Management updates remain idempotent.

Use the split lifecycle for custom transports or background workflows. Normal route handlers invoke it automatically.

Return type

Returns a non-empty, ordered array of server methods, one per accepted currency. Mppx.create({ methods: [tempo.session(options)] }) accepts the group directly.

type ReturnType = readonly [Method.Server, ...Method.Server[]]

Code that inspects a single method must destructure the group. For route composition, use the configured mppx.tempo.session handler.

Parameters

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

Defaults to mainnet (4217), or Moderato (42431) with testnet: true. An explicit chainId takes precedence; set it when getClient uses another network.

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.

chargeOnAcceptance (optional)

  • Type: boolean

Charge request-priced content when its voucher is accepted. Refund content you cannot serve with tempo.session.credit.

currencies (optional)

  • Type: readonly (string | Token)[]
  • Default: OUSD, then USDC.e on mainnet; OUSD, then pathUSD on Moderato

Ordered accepted token addresses or USD token definitions from viem/tokens. An explicit list replaces the defaults; currencies: [ousd(4217).address] accepts only OUSD on mainnet. Token definitions resolve against chainId; unavailable and non-USD tokens are skipped, and duplicate addresses are removed. An empty resolved list throws. Ordering controls presentation, not client selection or automatic swaps.

currency (optional, deprecated)

  • Type: string

Accepts one token address. Use currencies: [currency] instead. Don't set both options. Wire Challenges and per-handler overrides still use singular 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 client-driven open and top-up transactions, credential-triggered settlement and close transactions, and server-driven scheduled settlement and cooperative close transactions.

feePayerPolicy (optional)

  • Type: Partial<{ 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.

Sponsored transactions may carry a keyAuthorization, so an access key can open a sponsored Session in the transaction that installs it. maxGas and maxTotalFee bound the sponsor's cost.

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.

onSessionSettlement (optional)

  • Type: OnSessionSettlement

Runs after a settlement or close transaction confirms, including when recording the settlement in the channel store fails. Observer errors don't change payment handling. amount is cumulative; delta is the amount paid by this transaction, read from the settlement Receipt so retries don't double-count a stale checkpoint.

type OnSessionSettlement = (
  context: Readonly<{
    amount: bigint
    channelId: Hex
    delta: bigint
    trigger: 'close' | 'scheduled' | 'settle'
    txHash: Hex
  }>,
) => void | Promise<void>

onSessionSettlementFailure (optional)

  • Type: OnSessionSettlementFailure

Runs when scheduled settlement or Credential-driven close fails, including a confirmed settlement whose channel-store checkpoint fails. Observer errors are ignored. Explicit settle calls report errors to their caller.

import { ,  } from 'mppx/server'
import {  } from 'viem/accounts'
 
const  = .({
  : ('0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'),
  ({ , , ,  }) {
    .({ , , ,  })
  },
  : { : 100 },
  : .(),
})
type OnSessionSettlementFailure = (
  context: Readonly<{
    chainId: number
    channelId: Hex
    error: unknown
    trigger: 'close' | 'scheduled'
  }>,
) => void | Promise<void>

Import Session from mppx/tempo to access Session.Server.OnSessionSettlementFailure and Session.Server.SessionSettlementFailureContext. The success-hook types are Session.Server.OnSessionSettlement and Session.Server.SessionSettlementContext.

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.

reusableChallenges (optional)

  • Type: boolean

Keep a Session Challenge valid for later requests to the same route until it expires. The binding includes route, currency, recipient, and channel terms, but excludes the quoted amount. Each request charges its own price from the accepted channel balance. Clients can send a voucher with the request instead of waiting for a 402.

settlementSchedule (optional)

  • Type: { amount?: string | bigint; intervalMs?: number; units?: number }

Server-owned automatic settlement cadence. Clients don't receive or control this schedule.

HTTP, SSE, and WebSocket charges remain served when scheduled settlement encounters a transient transport or RPC failure, rate limit, receipt timeout, or lease-claim failure. The charge remains due for a later settlement. Reverts and configuration errors still fail the request. A confirmed settlement whose checkpoint fails also keeps the charged request successful and reports the error through onSessionSettlementFailure.

Settlement records only the spend and units captured with the submitted voucher. Charges accepted while that transaction is pending remain due for the next settlement.

sponsorshipLimit (optional)

  • Type: { perDay?: number; store: Store.AtomicStore }

Limit sponsored channel opens and top-ups separately per verified transaction signer and UTC day. The default limit is five of each. Transactions over the limit continue without sponsorship, so the payer needs its own fee balance.

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.

The in-memory fallback logs a one-time warning because replay protection applies only within one instance.

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".

voucherStateMaxAge (optional)

  • Type: number
  • Default: 0

Maximum age in milliseconds of verified on-chain state used to accept a voucher before a fresh read finishes in the background. Keep it well below Tempo's 900-second close grace period so accepted vouchers remain settleable. The default reads on-chain state before every acceptance.

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.

Direct calls report failures through onSessionSettlementFailure and reject on settlement failure. Custom adapters must handle retryable failures themselves to keep a charged response served. Confirmed checkpoint failures return the confirmed transaction hash.

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, {
  idempotencyKey: operationId,
})

This returns the updated channel state and throws if the channel is missing, closed, or does not have enough unspent deposit. Retrying the same key and amount returns the recorded charge. Reusing a key with another amount throws IdempotencyKeyConflictError. Pass retention in milliseconds when retries can span more than the default 24 hours.

tempo.session.credit

Return unused spend to a channel at most once per key. Credit only unserved content; a declined credit leaves spend unchanged and needs another refund path. An unknown outcome means the store write may have committed, so retry with the same key.

import { tempo } from 'mppx/server'
 
const result = await tempo.session.credit(store, channelId, unusedAmount, {
  idempotencyKey: refundId,
})

credit declines when the channel is closing or finalized, or the credit would reduce spend below zero or an on-chain settlement floor. The units option also returns charged units. Charge and credit keys remain on the channel record for 24 hours by default and expire during a later keyed operation.

tempo.session.settle

Settles the largest accepted voucher at or below recorded spend for a single precompile-backed channel. It throws when no voucher is settleable.

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.

settle reads back channel state at the confirmed Receipt's block. If the transaction confirms but the channel store can't record it, the success hook still runs and settle throws tempo.SettlementCheckpointError. Read error.txHash to identify the confirmed payment; error.cause contains the store failure.

When an RPC replica temporarily reports a revert while preparing settlement, settle retries preparation. Sponsored settlement and close transactions recheck that at least 15 seconds of validity remain before signing and submission.

For low-level retry control, tempo.Session.Precompile.Chain.readbackWithRetry accepts an optional shouldRetry(error) => boolean callback. It retries every failure by default; return false to stop on a definitive error.

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.

tempo.session.superviseCloses

Run close supervision on a schedule, for example every minute. Track channel IDs before their first charge and pass them to each run; the store does not list channels. Supervision reads one pinned block, answers payer close requests before Tempo's 900-second grace period ends, reopens cancelled closes, and finalizes channels from close events.

import { tempo } from 'mppx/server'
 
const result = await tempo.session.superviseCloses({
  account: settlementAccount,
  channelIds,
  client,
  previousBlock,
  settlementSchedule: { amount: 100_000n },
  store,
})

Persist result.block as previousBlock only when result.complete is true. Remove channels reported closed, unrecovered, finalized, or missing from the tracked IDs. Inspect result.failed and result.unrecovered; retry failed channels. The optional settlementSchedule uses raw token units and settles open channels charged outside the Session handler. Use Session.Server.resolveSettlementSchedule to convert a configured schedule to raw units.