<!--
Sitemap:
- [MPP — Machine Payments Protocol](/index): MPP (Machine Payments Protocol) is the open standard for machine-to-machine payments—co-developed by Tempo and Stripe. Charge for API requests, tool calls, and content via HTTP 402.
- [Page Not Found](/404)
- [Brand assets and guidelines](/brand): Download official MPP logos, wordmarks, and brand assets. Guidelines for using the Machine Payments Protocol brand in your project or integration.
- [Extensions](/extensions): Community-built tools and integrations for MPP
- [Frequently asked questions](/faq): Answers to common questions about MPP—payment methods, settlement, pricing, security, and how the protocol compares to API keys and subscriptions.
- [Machine Payments Protocol](/overview): MPP standardizes HTTP 402 for machine-to-machine payments. Learn how agents, apps, and services exchange payments in the same HTTP request.
- [Payment methods](/payment-methods/): Available methods and how to choose one
- [Protocol overview](/protocol/): The Machine Payments Protocol standardizes HTTP 402 with an extensible challenge–credential–receipt flow that works with any payment network.
- [Quickstart](/quickstart/): Get started with MPP in minutes. Protect your API with payments, connect your agent, or integrate your app with MPP-enabled services.
- [SDKs and client libraries](/sdk/): Official MPP SDKs in TypeScript, Python, and Rust, plus community SDKs in other languages.
- [Discovery](/advanced/discovery): Advertise your API's payment terms with an OpenAPI discovery document so clients and agents know what endpoints cost before making requests.
- [Identity](/advanced/identity): Use MPP Credentials for access control, rate limiting, and multi-step workflows—without requiring payment.
- [Refunds](/advanced/refunds): Return funds to clients after a charge, or let sessions refund unused deposits automatically.
- [Build with an LLM](/guides/building-with-an-llm): Use llms-full.txt to give your agent complete MPP context.
- [Accept multiple payment methods](/guides/multiple-payment-methods): Accept Tempo stablecoins, Stripe cards, and Lightning Bitcoin on a single API endpoint. Serve a multi-method 402 Challenge and let clients choose.
- [Accept one-time payments](/guides/one-time-payments): Charge per request with a payment-gated API
- [Accept pay-as-you-go payments](/guides/pay-as-you-go): Build a payment-gated API with session-based billing using mppx payment channels. Charge per request with near-zero latency overhead.
- [Create a payment link](/guides/payment-links): Create a payment link for any API endpoint. Share it anywhere—users pay directly from the page, no integration required.
- [Proxy an existing service](/guides/proxy-existing-service): Put a payment gate in front of any API without changing its code. Use the mppx Proxy SDK to charge for upstream access.
- [Accept split payments](/guides/split-payments): Distribute a charge across multiple recipients
- [Accept streamed payments](/guides/streamed-payments): Accept streamed payments over Server-Sent Events with mppx. Bill per token in real time using Tempo payment channels for LLM inference APIs.
- [Charge intent for one-time payments](/intents/charge): Immediate one-time payments
- [Card payment method](/payment-methods/card/): Card payments via encrypted network tokens
- [Custom payment methods](/payment-methods/custom): Build your own payment method
- [Lightning](/payment-methods/lightning/): Bitcoin payments over the Lightning Network
- [Solana](/payment-methods/solana/): Native SOL and SPL token payments
- [Stellar SEP-41 token payments](/payment-methods/stellar/): SEP-41 token payments on the Stellar network
- [Stripe payment method](/payment-methods/stripe/): Cards, wallets, and other Stripe supported payment methods
- [Tempo stablecoin payments](/payment-methods/tempo/): Stablecoin payments on the Tempo blockchain
- [Challenges](/protocol/challenges): Server-issued payment requirements
- [Credentials](/protocol/credentials): Client-submitted payment proofs
- [HTTP 402 payment required](/protocol/http-402): HTTP 402 Payment Required signals that a resource requires payment. Learn when and how MPP servers return 402 with a WWW-Authenticate Challenge.
- [Payment receipts and verification](/protocol/receipts): Receipts confirm successful payment in MPP. Return them in the Payment-Receipt header so clients can verify that the server accepted their Credential.
- [Transports](/protocol/transports/): MPP defines transport bindings for HTTP and MCP. Learn how Challenges, Credentials, and Receipts map to headers and JSON-RPC messages.
- [Use with agents](/quickstart/agent): Connect your coding agent to MPP-enabled services. Set up Tempo Wallet or the mppx SDK to handle 402 payment flows automatically.
- [Use with your app](/quickstart/client): Handle payment-gated resources in your app. Use the mppx client SDK to intercept 402 responses, pay, and retry—all automatically.
- [Add payments to your API](/quickstart/server): Add payment-gated access to your API with mppx. Accept stablecoins, cards, and Bitcoin in a few lines of code using the MPP server SDK.
- [SDK features](/sdk/features): Feature parity across TypeScript, Python, and Rust MPP SDKs.
- [Python SDK](/sdk/python/): The pympp Python library
- [Rust SDK for MPP](/sdk/rust/): The mpp Rust library
- [Getting started](/sdk/typescript/): The mppx TypeScript library
- [Card charge](/payment-methods/card/charge): One-time payments using encrypted network tokens
- [Lightning charge](/payment-methods/lightning/charge): One-time payments using BOLT11 invoices
- [Lightning session](/payment-methods/lightning/session): Pay-as-you-go payments over Lightning
- [Solana charge](/payment-methods/solana/charge): One-time payments on Solana
- [Stellar charge](/payment-methods/stellar/charge): One-time SEP-41 token transfers
- [Channel](/payment-methods/stellar/session): High-frequency off-chain payments
- [Stripe charge](/payment-methods/stripe/charge): One-time payments using Shared Payment Tokens
- [Tempo charge](/payment-methods/tempo/charge): One-time TIP-20 token transfers
- [Session](/payment-methods/tempo/session): Low-cost high-throughput payments
- [HTTP transport](/protocol/transports/http): The HTTP transport maps MPP payment flows to standard HTTP headers—WWW-Authenticate for Challenges, Authorization for Credentials, and Payment-Receipt.
- [MCP and JSON-RPC transport](/protocol/transports/mcp): Payment flows for AI tool calls
- [Python MPP client](/sdk/python/client): Handle 402 responses automatically
- [Core Types](/sdk/python/core): Challenge, Credential, and Receipt primitives
- [Server](/sdk/python/server): Protect endpoints with payment requirements
- [Client](/sdk/rust/client): Handle 402 responses automatically
- [Core types](/sdk/rust/core): Challenge, Credential, and Receipt primitives
- [Server](/sdk/rust/server): Protect endpoints with payment requirements
- [CLI Reference](/sdk/typescript/cli): Built-in command-line tool for paid HTTP requests
- [Method.from](/sdk/typescript/Method.from): Create a payment method from a definition
- [Paid API proxy server](/sdk/typescript/proxy): Paid API proxy
- [McpClient.wrap](/sdk/typescript/client/McpClient.wrap): Payment-aware MCP client
- [stripe client method](/sdk/typescript/client/Method.stripe): Register all Stripe intents
- [Method.stripe.charge](/sdk/typescript/client/Method.stripe.charge): One-time payments via Shared Payment Tokens
- [tempo client method](/sdk/typescript/client/Method.tempo): Register all Tempo intents
- [Method.tempo.charge](/sdk/typescript/client/Method.tempo.charge): One-time payments
- [Method.tempo.session](/sdk/typescript/client/Method.tempo.session): Low-cost high-throughput payments
- [tempo.session](/sdk/typescript/client/Method.tempo.session-manager): Standalone session manager
- [Mppx.create](/sdk/typescript/client/Mppx.create): Create a payment-aware fetch client
- [Mppx.restore](/sdk/typescript/client/Mppx.restore): Restore the original global fetch
- [Transport.from](/sdk/typescript/client/Transport.from): Create a custom transport
- [Transport.http](/sdk/typescript/client/Transport.http): HTTP transport for payments
- [Transport.mcp](/sdk/typescript/client/Transport.mcp): MCP transport for payments
- [BodyDigest.compute](/sdk/typescript/core/BodyDigest.compute): Compute a body digest hash
- [BodyDigest.verify](/sdk/typescript/core/BodyDigest.verify): Verify a body digest hash
- [Challenge.deserialize](/sdk/typescript/core/Challenge.deserialize): Deserialize a Challenge from a header
- [Challenge.from](/sdk/typescript/core/Challenge.from): Create a new Challenge
- [Challenge.fromHeaders](/sdk/typescript/core/Challenge.fromHeaders): Extract a Challenge from Headers
- [Challenge.fromMethod](/sdk/typescript/core/Challenge.fromMethod): Create a Challenge from a method
- [Challenge.fromResponse](/sdk/typescript/core/Challenge.fromResponse): Extract a Challenge from a Response
- [Challenge.meta](/sdk/typescript/core/Challenge.meta): Extract correlation data from a Challenge
- [Challenge.serialize](/sdk/typescript/core/Challenge.serialize): Serialize a Challenge to a header
- [Challenge.verify](/sdk/typescript/core/Challenge.verify): Verify a Challenge HMAC
- [Credential.deserialize](/sdk/typescript/core/Credential.deserialize): Deserialize a Credential from a header
- [Credential.from](/sdk/typescript/core/Credential.from): Create a new Credential
- [Credential.fromRequest](/sdk/typescript/core/Credential.fromRequest): Extract a Credential from a Request
- [Credential.serialize](/sdk/typescript/core/Credential.serialize): Serialize a Credential to a header
- [Expires utility functions](/sdk/typescript/core/Expires): Generate relative expiration timestamps
- [Method.from](/sdk/typescript/core/Method.from): Create a payment method definition
- [Method.toClient](/sdk/typescript/core/Method.toClient): Extend a method with client logic
- [Method.toServer](/sdk/typescript/core/Method.toServer): Extend a method with server verification
- [PaymentRequest.deserialize](/sdk/typescript/core/PaymentRequest.deserialize): Deserialize a payment request
- [PaymentRequest.from](/sdk/typescript/core/PaymentRequest.from): Create a payment request
- [PaymentRequest.serialize](/sdk/typescript/core/PaymentRequest.serialize): Serialize a payment request to a string
- [Receipt.deserialize](/sdk/typescript/core/Receipt.deserialize): Deserialize a Receipt from a header
- [Receipt.from](/sdk/typescript/core/Receipt.from): Create a new Receipt
- [Receipt.fromResponse](/sdk/typescript/core/Receipt.fromResponse): Extract a Receipt from a Response
- [Receipt.serialize](/sdk/typescript/core/Receipt.serialize): Serialize a Receipt to a string
- [Elysia payment middleware](/sdk/typescript/middlewares/elysia): Payment middleware for Elysia
- [Express payment middleware](/sdk/typescript/middlewares/express): Payment middleware for Express
- [Hono payment middleware](/sdk/typescript/middlewares/hono): Payment middleware for Hono
- [Next.js payment middleware](/sdk/typescript/middlewares/nextjs): Payment middleware for Next.js
- [stripe](/sdk/typescript/server/Method.stripe): Register all Stripe intents
- [Method.stripe.charge](/sdk/typescript/server/Method.stripe.charge): One-time payments via Shared Payment Tokens
- [tempo server method](/sdk/typescript/server/Method.tempo): Register all Tempo intents
- [Method.tempo.charge](/sdk/typescript/server/Method.tempo.charge): One-time stablecoin payments
- [Method.tempo.session](/sdk/typescript/server/Method.tempo.session): Low-cost high-throughput payments
- [Mppx.compose](/sdk/typescript/server/Mppx.compose): Present multiple payment options
- [Mppx.create](/sdk/typescript/server/Mppx.create): Create a server-side payment handler
- [Mppx.toNodeListener](/sdk/typescript/server/Mppx.toNodeListener): Adapt payments for Node.js HTTP
- [Request.toNodeListener](/sdk/typescript/server/Request.toNodeListener): Convert Fetch handlers to Node.js
- [Response.requirePayment](/sdk/typescript/server/Response.requirePayment): Create a 402 response
- [Transport.from](/sdk/typescript/server/Transport.from): Create a custom transport
- [Transport.http](/sdk/typescript/server/Transport.http): HTTP server-side transport
- [Transport.mcp](/sdk/typescript/server/Transport.mcp): Raw JSON-RPC MCP transport
- [Transport.mcpSdk](/sdk/typescript/server/Transport.mcpSdk): MCP SDK server-side transport
-->

