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

# Auto-Batch

> Optional background engine that aggregates transfers into batched ledger submissions for high throughput.

Auto-Batch is an optional background feature that automatically aggregates pending transfer orders into batched ledger submissions. Instead of one interactive transaction per transfer, up to `AUTO_BATCH_MAX_SIZE` transfers for the same party are collapsed into a single submission — significantly improving throughput under load.

## Enabling

Auto-Batch is **disabled by default** (`AUTO_BATCH_SWITCH=false`). When disabled, no background loops run and the HTTP server behaves exactly as before this feature was introduced.

```bash theme={null}
AUTO_BATCH_SWITCH=true
```

## Database

The feature persists batch state in a relational store. Two backends are supported:

> **in-memory (default)**
>
> An H2 database in PostgreSQL-compatibility mode. No extra infrastructure required. **State does not survive a restart.**

> **psql**
>
> A PostgreSQL instance for persistent, production-grade storage. Set `AUTO_BATCH_DB_TYPE=psql`.

> [!NOTE]
>
> The default is `in-memory` for **backward compatibility**. Existing deployments can opt into auto-batching without provisioning a PostgreSQL instance.

> [!WARNING]
>
> The database is initialised on every startup (schema migrations apply automatically). In `in-memory` mode the store is empty after each restart — any in-flight batches from a prior run are not recovered.

## Batch lifecycle

An `Order` (a single transfer request submitted via the HTTP API) moves through two independent state machines:

| Entity | Terminal success | Terminal failure |
| - | - | - |
| **Order** | `Dequeue` (picked up by a batch) | (released back to `Queued` if batch fails terminally) |
| **Batch** | `Confirmed` | `Failed` with `attempts ≥ maxAttempts` |

```mermaid theme={null}
stateDiagram-v2
    direction LR

    [*] --> Prepared : prepareBatch

    Prepared --> Submitted : submitBatch (batching / recovery)
    Prepared --> Failed    : submitBatch (sync error)

    Submitted --> Accepted  : ledger ack
    Submitted --> Failed    : ledger rejects

    Accepted --> Confirmed  : ledger confirms
    Accepted --> Failed     : ledger rejects

    Failed --> Submitted    : retry (attempts < maxAttempts)
    Failed --> [*]          : terminal (attempts ≥ maxAttempts)

    Confirmed --> [*]
```

### Key invariants

* `attempts` is incremented **before** each submission attempt (atomically). Both synchronous errors (network, SDK) and asynchronous ledger rejections consume exactly one credit — no silent infinite retries.
* Every `update()` uses **optimistic locking** (keyed on `batch.updatedAt`) so the three concurrent loops never corrupt each other's state.

## Background loops

`BatchOrdersJob` spawns three loops in parallel. All loops share the same **exponential-backoff wrapper** (1 s → 60 s, with jitter) that activates on unhandled errors, preventing cascading failures from hammering downstream services.

> **batchingLoop**
>
> Prepares and submits one new batch per idle party per cycle. Also recovers `Prepared` batches with `attempts = 0` that were created before a restart.
>
> ```mermaid theme={null}
> sequenceDiagram
>     participant batchingLoop
>     participant BatchOrdersWorker
>     participant BatchStore
>     participant OrdersStore
>     participant WalletService
>
>     loop every batchInterval
>         batchingLoop->>BatchOrdersWorker: recoverPreparedBatches()
>         BatchOrdersWorker->>BatchStore: searchByStatus(Prepared)
>         note over BatchOrdersWorker: submit any stuck batches (attempts = 0, never flew)
>
>         batchingLoop->>BatchOrdersWorker: prepareBatch()
>         BatchOrdersWorker->>BatchStore: searchActives()
>         note over BatchOrdersWorker,OrdersStore: exclude parties with any active batch
>         BatchOrdersWorker->>OrdersStore: searchNextAvailables(excludedParties, limit)
>
>         alt orders found for an idle party
>             BatchOrdersWorker->>BatchStore: create(Batch, orderIds)
>             note over BatchOrdersWorker,BatchStore: atomic: Batch(Prepared) + Order status → Dequeue
>             batchingLoop->>BatchOrdersWorker: submitBatch(batchId)
>             BatchOrdersWorker->>BatchStore: incrementAttempts(batchId)
>             BatchOrdersWorker->>WalletService: batchSend(from, transfers)
>             BatchOrdersWorker->>BatchStore: update(Submitted)
>         else no idle orders
>             batchingLoop->>batchingLoop: sleep(batchInterval)
>         end
>     end
> ```

> **monitoringLoop**
>
> Polls the ledger for every in-flight batch (`Submitted` or `Accepted`) and advances its state. Terminal outcomes (`Confirmed`, `Failed`) are written back; the retry loop handles `Failed`.
>
> ```mermaid theme={null}
> sequenceDiagram
>     participant monitoringLoop
>     participant BatchOrdersMonitor
>     participant BatchStore
>     participant WalletService
>
>     loop every monitorInterval
>         monitoringLoop->>BatchStore: searchMonitoreables()
>         note over BatchStore: Submitted + Accepted only
>
>         loop each in-flight batch
>             monitoringLoop->>BatchOrdersMonitor: monitorBatch(batchId)
>             BatchOrdersMonitor->>WalletService: commandStatus(partyId, commandId)
>
>             alt confirmed
>                 BatchOrdersMonitor->>BatchStore: update(Confirmed)
>             else failed
>                 BatchOrdersMonitor->>BatchStore: update(Failed)
>                 note over BatchOrdersMonitor: attempts already counted retryingLoop will handle retry
>             else in-flight
>                 BatchOrdersMonitor->>BatchStore: update(Accepted)
>             end
>         end
>
>         monitoringLoop->>monitoringLoop: sleep(monitorInterval)
>     end
> ```

> **retryingLoop**
>
> Separates failed batches into two groups: those still within the retry budget are re-submitted; those that have exhausted `maxAttempts` are marked terminal and their orders are released back to `Queued`.
>
> ```mermaid theme={null}
> sequenceDiagram
>     participant retryingLoop
>     participant BatchOrdersWorker
>     participant BatchStore
>     participant BatchOrdersStore
>
>     loop every retryInterval
>         retryingLoop->>BatchStore: searchFailed()
>
>         loop terminal batches (attempts ≥ maxAttempts)
>             retryingLoop->>BatchOrdersStore: searchIn(batchId)
>             retryingLoop->>BatchStore: markAsFailedAndReleaseOrders(batchId, orderIds)
>             note over BatchStore: atomic: status → Failed + orders → Queued
>         end
>
>         loop retriable batches (0 < attempts < maxAttempts)
>             retryingLoop->>BatchOrdersWorker: submitBatch(batchId)
>             retryingLoop->>retryingLoop: sleep(recoveryInterval)
>         end
>
>         retryingLoop->>retryingLoop: sleep(retryInterval)
>     end
> ```

> [!TIP]
>
> Tune batch and retry timings via the `AUTO_BATCH_*` environment variables. See [Configuration](https://docs.cantor8.tech/enterprise-wallet/configuration.md#auto-batch).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.