# Batch and Bulk Signing

Sign several transactions with one approval, or submit an atomic XLS-56 Batch.

Joey supports two different ways to handle several transactions at once. Don't confuse them:

| | Bulk (`signTransactionBulk`) | XLS-56 `Batch` |
|---|---|---|
| What it is | Up to 32 separate, ordinary transactions approved together | One ledger transaction that carries 2–8 inner transactions |
| Atomic? | **No.** Earlier transactions stay applied if a later one fails. | According to the batch mode flag, for example all-or-nothing |
| How to send | `joey.signTransactionBulk({ tx_list, submit })` | `signTransaction` or `signAndSubmitTransaction` with `TransactionType: 'Batch'` |
| Availability | All versions | When `joey.supportsBatch()` is `true` (SDK 0.5.0+) |

## Bulk signing

```ts
const results = await joey.signTransactionBulk({
  tx_list: [{ tx_json: trustSet }, { tx_json: payment }],
  submit: true,
})
```

- **One approval and one password** cover the whole list. The user can't approve some entries and reject others. With a Ledger account, each transaction is still confirmed on the device.
- **`submit: true`** signs everything first, then submits strictly in order. **`submit: false`** returns the signed blobs for you to submit.
- **Autofill** reads the ledger once and assigns consecutive `Sequence` numbers, with staggered `LastLedgerSequence` values. Any `Sequence` you set yourself is kept.

### Partial failure

If an entry fails during submission, the call rejects. The error's `data` describes every entry:

```ts
import type { JoeyRpcError, SignTransactionBulkFailure } from '@joeywallet/wallet-sdk'

try {
  await joey.signTransactionBulk({ tx_list, submit: true })
} catch (e) {
  const data = (e as JoeyRpcError).data as SignTransactionBulkFailure | undefined
  if (!data) throw e

  console.log('First entry that did not succeed:', data.failedIndex) // zero-based
  for (const entry of data.results) {
    switch (entry.status) {
      case 'submitted': break // validated with tesSUCCESS
      case 'failed':    break // definitely failed; see entry.engine_result
      case 'unknown':   break // may still validate: check entry.hash, don't assume failure
      case 'signed':    break // never broadcast; can still be submitted as-is, in order
      case 'stranded':  break // never broadcast and can no longer apply; sign again
    }
  }
}
```

- The error message looks like `transaction at index 2 of 5 did not succeed: tecUNFUNDED_PAYMENT`.
- Joey watches each submitted entry for up to 10 minutes. Entries still pending after that come back as `unknown`.

## XLS-56 Batch

Check support first. An older extension refuses every `Batch` with `4100`.

```ts
import { BATCH_FLAGS, TF_INNER_BATCH_TXN, type BatchTransaction } from '@joeywallet/wallet-sdk'

if (!joey.supportsBatch()) {
  // Fall back to separate transactions
}

const batch: BatchTransaction = {
  TransactionType: 'Batch',
  Account: user,
  Flags: BATCH_FLAGS.tfAllOrNothing, // exactly one mode flag
  Sequence: n,
  Fee: fee,                          // base fee × (2 + inner transactions + co-signers)
  LastLedgerSequence: current + 15,
  RawTransactions: [
    {
      RawTransaction: {
        TransactionType: 'Payment', Account: user, Destination: a, Amount: '1000000',
        Sequence: n + 1, Fee: '0', SigningPubKey: '', Flags: TF_INNER_BATCH_TXN,
      },
    },
    {
      RawTransaction: {
        TransactionType: 'Payment', Account: user, Destination: b, Amount: '2000000',
        Sequence: n + 2, Fee: '0', SigningPubKey: '', Flags: TF_INNER_BATCH_TXN,
      },
    },
  ],
}

const result = await joey.signAndSubmitTransaction({ tx_json: batch, autofill: false })

// Decide success from result.batch, never from engine_result alone
if (result.batch?.applied !== 'all') {
  // 'none' | 'some' | 'unknown'
  // Inspect result.batch.inner[i]: { hash, account, status, engine_result }
}
```

> [!WARNING]
> A rolled-back all-or-nothing batch still returns `tesSUCCESS`: the fee is charged but nothing is applied. Always check `result.batch.applied`.

### Rules Joey enforces

Violations fail with `-32602` before the user is asked.

- The approval screen shows every inner transaction separately, and each must pass the same [refusal rules](/docs/browser-extension/transactions) as a normal transaction.
- **Inner transactions:** `Fee: "0"`, empty `SigningPubKey`, no signature, the `tfInnerBatchTxn` flag set, and no nested `Batch`. The user's inner transactions use the outer `Sequence` +1, +2, … and have no `LastLedgerSequence`.
- **Exactly one mode flag**, and the `Batch` must be the only transaction in its request.
- **Batches that include other accounts** need `autofill: false`, and a valid single-signature `BatchSigners` entry for each other account. Joey never creates a `BatchSigner` itself.
- **Ledger accounts can't sign a `Batch`.**
- With autofill on and no other accounts involved, Joey only fills fields you left out.
