Skip to main content
This page helps you run several swaps at once from one account, and keep that account ready for the next batch, with the TypeScript SDK, Python SDK, or CLI.

Private token inventory basics

Your confidential balance is a set of records. Each record holds an amount you can spend in one transaction, and a swap spends exactly one of them. The swap takes the input amount from that record and returns the rest in a new change record. How many swaps you can run at once depends on how many records each cover one trade, not on your total balance. An account holding 4 USDCx in one record can run one 1 USDCx swap at a time. The same 4 USDCx in four records can run four. A swap spends one input record, sends 1 USDCx to the pool, and returns a 3 USDCx change record. The claim returns an ETH output record and, on a partial fill, a refund record. One 4 USDCx record funds one swap at a time while three wait; four 1 USDCx records fund four swaps at once. Two swaps that pick the same record cannot both settle. The network drops the second transaction when it verifies the spend, and your client sees a confirmation wait for a transaction that never reaches a block. Record coordination prevents this: each swap reserves its record in a shared journal before proving, and a swap that loses a record moves on to the next one. Reservations are off by default in the SDKs and the CLI. This page enables them. Inventory also changes while you trade. Every accepted swap returns a change record, and every claim returns an output record and any refund. Over time an account accumulates many small records, and a trade can fail for lack of one covering record while the total balance is more than enough. Joining and splitting records restores the layout your strategy needs. The pool, the amounts, the route, and the timing of each swap are public. Join and split transactions are public as well, including the program and function they call. The number and size of your records, and which record funded which swap, stay out of the public market data. See Public data.

Plan your inventory

Prerequisites: Your mainnet account and trading tools from Setup. Record coordination and inventory maintenance need the latest SDK and CLI releases, so upgrade the packages from Setup before using this page. The SQLite journal used below requires Node.js 22.13 or later. Decide three things before preparing records. How many records. One record per swap you want in flight, plus spares. A record spent by a swap is unavailable until that swap is accepted and the record scanner shows its change record. A record returned by a claim is unavailable until the scanner shows it. Keeping one spare lot for each parallel slot lets the next batch start without waiting on that lag. How large each record is. A record must cover the swap’s input amount. Equal lots suit a strategy that trades a fixed size. A mix of sizes suits a strategy that trades varying sizes, at the cost of larger change records. Amounts are integers in the token’s base units; the SDKs convert from decimal units using the token’s decimals, and the CLI takes base units directly. Which asset the records belong to. Wrapped assets are spent from the underlying token program, so the inventory you prepare belongs to that underlying program, not to the pool’s wrapper. The SDKs resolve this for you from the token symbol. For the CLI, read the token’s underlying_program from the token list. For four parallel 1 USDCx swaps with one spare lot per slot, the target is eight records of at least 1 USDCx, which needs at least 8 USDCx in the account: Concurrent swaps from one account share four resources: By default both SDKs and the CLI pay network fees through the delegated prover, so fees do not consume your records. If you configure an account that pays its own fees from confidential credits, those credits records are inventory too, and each concurrent swap needs its own. Prepare them with the same tools, using credits.aleo as the asset.

Example plan

Suppose the account holds 10 USDCx as three records of 7, 2.5, and 0.5 USDCx, and you want four records of at least 1 USDCx with equal lots. The 0.5 USDCx record is below the minimum, so the planner joins all three into one record and then splits it. The plan below is the SDK planner’s output for that layout: Three USDCx records of 7, 2.5, and 0.5 are joined into one 10 USDCx record through a helper program. Three splits then cut it into four 2.5 USDCx lots, each split returning one lot and a remainder for the next. The plan is four transactions, run one at a time because each split spends the previous remainder. Each step waits for its outputs to appear in the record scanner before the next begins. USDCx joins and splits have no intrinsic deduction, so the four lots sum to the original 10 USDCx; only network fees apply. The distribution changes what the planner does with records that already fit. If the same account instead held 6, 2, 1, and 1 USDCx, a preserve target of four records at 1 USDCx is already met and the plan has no steps. A balanced target with zero tolerance joins all four and splits the result into four 2.5 USDCx lots, the same four transactions as above. Choose preserve when any covering record will do and balanced when every trade should spend the same size.

Setup a client with resource stores

Prerequisites: Your mainnet account from Setup and its private key in SHIELD_SWAP_PRIVATE_KEY. Reuse the recovery directory from optional storage so the swap identities saved there remain available. The client needs two stores. The record journal holds reservations and prepared transactions; the identity store or swap journal holds the data needed to claim. Keep both files between sessions, keep them out of logs and source control, and run one trading process per account and network. A SQLite journal coordinates records between a trading process and a maintenance process on the same machine, but identity allocation stays within one process.
Install the packages in an ES module application using Node.js 22.13 or later. The CLI package supplies the SQLite journal:
Build the client with recordManagement enabled. waitForSync makes record reads wait until the scanner reports it has caught up, so a lagging scan does not plan needless joins:
client now reserves a record for every swap and inventory operation it submits. Call store.close() when the application shuts down, after every call has settled. For a script that runs to completion with nothing else trading for the account, memoryRecordInventoryStore() from @provablehq/veil-core works without SQLite, but its reservations are lost when the process exits.

1. Inspect the records

Prerequisites: Your coordinated mainnet client for TypeScript, dex and aleo for Python, or the CLI configuration from Setup a client with resource stores. Read how many USDCx records you hold, their sizes, and which ones another operation has reserved. These reads submit nothing.
inventory.available lists spendable records that no journal entry holds; inventory.reserved lists records held by a swap or maintenance step in flight. Each entry has an id, the record nonce, and an amount as a base-unit bigint. inventory.balance sums the available amounts.
An account with 4 USDCx available in one record reports one available record. It can fund one 1 USDCx swap at a time until the next step reshapes it.

