Skip to content
LogoLogo

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.

tempo.renewSubscription

Renews an overdue subscription outside the request path. See tempo.renewSubscription.