Multi-send

Multi-send: batch payouts to many addresses

A CSV of recipients becomes a run of independent orders — cross-chain, each in the asset that recipient wants. One recipient failing a provider's minimum does not hold up the other four hundred.

  • Per recipientown quote, address and status
  • Cross-chaineach recipient's own asset
  • Failure modelper recipient, never the whole run
  • RetriesIdempotency-Key, never a double send

Built on/v1/validate-address/v1/partner/quotes/v1/partner/orders

A batch is not one transaction — it is many independent orders that happen to start together. Each recipient gets their own quote, their own deposit address and their own status, so a payout list can cross chains and assets freely, and one bad line never holds up the rest.

  • Recipients can be paid in different assets and networks in the same run.
  • Failures are per recipient: below-minimum amounts are refused at creation, before money moves.
  • Idempotency keys make the whole run safe to re-execute after a crash.
  • There is no on-chain multisend contract involved, and no pooled balance anywhere.

From a CSV to a finished run

  1. Validate the list before you price it

    Every row is address, asset, network, amount. Run each one through GET /v1/validate-address — a keyless call — and reject the file rather than the payout. known:false means the network has no pattern to check against, which is "unverified", not "valid".

  2. Quote every row

    One quote per recipient, by the amount they should receive (amount_to). The sum of the returned FromAmount values is what the run will cost you — approve it before anything is created.

  3. Create the orders, a few at a time

    Five to ten concurrent creations is the right pace: providers throttle bursts, and each order must be created and funded inside the offer's five-minute life. Every creation carries an Idempotency-Key built from the run id and the row.

  4. Fund each order

    Each one has its own DepositAddress and exact AmountFrom. This is where the network fees live: a hundred recipients is a hundred outgoing transfers from your wallet.

  5. Collect the results

    Poll each order to a terminal status and write the run's report: DONE with TxID, or a reason and a row to retry.

The fan-out

import pLimit from "p-limit";
const limit = pLimit(8);                      // providers dislike bursts

const results = await Promise.all(rows.map((row, i) => limit(async () => {
  try {
    const order = await createPayout(runID, row, i);   // quote → POST /orders
    return { row: i, ok: true, id: order.ID, send: order.AmountFrom,
             to: order.DepositAddress };
  } catch (e) {
    return { row: i, ok: false, error: String(e) };    // one row, not the run
  }
})));

const payable = results.filter((r) => r.ok);
const rejected = results.filter((r) => !r.ok);         // show these before funding

The idempotency key is what makes the run restartable. Derive it from the run and the row, never from the clock:

const key = `${runID}:${row.address}:${row.amount}`;  // stable across retries
// POST /v1/partner/orders with header "Idempotency-Key: "
// A repeat returns the same order — a crashed run resumes instead of duplicating.

When a row fails

What you seeWhyWhat to do
400 amount below minimumThe row is under that provider's MinFromAmount for the pair.Check MinFromAmount at quote time; batch small amounts into fewer, larger payouts, or use a different asset.
400 offer expiredMore than five minutes passed between quoting and creating.Re-quote that row. Keep quote → create → fund tight.
400 provider is disabledThe exchange went offline after the quote.Re-quote; the next offer comes from another provider.
No offers at allNo provider serves that pair right now.Pay that recipient in an asset that /v1/assets lists.
TIME_EXPIRED after funding was skippedThe deposit never arrived in the provider's window.Re-quote and re-create; nothing was sent, so nothing is lost.
REFUNDEDThe swap failed after funding.The provider returns the coins to refund_address — always set it to your own wallet.

How this differs from an on-chain multisend

Multisend contractSwapZilla batch
ChainsOne, the contract's ownAny pair the providers cover, mixed freely in a run
AssetsThe one you already holdEach recipient's own — converted on the way
TransactionsOne, cheap per recipientOne deposit per recipient — the cost of not pooling funds
CustodyNoneNone
FailureUsually all-or-nothingPer recipient, with a reason

Not included, and worth planning around: there is no single batch endpoint, no one funding transaction, no webhooks, and no fiat leg. A run is a loop your side drives — the API makes each iteration safe.

What people fan out

  • Affiliate and referral payouts — hundreds of small amounts, each to whichever asset the partner asked for.
  • Creator and bounty programmes, where the payout list changes every cycle and the addresses are user-supplied.
  • Airdrops and rewards that must land in the recipient's preferred network rather than yours.
  • Refund and compensation runs, reconciled row by row with a TxID against each one.

Questions

Is there a single endpoint that takes a list of recipients?

No. A batch is created as one order per recipient — the API has no bulk endpoint. In practice this is what makes mixed-asset, mixed-network runs possible at all, and it means each recipient carries their own status and their own failure reason.

Can I send to a hundred addresses on different chains in one run?

Yes. Each order names its own destination asset and network, so a run can settle TRC20 USDT, BTC and ETH side by side from the same source asset. What is shared is only your run id, which you set as client_order_id on each order.

What stops a crashed run from paying someone twice on restart?

A stable Idempotency-Key per row — derived from the run id and the recipient, not from the time. Repeating a creation with the same key returns the order that already exists instead of opening a second one.

How many orders can I create at once?

SwapZilla applies no rate limit, but providers throttle bursts of quoting, so eight to ten concurrent rows is a comfortable pace. The real constraint is the five-minute offer expiry: each row has to be quoted, created and funded inside it.

Who pays the network fees?

You pay the fee on each deposit you send, and the provider's payout fee is already reflected in the offer's ToAmount — which is why quoting by amount_to is the reliable way to make a recipient's figure exact.

Can I see the whole run afterwards?

GET /v1/partner/orders?page=1&limit=100 lists your orders newest first, paged, and each carries the client_order_id you set — so the run reassembles from your own prefix. Individual orders can also be refreshed synchronously with POST /v1/partner/orders/{id}/refresh.

Getting a key

Register in the partner cabinet and your partner id and the api_key that goes in X-API-Key are issued at once. The key starts working once it is activated: write to @swapzilla_support_bot with a sentence about what you are building. Every endpoint, field and status is in the partner API reference.