2. Prepare one record per trade

Prerequisites: The token and asset from Inspect the records, and the coordinated client from Setup a client with resource stores. The account needs USDCx records totaling at least 4 USDCx for this example. Plan the joins and splits that leave the account with four records of at least 1 USDCx each, review the plan, then execute it. A balanced distribution with zero tolerance spreads the entire available balance into equal lots. A preserve distribution keeps records that already cover the minimum and changes as little as possible, which suits a strategy that trades varying sizes. Planning reads the scanner and the token program and submits nothing. Execution runs one transaction at a time: each step reserves its inputs, proves, submits, waits for acceptance, and then waits until the scanner shows the step’s exact outputs before the next step spends them. Two records join through the token program’s own join function; larger joins use deployed helper programs that take up to 16 records for credits, USDCx, and USAD, or 15 for other tokens. A credits split also deducts 10,000 microcredits; token joins and splits have no intrinsic deduction.
Prepare records before you quote. Joins and splits each take a confirmation, and a TypeScript quote expires 60 seconds after it is requested. Request the swap quote after maintenance finishes.

Plan

plan.steps lists each join or split with its inputs and predicted outputs. A plan for an account that already holds four covering records has no steps.

Execute

timeoutMs bounds each step’s confirmation and scanner wait. maintenance.transactionIds lists every submitted transaction. An interrupted result keeps the steps already accepted; reconcile before planning again. client.rebalanceInventory({ token, target }) plans and executes in one call when you do not need to review the plan first.
Repeat Inspect the records after execution. The account now reports four available USDCx records.

3. Submit swaps in parallel

Prerequisites: Four available USDCx records from Prepare one record per trade and the coordinated client from Setup a client with resource stores. These examples submit four 1 USDCx swaps for ETH on mainnet at once, then claim each output. Each swap selects its own record from one scan, smallest sufficient first, and reserves it before proving. A swap whose record another swap reserved first moves to the next candidate; a swap that finds no free candidate fails before proving and spends nothing. Proving, submission, and confirmation errors are not retried. Every swap and claim is its own transaction, so one failure leaves the others running.
Submit only when you’re ready to trade on mainnet. Each call spends a record. Running the batch again starts four more swaps.
Leave tokenRecord unset so each swap can reselect on conflict. Sharing one quote fixes the same minimum output for every trade; a quote does not reserve pool liquidity, so a trade can still fail when the price moves.
Promise.allSettled keeps every result. claims[index] belongs to handles[index], not to swaps[index], because a failed swap returns no handle. The identity store saves each handle as soon as its swap is submitted, so a claim that fails here can be finished later from Swap History & Recovery.
After the claims are accepted, check the result for each pair of transaction IDs. Each claim returns an ETH record and, when the trade stopped early, a USDCx refund record. Both are new inventory.

4. Keep the account ready between batches

Prerequisites: The coordinated client from Setup a client with resource stores, reopened with the same account, network, and files after any restart. A strategy that trades continuously repeats three steps between batches: settle the previous batch, restore the record layout, and only then quote the next batch. Do these in order, and stop other traders for the account while recovering.

Settle the previous batch

A timeout means your client stopped waiting; the transaction can still reach the chain. Check the journal against the chain before doing anything else. A reservation whose transaction was accepted stays excluded from selection even while the scanner still reports the record as unspent, which protects you from spending a consumed record after a stale scan.
reconcileRecordInventory marks accepted entries confirmed and rejected entries rejected, which releases their records. A transaction the node has never seen keeps its reservation; call client.reconcileRecordInventory({ rebroadcast: true }) to resend the saved transaction with its original proof and ID. pending.swaps are outputs still waiting on chain; claim them with Swap History & Recovery.
Do not delete the journal to clear a pending reservation. The journal holds the proved transaction needed to resend an uncertain submission. Deleting it can let the next batch spend a record that an earlier transaction already consumed.

Restore the record layout

Claims and change records change the layout after every batch. Rerun Inspect the records, and when the count of covering records drops below your target, rerun Prepare one record per trade. Maintenance and trading can share the journal: a maintenance step rescans before it runs and skips records a swap holds, and a swap skips records a maintenance step holds. Keep maintenance small and frequent, so the records a batch needs are rarely all reserved by a join. For a service, the CLI can maintain the layout on a schedule while an SDK trader or shield-swap swap shares its journal through VEIL_INVENTORY_DB:
The loop checks each policy every intervalMs (default 30 seconds) and waits cooldownMs (default one minute) after acting on an asset. Add "countRange": [3, 6] to a policy to leave the layout alone while the record count stays in that range and every record meets the minimum, which avoids churn when a balanced policy would otherwise rebuild lots after every trade. maxFeeMicrocredits and maxDailyFeeMicrocredits cap the fees maintenance may spend. Only one maintenance loop can run for an account and chain, and pending trading reservations take priority over maintenance. The loop stays in the foreground; run it under a service manager.

Quote and submit the next batch

Request a fresh quote after maintenance and repeat Submit swaps in parallel. Keep the batch size at or below the number of available covering records, so no swap waits on a record that is still in flight.

Troubleshooting

Next steps

Recover an interrupted batch

Swap History & Recovery shows how to establish each swap’s outcome and finish its claim without starting another trade.

Understand record requirements

Preparing token records explains the record fields a swap needs and the record state machine behind the journal.

Run packaged examples

The TypeScript concurrent-swaps example and the Python concurrent-swaps example prepare six balanced USDCx records and swap them for ETH at once on testnet, with a preview mode and recovery instructions. Each execution starts six new trades.