> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://mpp.dev/api/mcp` to find what you need.

# Accept split payments \[Distribute a charge across multiple recipients]

## Choose a signing account

### Direct

Create a server-only wallet.ts module, then import account wherever an example creates a local signing account.

```ts
import { privateKeyToAccount } from 'viem/accounts'

export const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
```

### Privy

Create an EVM wallet in Privy, fund it with the required currency on this page's network, and keep PRIVY\_APP\_SECRET server-side.

```bash
pnpm add @privy-io/node
```

Create a server-only privy.ts module, then import its account wherever an example configures account or feePayer.

```ts
import { PrivyClient } from '@privy-io/node'
import { createViemAccount } from '@privy-io/node/viem'

const privy = new PrivyClient({
  appId: process.env.PRIVY_APP_ID!,
  appSecret: process.env.PRIVY_APP_SECRET!,
})

export const account = createViemAccount(privy, {
  address: process.env.PRIVY_WALLET_ADDRESS as `0x${string}`,
  walletId: process.env.PRIVY_WALLET_ID!,
})
```

createViemAccount delegates signatures to the Privy wallet, so it replaces any local viem account in the examples on this page.

Split a single charge across multiple recipients in one atomic transaction. The primary recipient receives the remainder after all splits are deducted.

Split payments are useful for:

* **Marketplaces** — route a platform fee to yourself and the rest to the seller
* **Referral programs** — pay a bounty to the referrer on every purchase
* **Revenue sharing** — distribute earnings across partners or contributors

## How it works

When you add `splits` to a charge, the SDK constructs multiple on-chain transfers in a single transaction:

1. Each split recipient receives their declared amount
2. The primary `recipient` receives `amount - sum(splits)`
3. The server verifies all transfers atomically

:::info
Split amounts are in human-readable units, the same as the top-level `amount`. The primary recipient's share is always implicit — you only declare the splits.
:::

## Server

Add a `splits` array to any `mppx.charge` call. Each entry specifies a `recipient` and `amount`.

```ts twoslash
import { Mppx, tempo } from 'mppx/server'

const mppx = Mppx.create({ methods: [tempo.charge()] })
// ---cut---
export async function handler(request: Request) {
  const result = await mppx.charge({
    amount: '1.00',
    currency: '0x20c0000000000000000000000000000000000000', // pathUSD
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // seller
    // [!code hl:start]
    splits: [
      {
        amount: '0.10',
        recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // platform fee
      },
    ],
    // [!code hl:end]
  })(request)

  // seller receives $0.90, platform receives $0.10
  if (result.status === 402) return result.challenge
  return result.withReceipt(Response.json({ data: '...' }))
}
```

### With per-split memos

Each split can carry its own on-chain memo for reconciliation:

```ts twoslash
import { Mppx, tempo } from 'mppx/server'

const mppx = Mppx.create({ methods: [tempo.charge()] })

declare const request: Request
// ---cut---
const result = await mppx.charge({
  amount: '1.00',
  currency: '0x20c0000000000000000000000000000000000000', // pathUSD
  memo: '0x6f726465722d313233', // order-123
  recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // seller
  splits: [
    {
      amount: '0.10',
      memo: '0x706c6174666f726d2d666565', // platform-fee // [!code hl]
      recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // platform
    },
  ],
})(request)
```

### With fee sponsorship

Split payments work with [fee sponsorship](/payment-methods/tempo#fee-sponsorship). The server co-signs the multi-transfer transaction so the client doesn't need gas tokens.

```ts twoslash
import { Mppx, tempo } from 'mppx/server'

const mppx = Mppx.create({ methods: [tempo.charge()] })

declare const request: Request
// ---cut---
const result = await mppx.charge({
  amount: '1.00',
  currency: '0x20c0000000000000000000000000000000000000', // pathUSD
  feePayer: true, // [!code hl]
  recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // seller
  splits: [
    { amount: '0.05', recipient: '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC' }, // referrer
    { amount: '0.10', recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8' }, // platform
  ],
})(request)
```

## Client

The client SDK handles split payments automatically — no client-side configuration is needed. When the server includes `splits` in the Challenge, the client constructs the matching multi-transfer transaction.

### Validating split recipients

Use `expectedRecipients` to restrict which split recipients the client signs for. This prevents a compromised server from redirecting funds to unexpected addresses.

#### Accounts SDK

```ts twoslash
import { Provider } from 'accounts'
import { Mppx, tempo } from 'mppx/client'

const provider = Provider.create({ mpp: false }) // Avoid double 402 handling; mppx is configured below.
await provider.request({ method: 'wallet_connect' })
// ---cut---
Mppx.create({
  methods: [
    tempo.charge({
      account: provider.getAccount({ signable: true }),
      // [!code hl:start]
      expectedRecipients: [
        '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // platform
        '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', // referrer
      ],
      // [!code hl:end]
      getClient: provider.getClient,
    }),
  ],
})
```

#### viem

```ts twoslash
import { Mppx, tempo } from 'mppx/client'
import { privateKeyToAccount } from 'viem/accounts'

const account = privateKeyToAccount('0xabc…123')
// ---cut---
Mppx.create({
  methods: [
    tempo.charge({
      account,
      // [!code hl:start]
      expectedRecipients: [
        '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // platform
        '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', // referrer
      ],
      // [!code hl:end]
    }),
  ],
})
```

If the server sends a Challenge with a split recipient not in the allowlist, the client throws an error instead of signing.

## Constraints

| Rule | Limit |
|------|-------|
| Splits per charge | 1–10 |
| Each split amount | Must be > 0 |
| Sum of all splits | Must be strictly less than `amount` |
| Split memo | Optional, 32-byte hex hash |

## Next steps

[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) — Session-based billing with payment channels

[Server quickstart](/quickstart/server) — Learn how to charge for resources
