Accept pay-as-you-go payments
Build a payment-gated photo gallery API that charges $0.01 per photo using mppx sessions. The server returns random photos from Picsum behind a paywall.
How pay-as-you-go sessions work
- Open — Client deposits funds into an on-chain reserve contract, creating a payment channel
- Session — Client signs EIP-712 vouchers with increasing cumulative amounts as service is consumed
- Top up — If the channel runs low, the client deposits additional tokens without closing the channel
- Close — Either party closes the channel, settling the final balance on-chain and refunding unused deposit
Pay-as-you-go server setup
Set up an Mppx session instance
Set up an Mppx instance with the tempo method.
recipientis the address where you receive payments.currencyis the token address for payments (in this case,pathUSD).
import { Mppx, tempo } from 'mppx/server'
const mppx = Mppx.create({
methods: [tempo({
currency: '0x20c0000000000000000000000000000000000000',
recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8',
})],
})Add a pay-as-you-go route
Add payment verification using mppx.session as route middleware. The handler only runs after payment is verified.
import { Mppx, tempo } from 'mppx/nextjs'
const mppx = Mppx.create({
methods: [tempo({
currency: '0x20c0000000000000000000000000000000000000',
recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8',
})],
})
export const GET =
mppx.session({ amount: '0.01', unitType: 'photo' })
(async () => {
const res = await fetch('https://picsum.photos/200/200')
return Response.json({ url: res.url })
})import { Hono } from 'hono'
import { Mppx, tempo } from 'mppx/hono'
const app = new Hono()
const mppx = Mppx.create({
methods: [tempo({
currency: '0x20c0000000000000000000000000000000000000',
recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8',
})],
})
app.get(
'/api/sessions/photo',
mppx.session({ amount: '0.01', unitType: 'photo' }),
async (c) => {
const res = await fetch('https://picsum.photos/200/200')
return c.json({ url: res.url })
},
)import express from 'express'
import { Mppx, tempo } from 'mppx/express'
const app = express()
const mppx = Mppx.create({
methods: [tempo({
currency: '0x20c0000000000000000000000000000000000000',
recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8',
})],
})
app.get(
'/api/sessions/photo',
mppx.session({ amount: '0.01', unitType: 'photo' }),
async (req, res) => {
const response = await fetch('https://picsum.photos/200/200')
res.json({ url: response.url })
},
)import { Mppx, tempo } from 'mppx/server'
const mppx = Mppx.create({
methods: [tempo({
currency: '0x20c0000000000000000000000000000000000000',
recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8',
})],
})
Bun.serve({
async fetch(request) {
const result = await mppx.session({
amount: '0.01',
unitType: 'photo',
})(request)
if (result.status === 402) return result.challenge
const res = await fetch('https://picsum.photos/200/200')
return result.withReceipt(Response.json({ url: res.url }))
},
})Pay-as-you-go client setup
When using sessions from a client, set maxDeposit to enable automatic channel management. This is the maximum amount of tokens the client locks into the payment channel's reserve contract. Any unspent deposit is refunded when the channel closes.
import { Mppx, tempo } from 'mppx/client'
import { privateKeyToAccount } from 'viem/accounts'
const mppx = Mppx.create({
methods: [tempo({
account: privateKeyToAccount('0x...'),
maxDeposit: '1', // Lock up to 1 pathUSD per channel
})],
})
// Each fetch automatically manages the session lifecycle:
// 1st request: opens channel on-chain, sends initial voucher
// 2nd+ requests: sends off-chain vouchers (no on-chain tx)
const res = await fetch('http://localhost:3000/api/sessions/photo')maxDeposit: '1'— Locks up to 1 pathUSD into the payment channel. At $0.01/photo, this covers up to 100 requests before the channel runs out.- The client handles the full session lifecycle automatically: channel open, voucher signing, and retry after
402responses. - If the server sets
suggestedDeposit, the client usesmin(suggestedDeposit, maxDeposit).
Next steps for pay-as-you-go payments
Per-token billing over Server-Sent Events
Framework middleware reference
Complete tempo.session API documentation
Was this helpful?