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

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 methodsCompatible client methods
tempo.session() onlytempo.session() or tempo()
tempo.sessionLegacy() onlytempo.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.Server

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

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

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.