# Custom \[Build your own payment method]

The `mppx` SDK supports dynamic extensibility for new payment methods. You can implement custom payment methods to integrate any payment rail—other blockchains, card processors, or proprietary systems.

| Approach | Description | Best for |
|---|---|---|
| **[Dynamic extension](#dynamic-extension)** | Define a method inline in your app | Integrating a new payment rail |
| **[First-party SDK](#first-party-sdk)** | Package your method as a standalone npm module | Publishing a reusable method for the ecosystem |

## Dynamic extension

A custom payment method requires three pieces:

1. **Method definition** — Define the method name, intent, and schemas for request parameters and Credential payloads
2. **Client logic** — Create Credentials when the client gets a `402` response
3. **Server logic** — Verify Credentials and return Receipts

### Define a method

Start by defining your payment method with `Method.from`. The definition includes the method name, intent type, and schemas for request parameters and Credential payloads.

```ts twoslash [methods.ts]
import { Method, z } from 'mppx'

const lightning = Method.from({
  intent: 'charge',
  name: 'lightning',
  schema: {
    credential: {
      payload: z.object({
        preimage: z.string(),
      }),
    },
    request: z.object({
      amount: z.string(),
      currency: z.string(),
      invoice: z.string(),
      paymentHash: z.string(),
      recipient: z.string(),
    }),
  },
})
```

### Client implementation

Extend the method with Credential creation logic using `Method.toClient`. The `createCredential` function runs when the client gets a `402` response:

```ts twoslash [methods.client.ts]
import { Credential, Method, z } from 'mppx'

const lightning = Method.from({
  intent: 'charge',
  name: 'lightning',
  schema: {
    credential: {
      payload: z.object({
        preimage: z.string(),
      }),
    },
    request: z.object({
      amount: z.string(),
      currency: z.string(),
      invoice: z.string(),
      paymentHash: z.string(),
      recipient: z.string(),
    }),
  },
})

declare function payInvoice(invoice: string): Promise<{ preimage: string }>
// ---cut---
const clientMethod = Method.toClient(lightning, {
  async createCredential({ challenge }) {
    const result = await payInvoice(challenge.request.invoice)

    return Credential.serialize({
      challenge,
      payload: {
        preimage: result.preimage,
      },
    })
  },
})
```

### Server implementation

Extend the method with verification logic using `Method.toServer`. For Lightning Network, verify that the preimage hashes to the payment hash:

```ts twoslash [methods.server.ts]
import { Method, Receipt, z } from 'mppx'

const lightning = Method.from({
  intent: 'charge',
  name: 'lightning',
  schema: {
    credential: {
      payload: z.object({
        preimage: z.string(),
      }),
    },
    request: z.object({
      amount: z.string(),
      currency: z.string(),
      invoice: z.string(),
      paymentHash: z.string(),
      recipient: z.string(),
    }),
  },
})

declare function bytesToHex(bytes: Uint8Array): string
declare function hexToBytes(hex: string): Uint8Array
declare function sha256(data: Uint8Array): Uint8Array
// ---cut---
const serverMethod = Method.toServer(lightning, {
  async verify({ credential }) {
    const preimage = credential.payload.preimage
    const expectedHash = credential.challenge.request.paymentHash

    const actualHash = bytesToHex(sha256(hexToBytes(preimage)))
    if (actualHash !== expectedHash) {
      throw new Error('Preimage does not match payment hash')
    }

    return Receipt.from({
      method: 'lightning',
      reference: preimage,
      status: 'success',
      timestamp: new Date().toISOString(),
    })
  },
})
```

### Use in your app

#### Client

Pass the client method to `Mppx.create`:

```ts twoslash [client.ts]
import { Credential, Method, z } from 'mppx'
import { Mppx } from 'mppx/client'

const lightning = Method.from({
  intent: 'charge',
  name: 'lightning',
  schema: {
    credential: {
      payload: z.object({
        preimage: z.string(),
      }),
    },
    request: z.object({
      amount: z.string(),
      currency: z.string(),
      invoice: z.string(),
      paymentHash: z.string(),
      recipient: z.string(),
    }),
  },
})

declare function payInvoice(invoice: string): Promise<{ preimage: string }>

const clientMethod = Method.toClient(lightning, {
  async createCredential({ challenge }) {
    const result = await payInvoice(challenge.request.invoice)

    return Credential.serialize({
      challenge,
      payload: {
        preimage: result.preimage,
      },
    })
  },
})
// ---cut---
const { fetch } = Mppx.create({
  methods: [clientMethod],
  polyfill: false,
})

const response = await fetch('https://api.example.com/premium')
```

#### Server

Pass the server method to `Mppx.create`:

```ts twoslash [server.ts]
import { Method, Receipt, z } from 'mppx'
import { Mppx } from 'mppx/server'

const lightning = Method.from({
  intent: 'charge',
  name: 'lightning',
  schema: {
    credential: {
      payload: z.object({
        preimage: z.string(),
      }),
    },
    request: z.object({
      amount: z.string(),
      currency: z.string(),
      invoice: z.string(),
      paymentHash: z.string(),
      recipient: z.string(),
    }),
  },
})

declare function bytesToHex(bytes: Uint8Array): string
declare function hexToBytes(hex: string): Uint8Array
declare function sha256(data: Uint8Array): Uint8Array

const serverMethod = Method.toServer(lightning, {
  async verify({ credential }) {
    const preimage = credential.payload.preimage
    const expectedHash = credential.challenge.request.paymentHash

    const actualHash = bytesToHex(sha256(hexToBytes(preimage)))
    if (actualHash !== expectedHash) {
      throw new Error('Preimage does not match payment hash')
    }

    return Receipt.from({
      method: 'lightning',
      reference: preimage,
      status: 'success',
      timestamp: new Date().toISOString(),
    })
  },
})
// ---cut---
const mppx = Mppx.create({
  methods: [serverMethod],
})
```

### Advanced options

#### Pre-fill defaults

Use `defaults` to pre-fill request parameters so callers don't repeat them. Fields in `defaults` become optional at the call site.

```ts twoslash [methods.server.ts]
import { Method, Receipt, z } from 'mppx'

const lightning = Method.from({
  intent: 'charge',
  name: 'lightning',
  schema: {
    credential: { payload: z.object({ preimage: z.string() }) },
    request: z.object({ amount: z.string(), currency: z.string(), invoice: z.string(), paymentHash: z.string(), recipient: z.string() }),
  },
})
// ---cut---
const serverMethod = Method.toServer(lightning, {
  defaults: {
    currency: 'BTC',
    recipient: 'lnbc1...',
  },
  async verify({ credential }) {
    return Receipt.from({
      method: 'lightning',
      reference: credential.payload.preimage,
      status: 'success',
      timestamp: new Date().toISOString(),
    })
  },
})
```

#### Transform with `z.pipe`

Use `z.pipe` to accept human-readable input and emit a normalized wire format. The built-in `tempo` method uses this to convert dollar amounts to atomic units.

```ts twoslash [methods.ts]
import { Method, z } from 'mppx'
import { parseUnits } from 'viem'

export const charge = Method.from({
  intent: 'charge',
  name: 'acme-pay',
  schema: {
    credential: {
      payload: z.object({ receiptId: z.string() }),
    },
    request: z.pipe(
      z.object({
        amount: z.string(),
        currency: z.string(),
        decimals: z.number(),
        recipient: z.string(),
      }),
      z.transform(({ amount, decimals, ...rest }) => ({
        ...rest,
        amount: parseUnits(amount, decimals).toString(),
      })),
    ),
  },
})
```

Callers pass `{ amount: '1.50', decimals: 6 }`, the Challenge contains `{ amount: '1500000' }`. Use `parseUnits` from viem for decimal-safe conversion—never use `Number()` for monetary amounts.

#### Client context

Declare a `context` schema to accept per-call parameters. The context is validated at runtime before `createCredential` runs.

```ts twoslash [methods.client.ts]
import { Credential, Method, z } from 'mppx'

const lightning = Method.from({
  intent: 'charge',
  name: 'lightning',
  schema: {
    credential: { payload: z.object({ preimage: z.string() }) },
    request: z.object({ amount: z.string(), currency: z.string(), invoice: z.string(), paymentHash: z.string(), recipient: z.string() }),
  },
})

declare function payInvoice(invoice: string, maxFeeSats: number): Promise<{ preimage: string }>
// ---cut---
const clientMethod = Method.toClient(lightning, {
  context: z.object({ maxFeeSats: z.number() }),
  async createCredential({ challenge, context }) {
    const result = await payInvoice(challenge.request.invoice, context.maxFeeSats)
    return Credential.serialize({ challenge, payload: { preimage: result.preimage } })
  },
})
```

#### Request hook

Use the `request` hook to enrich parameters before the Challenge is issued:

```ts twoslash [methods.server.ts]
import { Method, Receipt, z } from 'mppx'

const lightning = Method.from({
  intent: 'charge',
  name: 'lightning',
  schema: {
    credential: { payload: z.object({ preimage: z.string() }) },
    request: z.object({ amount: z.string(), currency: z.string(), invoice: z.string(), paymentHash: z.string(), recipient: z.string() }),
  },
})

declare function createInvoice(amount: string): Promise<{ invoice: string; hash: string }>
// ---cut---
const serverMethod = Method.toServer(lightning, {
  async request({ request }) {
    const result = await createInvoice(request.amount)
    return { ...request, invoice: result.invoice, paymentHash: result.hash }
  },
  async verify({ credential }) {
    return Receipt.from({
      method: 'lightning',
      reference: credential.payload.preimage,
      status: 'success',
      timestamp: new Date().toISOString(),
    })
  },
})
```

#### Respond hook

Use `respond` to return a Response directly after verification, skipping the route handler. Return `undefined` to let the handler run normally.

```ts twoslash [methods.server.ts]
import { Method, Receipt, z } from 'mppx'

const lightning = Method.from({
  intent: 'charge',
  name: 'lightning',
  schema: {
    credential: { payload: z.object({ preimage: z.string() }) },
    request: z.object({ amount: z.string(), currency: z.string(), invoice: z.string(), paymentHash: z.string(), recipient: z.string() }),
  },
})
// ---cut---
const serverMethod = Method.toServer(lightning, {
  async verify({ credential }) {
    return Receipt.from({
      method: 'lightning',
      reference: credential.payload.preimage,
      status: 'success',
      timestamp: new Date().toISOString(),
    })
  },
  respond({ input }) {
    if (input.method === 'POST' && input.headers.get('content-length') === '0') {
      return new Response(null, { status: 204 })
    }
    return undefined
  },
})
```

## First-party SDK

When you want others to use your payment method, package it as a standalone npm module. Users install it and import your method the same way they use `tempo` or `stripe`—a single import gives them both `Mppx` and your method factory.

### Package structure

Organize your SDK with three export paths: root (shared schemas), `./client`, and `./server`. Start with a single intent (`charge`) and add more later.

```
my-method-sdk/
├── src/
│   ├── index.ts              # Re-export shared schemas
│   ├── Methods.ts            # Shared Method.from() definitions
│   ├── client/
│   │   ├── index.ts          # ./client entry point
│   │   └── Charge.ts         # Client charge implementation
│   └── server/
│       ├── index.ts          # ./server entry point
│       └── Charge.ts         # Server charge implementation
├── package.json
└── tsconfig.json
```

### Exports map

Define three entry points in `package.json`. Declare `mppx` as a peer dependency so the user's app shares a single instance.

```json [package.json]
{
  "name": "@my-org/my-method-sdk",
  "type": "module",
  "sideEffects": false,
  "files": ["dist", "src"],
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "default": "./dist/index.js"
    },
    "./client": {
      "types": "./dist/client/index.d.ts",
      "default": "./dist/client/index.js"
    },
    "./server": {
      "types": "./dist/server/index.d.ts",
      "default": "./dist/server/index.js"
    }
  },
  "peerDependencies": {
    "mppx": ">=0.3.15"
  }
}
```

### Shared method definition

Define your schemas once in a shared file. Both client and server import from here.

```ts [src/Methods.ts]
import { Method, z } from 'mppx'

export const charge = Method.from({
  intent: 'charge',
  name: 'my-method',
  schema: {
    credential: {
      payload: z.object({ proof: z.string() }),
    },
    request: z.object({
      amount: z.string(),
      currency: z.string(),
      recipient: z.string(),
    }),
  },
})
```

### Re-export `Mppx`

Re-export `Mppx` (and `Expires`, `Store` on the server) from your entry points so users need only one import:

```ts [src/server/index.ts]
export { charge } from './Charge.js'
export { Mppx, Expires, Store } from 'mppx/server'
```

```ts [src/client/index.ts]
export { charge } from './Charge.js'
export { Mppx } from 'mppx/client'
```

Users get a single-line import:

```ts [server.ts]
import { Mppx, charge } from '@my-org/my-method-sdk/server'

const mppx = Mppx.create({
  methods: [charge({ /* config */ })],
})
```

### Advanced SDK patterns

#### Method namespace

When your SDK supports multiple intents, export a namespace that groups them under a single name. Calling the namespace directly defaults to `charge`.

```ts [src/server/Methods.ts]
import { charge as charge_ } from './Charge.js'
import { session as session_ } from './Session.js'

export function myMethod(parameters: myMethod.Parameters) {
  return myMethod.charge(parameters)
}

export namespace myMethod {
  export type Parameters = charge_.Parameters
  export const charge = charge_
  export const session = session_
}
```

```ts [server.ts]
import { myMethod } from '@my-org/my-method-sdk/server'

myMethod(opts)             // defaults to charge
myMethod.charge(opts)      // explicit charge
myMethod.session(opts)     // explicit session
```

#### Augment the returned method

Use `Object.assign` to attach lifecycle methods (like `cleanup` or `close`) to the method object returned by `Method.toClient` or `Method.toServer`:

```ts [src/client/Charge.ts]
import { Credential, Method } from 'mppx'
import * as Methods from '../Methods.js'

export function charge(parameters: charge.Parameters) {
  let connection: WebSocket | null = null

  const method = Method.toClient(Methods.charge, {
    async createCredential({ challenge }) {
      // ... pay and return credential
    },
  })

  async function cleanup() {
    connection?.close()
  }

  return Object.assign(method, { cleanup })
}
```

### Reference implementations

| SDK | Payment rail | Intents | Source |
|---|---|---|---|
| [`@buildonspark/lightning-mpp-sdk`](https://github.com/buildonspark/lightning-mpp-sdk) | Lightning Network | charge, session | [GitHub](https://github.com/buildonspark/lightning-mpp-sdk) |

## Gotchas

* **Always reject invalid proofs.** Throw an error from `verify` when verification fails. Never return a success Receipt without checking the proof.
* **Use decimal-safe math for amounts.** Use `parseUnits`/`formatUnits` from viem instead of `Number()` or floating-point arithmetic.
* **Verify against the original Challenge.** Always check the Credential's proof against the request fields from the Challenge (amount, currency, recipient). Don't trust the payload alone.
* **Keep secrets server-side.** The Challenge is sent to the client. Don't put API keys, private keys, or other secrets in request fields.
* **Use `methodDetails` for method-specific fields.** Nest non-standard request fields under a `methodDetails` object to avoid collisions with the base schema.
* **Make invoice/order creation idempotent.** The `request` hook runs on both the initial `402` and the Credential submission. Don't generate a new invoice if one already exists for the Challenge.
* **Clean up resources.** If your client method opens WebSocket connections, SDK instances, or listeners, expose a `cleanup()` method so callers can tear them down.

## SDK references

* [`Method.from`](/sdk/typescript/Method.from) — Define a payment method with schemas
* [`Method.toClient`](/sdk/typescript/core/Method.toClient) — Extend a method with client-side Credential creation logic
* [`Method.toServer`](/sdk/typescript/core/Method.toServer) — Extend a method with server-side verification logic
