Method.tempo.subscription
Recurring stablecoin payments
Creates a Tempo subscription method for server-side activation, access reuse, and renewal.
Activation verifies that the signed Tempo key authorization witness matches the server-issued Challenge ID, preventing reuse across equivalent Challenges.
Anonymous setup carries an authenticated encrypted key token in the Challenge. A successful payment retains the billing key; abandoned Challenges do not create pending key records. A new Challenge can activate an expired subscription for the same payer.
Usage
Creates OUSD-first offers with USDC.e as the mainnet fallback, or pathUSD with testnet: true. Existing subscriptions retain their authorized currency during reuse and renewal.
import { , , } from 'mppx/server'
const = .()
const = .({
: [
.({
: '1.00',
: '1',
: 'week',
: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
: true,
: async ({ }) => {
if (!) return null
return { : `payer:${.}:${.}:plan:pro` }
},
,
: new ('2027-01-01T00:00:00.000Z'),
}),
],
})
export async function (: Request) {
const = await ..({})()
if (. === 402) return .
return .(.({ : 'pro' }))
}With background renewals
Use a durable store and call tempo.renewSubscription from a worker.
import { , , } from 'mppx/server'
const = .()
const = .({
: [
.({
: '1.00',
: ['0x20c0000000000000000000006a37DA5C996874BE'], // OUSD on Tempo
: '1',
: 'week',
: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
: true,
: async ({ }) => {
if (!) return null
return { : `payer:${.}:${.}:plan:pro` }
},
,
: new ('2027-01-01T00:00:00.000Z'),
}),
],
})
await .({
,
: 'sub_abc123',
})With custom activation
Pass activate when your app owns first-period settlement and subscription record creation.
import { , , } from 'mppx/server'
const = .()
const = .({
: [
.({
: async ({ , }) => {
const = new ().()
return {
: {
: 'tempo',
: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
: 'success',
: 'sub_abc123',
,
},
: {
: .,
: ,
: .,
: 0,
: .,
: .,
: .,
: .,
: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
: .,
: 'sub_abc123',
,
},
}
},
: '1.00',
: ['0x20c0000000000000000000006a37DA5C996874BE'], // OUSD on Tempo
: '1',
: 'week',
: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
: async () => ({ : 'user:123:plan:pro' }),
,
: new ('2027-01-01T00:00:00.000Z'),
}),
],
})With cancellation
Mark the stored subscription record with canceledAt. mppx no longer reuses or renews that record, and the next protected request returns a new subscription Challenge. If the client also revokes the Tempo access key, still update the server record so access and billing state stay aligned.
import { } from 'mppx/server'
import { } from 'mppx/tempo'
const = .()
const = .()
export async function (: string) {
const = await .(`user:${}:plan:pro`)
if (!) return false
await .({
...,
: new ().(),
})
return true
}Return type
Returns a non-empty, ordered array of server methods, one per accepted currency. Mppx.create({ methods: [tempo.subscription(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.subscription handler.
Configuration
These parameters configure tempo.subscription().
accessKey (optional)
- Type:
(parameters: { input: Request; request: SubscriptionRequest; resolved: SubscriptionLookup }) => MaybePromise<SubscriptionAccessKey>
Returns the access key to include in the Challenge. Omit this for the recommended path: mppx generates and stores a server-owned access key per resolved subscription key.
account (optional)
- Type:
Account
Account used as the default recipient and local transaction signer.
activate (optional)
- Type:
(parameters: ActivationParameters) => Promise<ActivationResult>
Custom activation hook. It must verify that the access key matches the resolved subscription and return the activated subscription with its Receipt.
activationTimeoutMs (optional)
- Type:
number - Default:
900000
Milliseconds before an in-flight activation lock can be replaced.
amount (optional)
- Type:
string
Default amount to charge per period.
canOffer (optional)
- Type:
Method.CanOfferFn
Returns whether this configured subscription 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.
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.
description (optional)
- Type:
string
Human-readable subscription description.
externalId (optional)
- Type:
string
Application-defined identifier for reconciliation.
feePayerPolicy (optional)
- Type:
Partial<FeePayer.Policy>
Overrides the fee-sponsor limits used for subscription activation and renewal transactions.
Sponsored transactions may carry a keyAuthorization, so the activation transaction can install the subscription access key. maxGas and maxTotalFee bound the sponsor's cost.
Sponsored activation and renewal transactions must have at least 15 seconds of remaining validity when signed and submitted.
getClient (optional)
- Type:
(parameters: { chainId?: number }) => MaybePromise<Client>
Function that returns a viem client for the given Tempo chain ID.
hooks (optional)
- Type:
{ activated?: (parameters) => MaybePromise<void>; renewed?: (parameters) => MaybePromise<void> }
Callbacks that run after activation or renewal commits.
onPaymentSuccess (optional)
- Type:
Method.OnPaymentSuccessFn<typeof tempo.Methods.subscription>
Runs after this subscription 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.
periodCount (optional)
- Type:
string
Number of period units per billing period.
periodUnit (optional)
- Type:
'day' | 'dev_second' | 'week'
Billing period unit. Use dev_second only for development and tests.
recipient (optional)
- Type:
Address
Address that receives subscription payments.
renew (optional)
- Type:
(parameters: { inFlightReference: string; periodIndex: number; subscription: SubscriptionRecord }) => Promise<RenewalResult>
Custom renewal hook. Use this when your app owns period settlement and subscription record updates.
renewalTimeoutMs (optional)
- Type:
number - Default:
900000
Milliseconds before an in-flight renewal lock can be replaced.
requireCredential (optional)
- Type:
boolean
Requires a fresh subscription Credential even when a subscription is active. Use this when access reuse must be bound to the stored payer instead of request metadata.
resolve
- Type:
(parameters: { input: Request; request: SubscriptionRequest; source?: { address: Address; chainId: number } }) => MaybePromise<SubscriptionLookup | null>
Maps a request to a subscription lookup key. With requireCredential: true, return null when source is absent. The SDK automatically generates the initial Challenge access key, verifies the client signature, and calls resolve again with the verified source. Derive the key from that payer. Leave activate and accessKey unset to use this automatic setup.
store (optional)
- Type:
Store.AtomicStore<Record<string, unknown>> - Default:
Store.memory()
Atomic store for access keys, activation locks, renewal locks, and subscription records. Use Subscription.fromStore from mppx/tempo when you need to update subscription records directly, such as marking canceledAt.
The in-memory fallback logs a one-time warning because replay protection applies only within one instance.
storeKeyPrefix (optional)
- Type:
string
Prefix prepended to all subscription store keys. Use distinct prefixes when multiple applications share one store.
subscriptionExpires (optional)
- Type:
string | Date
Maximum authorization expiry. The client authorization cannot outlive this timestamp.
testnet (optional)
- Type:
boolean
Uses Tempo testnet defaults. Testnet chain ID is 42431.
waitForConfirmation (optional)
- Type:
boolean - Default:
true
Whether to wait for activation and automatic renewal transfers to confirm before returning a Receipt.
Request parameters
These parameters configure each mppx.tempo.subscription() call.
accessKey (optional)
- Type:
SubscriptionAccessKey
Access key to authorize. Most apps let the server method generate this value.
amount (optional)
- Type:
string
Amount to charge per billing period.
chainId (optional)
- Type:
number
Tempo chain ID for the subscription.
currency (optional)
- Type:
Address
TIP-20 token address for payments.
decimals (optional)
- Type:
number
Decimal places for amount parsing.
description (optional)
- Type:
string
Human-readable subscription description.
expires (optional)
- Type:
string
Challenge expiry timestamp.
externalId (optional)
- Type:
string
Application-defined identifier for reconciliation.
meta (optional)
- Type:
Record<string, string>
Server-defined correlation data serialized as the Challenge opaque auth-param.
periodCount (optional)
- Type:
string
Number of period units per billing period.
periodUnit (optional)
- Type:
'day' | 'week'
Billing period unit.
recipient (optional)
- Type:
Address
Address that receives payments.
scope (optional)
- Type:
string
Route or resource scope bound into the Challenge metadata.
subscriptionExpires (optional)
- Type:
string | Date
Maximum authorization expiry.
Related
tempo.renewSubscription
Renews an overdue subscription outside the request path. See tempo.renewSubscription.