Skip to content
LogoLogo

Method.tempo.charge

One-time stablecoin payments

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}`)

The charge intent for the Tempo payment method. Requests a one-time payment from the client.

Non-zero charges verify on-chain transfers. Zero-amount charges verify a proof payload signed by the client's identity key and return a Receipt without broadcasting a transaction.

Usage

import { ,  } from 'mppx/server'
 
const  = .({ : [.()] })
 
export async function (: Request) {
  const  = await .({
    : '0.1',
    : '0x20c0000000000000000000000000000000000000',
    : '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })()
 
  if (. === 402) return .
  return .(.({ : '...' }))
}

With expiry

Set a custom expiration time for the charge using the expires option.

import {  } from 'mppx'
 
export async function (: Request) {
  const  = await .({
    : '0.1',
    : '0x20c0000000000000000000000000000000000000',
    : .(10), 
    : '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })()
 
  if (. === 402) return .
  return .(.({ : '...' }))
}

With description

Add a human-readable description for the payment request.

export async function (: Request) {
  const  = await .({
    : '0.1',
    : '0x20c0000000000000000000000000000000000000',
    : 'API access for /resource', 
    : '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })()
 
  if (. === 402) return .
  return .(.({ : '...' }))
}

With a custom fee payer policy

Override the local fee-sponsor limits when you co-sign charge transactions.

import { ,  } from 'mppx/server'
import {  } from 'viem/accounts'
 
const  = .({
  : [
    .({
      : (
        '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
      ),
      : {
        : 50_000_000_000n,
        : 100_000_000_000_000_000n,
      },
    }),
  ],
})

With a custom sponsor budget

Limit aggregate fee exposure across sponsored transactions. Use a shared AtomicStore when more than one process uses the fee payer.

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

With Tempo API relay

Delegate Tempo charge validation and finalization to Tempo API. The relay preserves this method's Challenge configuration and needs an API key with the mpp:write scope. It broadcasts pull Credentials and recognizes push Credentials as already broadcast.

import { ,  } from 'mppx/server'
 
const  = .({
  : [
    .({
      : {
        : ..!,
      },
    }),
  ],
})

See Relays for the lifecycle, custom API base URLs, and custom validate and broadcast hooks.

With replay protection for zero-dollar auth

Pass store when you want zero-dollar proof Credentials to be single-use.

import { , ,  } from 'mppx/server'
 
const  = .()
 
const  = .({
  : [
    .({
      : ,
    }),
  ],
})
 
export async function (: Request) {
  const  = await .({
    : '0',
    : '0x20c0000000000000000000000000000000000000',
    : '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })()
 
  if (. === 402) return .
  return .(.({ : '...' }))
}

Return type

Returns a function that accepts a Request and returns a response object with payment status.

type ReturnType = (request: Request) => Promise<
  | { status: 402; challenge: Response }
  | { status: 200; withReceipt: <T>(response: T) => T }
>

Configuration

These parameters configure the tempo.charge() constructor.

canOffer (optional)

  • Type: Method.CanOfferFn

Returns whether this configured charge 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.

decimals (optional)

  • Type: number
  • Default: 6

Decimal places for amount parsing.

externalId (optional)

  • Type: string

External identifier for the payment.

feePayer (optional)

  • Type: Account | string | true

Account or URL for sponsoring transaction fees. Pass a viem Account to co-sign locally, a URL string to delegate to a remote fee payer service, or true when the account parameter doubles as the fee payer.

This setting only applies to non-zero charges. Zero-amount proof flows do not create a transaction.

feePayerPolicy (optional)

  • Type: Partial<{ maxFeePerGas: bigint; maxGas: bigint; maxInFlightReservations: number; maxInFlightTotalFee: bigint; maxPriorityFeePerGas: bigint; maxTotalFee: bigint; maxValidityWindowSeconds: number }>

Override the local fee-sponsor policy used when the server co-signs Tempo charge transactions. Remote fee payer services enforce their own policy.

mppx resolves defaults per chain automatically. On mainnet (4217), the defaults are maxFeePerGas: 100_000_000_000n, maxGas: 2_000_000n, maxPriorityFeePerGas: 10_000_000_000n, maxTotalFee: 50_000_000_000_000_000n, and maxValidityWindowSeconds: 900. On Moderato (42431), maxPriorityFeePerGas increases to 50_000_000_000n and the other limits stay the same.

The deprecated maxInFlightReservations and maxInFlightTotalFee properties still work here. Configure them with sponsorBudget instead.

import { ,  } from 'mppx/server'
import {  } from 'viem/accounts'
 
const  = .({
  : [
    .({
      : (
        '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
      ),
      : {
        : 50_000_000_000n,
        : 100_000_000_000_000_000n,
      },
    }),
  ],
})

getClient (optional)

  • Type: (parameters: { chainId?: number }) => MaybePromise<Client>

Function that returns a viem client for the given chain ID. Overrides the default RPC configuration.

memo (optional)

  • Type: string

On-chain memo for the transaction.

onPaymentSuccess (optional)

  • Type: Method.OnPaymentSuccessFn<typeof tempo.Methods.charge>

Runs after this Tempo charge succeeds. The hook receives the normalized request, its Receipt, and the HTTP input when available. Errors don't change payment handling.

relay (optional)

  • Type: { apiBaseUrl?: string; apiKey: string; fetch?: typeof globalThis.fetch }

Delegates Tempo charge Credential validation and finalization to a compatible MPP relay. apiKey needs the mpp:write scope. Defaults to https://api.tempo.xyz; set apiBaseUrl for a compatible relay, including one behind a path prefix. The relay broadcasts pull Credentials and verifies push Credential hashes without broadcasting them again.

sponsorBudget (optional)

  • Type: { maxInFlightReservations?: number; maxInFlightTotalFee?: bigint; store?: Store.AtomicStore } | false
  • Default: {}

Limits aggregate declared fee exposure for sponsored transactions. maxInFlightReservations defaults to 100, and maxInFlightTotalFee defaults to ten times feePayerPolicy.maxTotalFee. Both values must be greater than zero.

The store defaults to the charge method's store and must be shared by every process using the sponsor. Budget keys don't use storeKeyPrefix, so every tenant and process sees the sponsor-wide capacity.

Set false only when another system enforces the aggregate sponsor budget.

store (optional)

  • Type: Store.AtomicStore

Pass a store when you want replay protection for charge Credentials. A Store provides async key-value operations (get, put, delete). An AtomicStore extends Store with an atomic update(key, fn) method and an optional optimized tryClaim(key, expires) operation.

For non-zero charges, mppx falls back to an in-memory store when you omit this parameter. For zero-dollar proof auth, replay prevention is disabled unless you pass a store.

mppx claims transaction hashes and proof Challenge IDs through the Challenge expiration time. It calls Store.tryClaim, which uses the store's optimized operation when present and otherwise falls back to update.

Use Store.memory() for local development, tests, or a single long-lived server process. For multi-instance deployments, use Store.redis(), Store.upstash(), or Store.cloudflare(). All built-in factories return AtomicStore—for custom backends, provide an update function alongside get, put, and delete.

testnet (optional)

  • Type: boolean

Testnet mode. Defaults the chain ID to 42431 (Tempo testnet).

waitForConfirmation (optional)

  • Type: boolean
  • Default: true

Whether to wait for the charge transaction to confirm on-chain before responding. When false, the transaction is simulated via eth_estimateGas and broadcast without waiting for inclusion. The Receipt optimistically reports status: 'success' based on simulation alone.

This option applies only to non-zero charges. Zero-amount proof flows return immediately after signature verification.

import { ,  } from 'mppx/server'
 
const  = .({
  : [.({
    : false, 
  })],
})

Request parameters

amount

  • Type: string

Payment amount in human-readable units. For example, '0.1' represents $0.10 USD.

Set '0' to require identity-only zero-dollar auth. In that case, the client submits a proof payload instead of a transaction or hash.

By default, zero-dollar proof Credentials remain reusable until the Challenge expires. Pass store to treat proofs as single-use across the scope of that store.

currency

  • Type: string

TIP-20 token address for the payment currency.

description (optional)

  • Type: string

Human-readable description of the payment request.

expires (optional)

  • Type: string
  • Default: 5 minutes from now

ISO 8601 timestamp for when the payment Challenge expires.

meta (optional)

  • Type: Record<string, string>

Server-defined correlation data. mppx serializes it as the base64url-encoded opaque auth-param on the Challenge, and clients echo that same string back in the Credential.

recipient

  • Type: string

Address to receive the payment.

scope (optional)

  • Type: string

Route or resource scope bound into the Challenge metadata. Use this to prevent a Credential issued for one route from being replayed against another route with the same payment terms.

splits (optional)

  • Type: Array<{ amount: string; memo?: string; recipient: string }>

Split the charge across additional recipients. Each entry specifies an amount (in human-readable units) and a recipient address. The primary recipient receives amount minus the sum of all split amounts.

ConstraintValue
Array length1–10
Each split amountMust be > 0
Sum of splitsMust be strictly less than amount
Split memoOptional, 32-byte hex hash
export async function (: Request) {
  const  = await .({
    : '1.00',
    : '0x20c0000000000000000000000000000000000000', // pathUSD
    : '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // seller
    : [ 
      { : '0.10', : '0x70997970C51812dc3A010C7d01b50e0d17dc79C8' }, // platform fee
    ], 
  })()
 
  if (. === 402) return .
  return .(.({ : '...' }))
}