> ## Documentation Index
> Fetch the complete documentation index at: https://dev-docs.multihopper.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-destination transfers

> Pay several wallets from one route

<Note>
  **Available on devnet.** Production support is rolling out; until then, production requires `recipientWallet` and ignores `destinations`. Send exactly one of the two.
</Note>

One transfer can pay up to **20 wallets**. You pass `destinations` instead of `recipientWallet`, and the route pays each destination its share after the last hop.

It costs noticeably less than one route per recipient. Most of a route's cost is per **hop**, not per destination, so an extra destination adds only its payout transactions and a small, refundable step account.

## How it differs from a single recipient

|             | Single recipient        | Multi-destination                             |
| ----------- | ----------------------- | --------------------------------------------- |
| Request     | `recipientWallet`       | `destinations: [{ wallet, amountRaw }]`       |
| Last hop    | pays the recipient      | a decoy; the destinations are paid afterwards |
| `/prepare`  | no dispersion txs       | adds `dispersionInitTxs`                      |
| `completed` | when the last hop lands | when **every** destination has been paid      |

No destination is written into the route's hop list. Each one is paid from an on-chain commitment after the route finishes.

<Warning>
  This is **not** an anonymity guarantee. Payouts are ordinary on-chain transfers, and anyone
  analysing the chain can observe them. What changes is that the destinations never appear in the
  route's published hop list.
</Warning>

## 1. Estimate

Pass `destinationCount` so the SOL estimate includes the payout accounts:

```bash theme={null}
curl -X POST https://multihopper.com/api/v1/transfers/estimate \
  -H "x-api-key: $MH_KEY" -H "Content-Type: application/json" \
  -d '{ "tokenMint": "So11111111111111111111111111111111111111112",
        "amountRaw": "1000000000", "tokenDecimals": 9, "hops": 3,
        "destinationCount": 2 }'
```

`sol.breakdown.dispersionRentLamports` is the extra rent. It comes back when the step accounts close.

## 2. Create

Each destination's `amountRaw` is its **share of the transfer's `amountRaw`**, and the shares must sum to it exactly. Fees come off the total once, and each destination receives its proportional share of what's left (the hop amount):

```bash theme={null}
curl -X POST https://multihopper.com/api/v1/transfers \
  -H "x-api-key: $MH_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "tokenMint": "So11111111111111111111111111111111111111112",
        "amountRaw": "1000000000", "amountTokens": "1", "tokenDecimals": 9,
        "sourceOwner": "<your wallet>", "hops": 3, "arrivalSeconds": 600,
        "destinations": [
          { "wallet": "7pBqizvE8vQZ29F49NWSu9cho3s7M9SqJyNN1UoQyQ2u", "amountRaw": "600000000" },
          { "wallet": "3UdgSTpcUbiBSmgdPBiVHRMbcDS731kMT6bAQQZgE3c4", "amountRaw": "400000000" }
        ] }'
```

The response lists what each destination will receive. Their amounts sum exactly to `recipientReceivesRaw`:

```json theme={null}
"recipientWallet": null,
"destinationCount": 2,
"recipientReceivesRaw": "993905472",
"destinations": [
  { "wallet": "7pBq…QyQ2u", "amountRaw": "596343283", "status": "pending", "payoutSignature": null, "paidAt": null },
  { "wallet": "3Udg…QZgE3c4", "amountRaw": "397562189", "status": "pending", "payoutSignature": null, "paidAt": null }
]
```

Rules, all checked before anything is created:

* exactly one of `recipientWallet` or `destinations`;
* shares sum to `amountRaw`; wallets are distinct; at most 20;
* **SOL:** each destination must receive at least the rent-exempt minimum after fees, because a payout creates the destination account (`MH_017`);
* **SPL:** needs a program version with SPL payout support on the cluster you're calling, otherwise `MH_017`.

## 3. Prepare and broadcast

`/prepare` returns one extra group, `dispersionInitTxs`. It sets up the payout orchestrator, one committed step per destination, and the route's exit binding. Sign each tx with `sourceOwner` and broadcast in order, **after** `routeInitTxs`:

```
keeperFundingTx → routeInitTxs → orchestratorInitTx → sessionInitTxs → dispersionInitTxs
```

`resume.dispersion` shows how much of the binding is already on chain, and resumed calls omit anything that has landed.

## 4. Confirm

Pass the new signatures as `dispersionInitSignatures`:

```json theme={null}
{ "routeInitSignatures": ["…"], "sessionInitSignatures": ["…"], "dispersionInitSignatures": ["…", "…"] }
```

<Warning>
  `confirm-broadcast` **refuses to start** a multi-destination transfer until the whole binding is on
  chain, and returns `MH_041` with what's missing. That's deliberate: the route's last hop is a
  decoy, so a route that ran without its binding would leave the funds parked. Broadcast whatever
  `/prepare` still returns, then confirm again.
</Warning>

## 5. Track payouts

After the last hop, the keeper pays each destination and records the payout signature. [`GET /transfers/{id}`](/api-reference/transfers/get) shows each destination's `status` (`pending` → `paid`), `payoutSignature` and `paidAt`.

The transfer stays `active` until **every** destination is paid, then becomes `completed`, and `transfer.completed` fires. For webhooks, `completed` means everyone was paid.
