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

# Atomic batch

> Settle multiple transfers all-or-nothing in one Stellar transaction with a single signature.

`sendCalls([...])` settles N calls **all-or-nothing in one transaction**, authorized with a
**single** signature. It is the EIP-5792-style alias of `pay` - same input shape, same
`Receipt` - with the atomicity guarantee made explicit.

## Quick example

```ts theme={null}
// Recipe 10 - ATOMIC BATCH (sendCalls). N USDC transfers settle all-or-nothing in ONE tx via the
// pinned Multicall router's `batch_transfer` - the smart account (or classic wallet) authorizes the
// whole batch with a SINGLE signature. A batch of 1 is exactly pay([call]).
import {
  createBuckspayClient,
  batch,
  MAX_BATCH_CALLS,
  BuckspayError,
  type BuckspayConfig
} from "@buckspay/core";
import { ozContractAccount } from "@buckspay/accounts/oz-contract";
import { passkey } from "@buckspay/signers/passkey";
import { buckspayFacilitator } from "@buckspay/relayer/buckspay-facilitator";

const SPONSOR_G = "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN";
const USDC_SAC = "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA";
const A = "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5";
const B = "GBPYQYRH62E6NLRGXHBT4I3ZPTEHVBQMYTSH44YLOAPTCDYNAXDOLJRY";

export const batchConfig: BuckspayConfig = {
  network: "testnet",
  account: ozContractAccount({ network: "testnet", sponsorAddress: SPONSOR_G }),
  signer: passkey({ rpId: "localhost", rpName: "buckspay" }),
  relayer: buckspayFacilitator({ url: "/api/gasless", network: "testnet" }),
  gas: { mode: "sponsored" }
};

export const client = createBuckspayClient(batchConfig);

export async function payManyAtomically(): Promise<void> {
  await client.connect();
  // Collect calls with the pure builder (enforces MAX_BATCH_CALLS on build()).
  const calls = batch()
    .add(client.transfer({ token: USDC_SAC, to: A, amount: "1.00" }))
    .add(client.transfer({ token: USDC_SAC, to: B, amount: "2.50" }))
    .build();

  // sendCalls = atomic, all-or-nothing. Either BOTH transfers land or NEITHER does.
  const receipt = await client.sendCalls(calls);
  console.log(receipt.transferTx); // one settlement tx for the whole batch
}

export async function guardOversizeBatch(): Promise<void> {
  const tooMany = Array.from({ length: MAX_BATCH_CALLS + 1 }, () =>
    client.transfer({ token: USDC_SAC, to: A, amount: "0.01" })
  );
  try {
    await client.sendCalls(tooMany);
  } catch (e) {
    if (e instanceof BuckspayError && e.code === "BATCH_TOO_LARGE") {
      console.error(`batch capped at ${MAX_BATCH_CALLS} calls`);
      return;
    }
    throw e;
  }
}
```

## The `batch()` builder

`batch()` is a pure, fluent builder that collects calls and enforces the size cap on
`build()`:

```ts theme={null}
import { batch, MAX_BATCH_CALLS } from "@buckspay/core";

const calls = batch()
  .add(client.transfer({ token: USDC_SAC, to: A, amount: "1.00" }))
  .add(client.transfer({ token: USDC_SAC, to: B, amount: "2.50" }))
  .build(); // throws BATCH_TOO_LARGE if > MAX_BATCH_CALLS

const receipt = await client.sendCalls(calls);
```

You can also pass an array directly to `sendCalls([...])` - the builder is optional.

## Native mechanism

The atomic unit is always the **transaction**; partial application is impossible at the
protocol level - not enforced by retry logic in the SDK.

| Account type      | Mechanism                                                                                                                          |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Classic (`G...`)  | Multi-operation Stellar transaction: operations share one envelope, so all land or none do.                                        |
| Contract (`C...`) | The pinned Multicall router's `batch_transfer`: the smart account authorizes the whole batch in `__check_auth` with one signature. |

## Size cap

`MAX_BATCH_CALLS` is **16**. `batch().build()` and `sendCalls` directly both reject an
oversized batch with `BuckspayError("BATCH_TOO_LARGE")`. Keep batches bounded so a single
authorization entry stays within simulation and fee limits.

## Parity invariant

A **batch of one is byte-identical to the single-call entry** produced by `pay([call])`.
Batching adds a wrapper only for N > 1; it never changes the one-call path. This invariant
is pinned by a regression golden so the core path cannot drift.

<Note>
  **Throughput for independent payments.** Settling many independent payments fast is a
  separate concern from atomic batching. The facilitator spreads independent calls across a
  pool of channel accounts with non-sequential nonces - transparently, with no API change
  to how you call `pay`.
</Note>

## Next

<CardGroup cols={2}>
  <Card title="Sessions" href="/features/sessions/overview">
    Grant scoped session keys so users can pay within fixed limits without a root prompt per action.
  </Card>

  <Card title="Gas in token" href="/features/gas-in-token">
    Let users pay transaction fees in USDC instead of XLM.
  </Card>
</CardGroup>
