Skip to content
LogoLogo

Mppx.create

Create a server-side payment handler

Creates a server-side payment handler from a method.

Usage

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

With multiple methods for one intent

Register multiple methods with the same intent, then use the intent shorthand. mppx.charge() configures every matching charge method and implicitly composes them into one handler.

import Stripe from 'stripe'
import { Mppx, stripe, tempo } from 'mppx/server'
 
const payment = Mppx.create({
  methods: [
    stripe.spt({
      client: new Stripe(process.env.STRIPE_SECRET_KEY!),
      currency: 'usd',
      decimals: 2,
      networkId: 'internal',
      paymentMethodTypes: ['card'],
    }),
    tempo.charge({
      currency: '0x20c0000000000000000000000000000000000000', // pathUSD on Tempo
      decimals: 6,
      recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
    }),
  ],
})
 
const handler = payment.charge({
  amount: '1',
})

Over HTTP, the shorthand applies canOffer and selectOffers, just like payment.compose(). Use Mppx.compose when methods need different request options or one method needs multiple offers.

The Express, Hono, Next.js, and Elysia adapters preserve this composed shorthand, so one mppx.charge() handler returns a Challenge for every available method.

MCP SDK and raw MCP transports also support composed shorthand and instance compose(). They return every available Challenge in one payment-required error, then dispatch the selected Credential to one matching handler.

With application authentication

Set requiresAuth: true when Authorization already carries an application Credential. The server advertises header="Payment-Authorization" in each Challenge, reads the Payment Credential from that field, and leaves Authorization available for Bearer, Basic, or another authentication scheme.

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

Compatible mppx clients follow the field advertised by the Challenge and preserve an existing non-Payment Authorization value on the retry.

Select offers per request

Use selectOffers to filter composed HTTP offers before the server issues Challenges. The hook receives normalized, immutable offer snapshots and a clone of the incoming request.

import { , ,  } from 'mppx/server'
 
const  = '0x20c0000000000000000000000000000000000000'
const  = .({
  : '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
})
const  = .({
  : 2,
  : 'acct_1234',
  : ['card'],
  : 'sk_live_...',
})
 
const  = .({
  : [, ],
  (, {  }) {
    if (!new (.)..('/stablecoin')) return 
    return .(({  }) => . === 'tempo')
  },
})
 
const  = .(
  [, { : '1', :  }],
  [, { : '1', : 'usd' }],
)

Return at least one offer from the input array, preserve its order, and don't return duplicates. mppx runs each method's canOffer hook before selectOffers. Successfully matched Credentials bypass both hooks, so clients can redeem Challenges that the server already issued.

With custom transport

Use a custom transport for non-HTTP environments like MCP servers.

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

With payment hooks

Register hooks on the returned payment instance to observe Challenges, successful payments, and failures.

import { ,  } from 'mppx/server'
 
const  = .({
  : [.()],
})
 
.(({ ,  }) => {
  .('challenge created:', ., .)
})
 
.(({ ,  }) => {
  .('payment failed:', ., .)
})
 
.(({ , ,  }) => {
  .('payment success:', ., ., ?.)
})

Return type

import type { Method } from 'mppx'
import type { Mppx, Transport } from 'mppx/server'
 
type ReturnType = Mppx<[Method.Server], Transport.Http>

The returned object includes intent functions (for example, charge), broadcastCredential, challenge, compose, payment hooks, validateCredential, and verifyCredential. An intent function calls its only matching method directly or implicitly composes every matching method.

Payment-success hooks receive the canonical request included in the Challenge and an optional requestInput. requestInput contains the resolved server-side method input before request-schema output transforms, including fields intentionally omitted from the Challenge. Both values are immutable snapshots. Standalone Credential verification omits requestInput when you don't pass route options.

Validate and broadcast Credentials

Use validateCredential to pre-check a Credential without consuming payment state. Use broadcastCredential to validate again and complete the payment. verifyCredential remains as a deprecated alias for the mutating broadcast operation.

import { ,  } from 'mppx/server'
 
const  = .({ : [.()] })
 
const  = await .('Payment credential="..."')
.(.)
 
const  = await .('Payment credential="..."')
.(.)
success

Parameters

attestation (optional)

  • Type: Readonly<Record<string, Attestation.Verifier>>

Request-attestation verifiers required before an HTTP handler issues a Challenge or accepts a Credential. Invalid attestations return 401; absent or unverified attestations return 403. Every configured verifier must succeed. See Identity.

methods

  • Type: readonly Method.Server[]

Array of payment methods (for example, [tempo.charge()]).

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

realm (optional)

  • Type: string
  • Default: Auto-detected from environment variables (MPP_REALM, FLY_APP_NAME, HEROKU_APP_NAME, HOST, HOSTNAME, RAILWAY_PUBLIC_DOMAIN, RENDER_EXTERNAL_HOSTNAME, VERCEL_URL, WEBSITE_HOSTNAME), falling back to "MPP Payment".

Server realm (for example, hostname). Auto-detected from common platform environment variables. Set explicitly to override.

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

requiresAuth (optional)

  • Type: boolean
  • Default: false

Uses Payment-Authorization for Payment Credentials so Authorization remains available for application authentication. Available with the HTTP transport.

secretKey (optional)

  • Type: string
  • Default: Auto-detected from MPP_SECRET_KEY environment variable. Throws if neither provided nor set.

Secret key for HMAC-bound Challenge IDs. Enables stateless verification—the server verifies that a Challenge was issued by itself without storing state. Treat it as root-of-trust material: store it in your secret manager, keep it server-side, never log it, and rotate it immediately if it is exposed. See Security.

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

selectOffers (optional)

  • Type: (offers: readonly ServerOffer[], context: { request: Request }) => MaybePromise<readonly ServerOffer[]>

Selects the composed HTTP offers available for the incoming request. Each offer includes its canonical key, method name, method intent, optional method alias, and schema-normalized request. Offer objects and their nested request values are immutable. The incoming request is cloned when its body is available; otherwise, the hook receives a bodyless copy with the same metadata.

The hook applies only to HTTP transport and runs before Challenge generation. Return a non-empty, ordered subset of the original offer objects.

transport (optional)

  • Type: Transport
  • Default: Transport.http()

Transport to use for handling payment requests.

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