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.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’sunderlying_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:
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 inSHIELD_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.
- TypeScript
- Python
- CLI
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 mainnetclient 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.
- TypeScript
- Python
- CLI
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.2. Prepare one record per trade
Prerequisites: Thetoken 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.
- TypeScript
- Python
- CLI
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.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.- TypeScript
- Python
- CLI
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.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.- TypeScript
- Python
- CLI
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.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 orshield-swap swap shares its journal through VEIL_INVENTORY_DB:
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.