# Paying many recipients

> Pay a whole roster from one wallet's notes in a single cycle, with a receipt for every recipient.

This guide pays N recipients from one wallet's [notes](/concepts/notes) in a single cycle. It is the pattern the [Payroll PoC](https://github.com/0xbow-io/PoC-Payroll) implements.

## Constraints to respect

The cycle has to fit the circuit's limits.

-   **5×5 circuit cap.** A single [transact](/protocol/circuits/transact) call takes at most 5 input notes and 5 output notes, so larger cycles split across multiple transactions.
-   **Label-aware output count.** The output count is also label-aware: each distinct deposit label among the inputs adds two outputs (one recipient note plus one change note), which means at most two labels per call if the outputs are to fit the 5x4 variant.
-   **Chain change forward.** After each transact, the change note becomes a fresh input for the next line item. Keep a running pool of spendable notes as you go.

## Outline

```ts
async function runCycle(recipients, treasury) {
    const pool = await loadAvailableNotes(treasury);

    for (const r of recipients) {
        // 1. Pick inputs (label-aware, chain-verified spent check)
        const inputs = pickInputs(pool, r.amount, r.asset);
        if (!inputs) {
            failures.push({ recipient: r, reason: "unallocated" });
            continue;
        }

        // 2. Prepare + relay
        const prepared = await session.prepareTransfer({
            inputCommitments: inputs.map(n => n.commitment),
            amount: r.amount,
            tokenId: r.asset,
            recipientDiscoveryData: {
                evmAddress: r.recipient,
            },
        });
        const result = await session.relayTransfer(prepared.relayOptions[0]);

        // 3. Persist receipt (per-line-item)
        const recipientNote = prepared.executeOptions.recipientPendingNotes[0];
        await persistReceipt({
            recipient: r,
            txHash: result.txReceipt.txHash,
            note: recipientNote,
        });

        // 4. Update running pool: remove spent inputs, add change notes
        removeFromPool(pool, inputs);
        for (const change of prepared.executeOptions.changePendingNotes) {
            if (isOwned(change)) addToPool(pool, change);
        }
    }
}
```

## Per-input chain-verified spent check

Wrap `pickInputs` so it skips notes whose [nullifier](/concepts/commitments-and-nullifiers) is on-chain even if local state says `ACTIVE`. See [verify spent](../operations/verify-spent).

## Error recovery

-   **[Relayer](/operations/relaying) reverts mid-cycle**: log the failure, drop the picked inputs from the pool (they may or may not be spent, so refresh via `discoverNotes` before the next run), and continue with the other line items.
-   **Quote expiry**: retry quote-expiry failures (`FeeCommitmentExpired` or a relayer rejection at submit time) by re-running `prepareTransfer` for a fresh quote, and cap the number of retries.
-   **NoteNotFoundError**: the [NoteManager](/sdk/note-manager) has drifted from chain state. Call `discoverNotes` before re-picking.

## UI

The PoC's `/org/cycles/[id]` page shows per-line-item status and a Download Receipt link for each settled item. See the [receipt guide](receipt-export-and-audit) for the receipt half.
