/api/wallets/consolidate
POSTReduce wallet size by automatically joining smaller denomination coins into larger ones.
This endpoint modifies your wallet by joining coins via RAIDA. The operation cannot be undone. Source coins are moved to Changed/ and a new larger-denomination coin is written to Bank/. Ensure you understand the implications before running.
Description
Reduces the number of coins in Bank/ by joining ten coins of denomination D into one coin of denomination D+1, repeatedly, sweeping from the lowest denomination upward.
The endpoint is asynchronous. The HTTP call returns a task_id and a poll url immediately; the actual work runs in a background thread. Poll GET /api/system/tasks?task_id=... until status becomes "success" or "failed".
- Loads every single-coin file from
Bank/into per-denomination buckets in memory. - Sweeps denominations from
-8(0.00000001 CC) upward to5(100,000 CC) — the highest source whose join target (6, 1,000,000 CC) is still in human circulation. - For each denomination, while the bucket has enough coins (see Modes below), takes the next 10 source coins and runs the RAIDA Join command (group 9, code 93) to mint one coin of the next-higher denomination.
- On RAIDA consensus (≥ 13 of 25), the 10 sources move to
Changed/and the new coin is written toBank/. The new coin is added to the next-higher in-memory bucket so cascades can fire in the same run (e.g. ten DN0 → one DN1, repeated until DN1 itself reaches the threshold). - If a join round fails (consensus not reached, network error, no available SN), the source coins stay in
Bank/and that denomination is skipped for the rest of the run. Other denominations still proceed.
Modes
The mode parameter controls the loop guard for each denomination:
tidy(default) — joins while count> 10. Leaves up to 10 coins of every denomination. Best when you want to keep a usable spread of small notes.complete(aliasfull) — joins while count>= 10. Maximally consolidates: only the leftover <10 of each denomination remains.
- You have 35 coins of denomination 0 (value 1) and nothing of denomination 1.
mode=tidy: while 35 > 10, join 10 → 1 DN1; bucket now has 25, then 15, then 5 of DN0 plus 3 of DN1. Final: 5×DN0 + 3×DN1.mode=complete: while 35 >= 10, join three batches; after the first cascade DN1 has 3 coins (still <10, so no further action). Final: 5×DN0 + 3×DN1 — same as tidy here, but tidy and complete diverge whenever a denomination has exactly 10 coins (tidy leaves them; complete merges them).
Fracked coins are not consolidated
Only single-coin .bin files in Bank/ are eligible. Coins in Fracked/ must be healed via /api/coins/authenticate or /api/coins/fix first.
Request
HTTP method: GET. All parameters are sent on the query string; there is no request body.
GET /api/wallets/consolidate
GET /api/wallets/consolidate?mode=complete
GET /api/wallets/consolidate?denomination=0&max_rounds=50
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
wallet_path |
string | No | Default wallet | Wallet to operate on. Resolved using the standard wallet-path rules. |
mode |
string | No | tidy |
One of tidy, complete, or full (alias of complete). Case-insensitive. Any other value returns 400. |
denomination |
integer | No | (sweep all) | If present, only this denomination is consolidated. Must be in range -8..5 (the highest human-circulation source). Values outside that range return 400. |
max_rounds |
integer | No | 1000 |
Safety cap on total join rounds. Clamped to the range 1..10000. |
debug |
bool | No | false |
Per-request debug verbosity. Does not change behavior; only adds detail to main.log. |
Response
Kickoff Response (HTTP 200)
Returned immediately, before any RAIDA work happens.
{
"command": "consolidate",
"success": true,
"task_id": "Apr-24-26_05-43-08-pm-b44b",
"url": "http://localhost:8080/api/system/tasks?task_id=Apr-24-26_05-43-08-pm-b44b",
"wallet_path": "E:\\Client_Data\\Wallets\\Default",
"mode": "tidy",
"max_rounds": 1000,
"message": "Consolidate started - poll /api/system/tasks for status"
}
If denomination was supplied, it is echoed in the kickoff response.
Kickoff Response Properties
| Property | Type | Description |
|---|---|---|
command |
string | Always "consolidate". |
success |
boolean | true when the worker thread was successfully spawned. This does not mean any join has completed — poll the task to find out. |
task_id |
string | Identifier for the background task. |
url |
string | Convenience: the exact URL to poll for status. |
wallet_path |
string | The resolved wallet path the worker will operate on. |
mode |
string | The mode that was selected (tidy or complete). |
denomination |
integer | Only present when a denomination filter was supplied. |
max_rounds |
integer | The clamped max_rounds the worker will use. |
Polling Response (Terminal)
When the task finishes, GET /api/system/tasks?task_id=... returns:
{
"status": "success",
"payload": {
"id": "Apr-24-26_05-43-08-pm-b44b",
"status": "success",
"progress": 100,
"message": "Done: 70 joined, 7 created, 0 failed round(s)",
"data": {
"command": "consolidate",
"mode": "tidy",
"wallet_path": "E:\\Client_Data\\Wallets\\Default",
"rounds_completed": 7,
"rounds_failed": 0,
"coins_joined": 70,
"coins_created": 7,
"starting_totals": { "0": 2, "2": 40, "3": 5 },
"ending_totals": { "0": 2, "2": 0, "3": 9 },
"rounds": [
{
"source_denomination": 2,
"target_denomination": 3,
"source_serials": [111, 222, 333, 444, 555, 666, 777, 888, 999, 1010],
"new_serial": 987654,
"raida_success_count": 25,
"result": "ok"
}
]
},
"raida_total": 25
}
}
data Object Properties
| Property | Type | Description |
|---|---|---|
command |
string | Always "consolidate". |
mode |
string | The mode used (tidy or complete). |
wallet_path |
string | The wallet operated on. |
rounds_completed |
integer | Number of join rounds where ≥ 13 RAIDAs reached consensus. |
rounds_failed |
integer | Number of join rounds that did not reach consensus or hit a transport / SN-availability error. |
coins_joined |
integer | Total source coins consumed across all successful rounds (10 × rounds_completed). |
coins_created |
integer | Total new higher-denomination coins minted (= rounds_completed). |
starting_totals |
object | Map of denomination code → coin count in Bank/ at the start. Only denominations with at least one coin are listed. |
ending_totals |
object | Same shape as starting_totals, after the run. |
rounds |
array | One entry per attempted round, in chronological order. See below. |
Round Object
Each entry in rounds[]:
| Property | Type | When present | Description |
|---|---|---|---|
source_denomination |
integer | always | The denomination of the 10 source coins. |
target_denomination |
integer | always | The denomination of the new minted coin (source_denomination + 1). |
raida_success_count |
integer | always | Number of RAIDAs (0–25) that returned a success status for this round. ≥ 13 is consensus. |
result |
string | always | "ok" if consensus was reached and disk operations completed, "failed" otherwise. |
source_serials |
array of integers | only on result: "ok" |
The 10 serial numbers consumed. |
new_serial |
integer | only on result: "ok" |
The serial number of the new minted coin. |
error |
string | only on result: "failed" |
The error string from the underlying join command (e.g. "Join command failed to achieve consensus (8/25)", "Failed to find an available serial number for the new coin."). |
Partial Success
If some rounds succeed and others fail, the task still ends with status: "success" — rounds_failed > 0 is the partial-success signal. Inspect rounds[] to see which denominations could not be consolidated. Re-running consolidate is safe and idempotent: succeeded coins are already in Bank/, and skipped denominations will be retried.
Failure Modes (task ends with status: "failed")
The whole task fails (no data object) only when the worker cannot start at all:
"Transport not initialized"— RAIDA transport layer was not bootstrapped."No encryption key coin available"— no Bank or Fracked coin in any registered wallet to use as an AES-128 key for encrypted RAIDA traffic."Failed to scan Bank folder"— filesystem error reading the wallet'sBank/.
HTTP Error Responses (kickoff)
These are returned synchronously from the kickoff GET, before any task is created:
Invalid wallet_path
{ "error": true, "message": "Invalid wallet_path", "code": 400 }
Invalid mode
{ "error": true, "message": "Invalid mode (must be 'tidy', 'complete', or 'full')", "code": 400 }
Invalid denomination
{ "error": true, "message": "Invalid denomination (must be -8 to 5; the join target must stay within human-circulation denominations)", "code": 400 }
Consolidation Rules
Denomination Range
CloudCoin denominations are pure metric powers of ten. The denomination byte is a signed exponent: code 0 = 1 CC, code 1 = 10 CC, code 2 = 100 CC, etc., and negative codes are fractions: code -1 = 0.1 CC, -2 = 0.01 CC, down to -8 = 0.00000001 CC.
- Eligible source denominations:
-8through5. Joining ten denom-D coins mints one denom-(D+1) coin, so the highest target is6(1,000,000 CC) — the largest denomination in human circulation. - Not eligible as a source:
6and above. Codes7-11exist in the protocol as reserved headroom but are not in circulation, so the API refuses to mint into them.
Loop Guard
For each denomination D processed in ascending order:
mode=tidy: whilecount(D) > 10, take 10 coins → mint 1 coin of D+1.mode=complete: whilecount(D) >= 10, take 10 coins → mint 1 coin of D+1.
Newly minted coins are appended to the in-memory bucket for the next denomination, so cascading works in a single run (e.g. minting the tenth DN1 coin can immediately trigger a DN1→DN2 round).
On Failure
If a join round fails (network error, no available SN, RAIDA consensus < 13, etc.), the source coins are left untouched in Bank/ and the failing denomination is skipped for the rest of this run. Other denominations still proceed. Call /api/wallets/consolidate again later to retry.
Disk Ordering
On a successful round, the 10 source files are moved from Bank/ to Changed/ before the new coin is written to Bank/. If the process crashes between the two steps, the RAIDA-side state and the source-coin location are still consistent: the sources are in Changed/ as the record of what was consumed, and the new coin can be recovered via authenticate / heal flows.
Example Usage
// Kick off consolidate and poll until done.
async function consolidateWallet(mode = 'tidy') {
const params = new URLSearchParams();
if (mode) params.set('mode', mode);
const kickoff = await fetch(
`http://localhost:8080/api/wallets/consolidate?${params}`
).then(r => r.json());
if (!kickoff.success) {
console.error('Kickoff failed:', kickoff.message);
return null;
}
console.log(`Started task ${kickoff.task_id} (mode=${kickoff.mode})`);
// Poll until terminal.
while (true) {
await new Promise(r => setTimeout(r, 2000));
const poll = await fetch(kickoff.url).then(r => r.json());
const p = poll.payload;
console.log(` status=${p.status} progress=${p.progress}% — ${p.message}`);
if (p.status === 'success' || p.status === 'failed') {
return p;
}
}
}
const result = await consolidateWallet('tidy');
if (result.status === 'success') {
const d = result.data;
console.log(`Joined ${d.coins_joined} coins into ${d.coins_created} larger coins.`);
console.log('Starting:', d.starting_totals);
console.log('Ending: ', d.ending_totals);
if (d.rounds_failed) {
console.warn(`${d.rounds_failed} round(s) failed; see rounds[].`);
}
}
# Default: tidy mode on the Default wallet, sweep all denominations
curl "http://localhost:8080/api/wallets/consolidate"
# Maximally consolidate
curl "http://localhost:8080/api/wallets/consolidate?mode=complete"
# Only consolidate denomination 0, with a small max_rounds cap
curl "http://localhost:8080/api/wallets/consolidate?denomination=0&max_rounds=5"
# Poll the task that the kickoff returned
curl "http://localhost:8080/api/system/tasks?task_id=Apr-24-26_05-43-08-pm-b44b"
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
type ConsolidateKickoff struct {
Command string `json:"command"`
Success bool `json:"success"`
TaskID string `json:"task_id"`
URL string `json:"url"`
WalletPath string `json:"wallet_path"`
Mode string `json:"mode"`
MaxRounds int `json:"max_rounds"`
Message string `json:"message"`
}
type ConsolidateRound struct {
SourceDenomination int `json:"source_denomination"`
TargetDenomination int `json:"target_denomination"`
SourceSerials []uint32 `json:"source_serials,omitempty"`
NewSerial uint32 `json:"new_serial,omitempty"`
RaidaSuccessCount int `json:"raida_success_count"`
Result string `json:"result"`
Error string `json:"error,omitempty"`
}
type ConsolidateData struct {
Command string `json:"command"`
Mode string `json:"mode"`
WalletPath string `json:"wallet_path"`
RoundsCompleted int `json:"rounds_completed"`
RoundsFailed int `json:"rounds_failed"`
CoinsJoined int `json:"coins_joined"`
CoinsCreated int `json:"coins_created"`
StartingTotals map[string]int `json:"starting_totals"`
EndingTotals map[string]int `json:"ending_totals"`
Rounds []ConsolidateRound `json:"rounds"`
}
type TaskPayload struct {
ID string `json:"id"`
Status string `json:"status"`
Progress int `json:"progress"`
Message string `json:"message"`
Data ConsolidateData `json:"data"`
}
type TaskResponse struct {
Status string `json:"status"`
Payload TaskPayload `json:"payload"`
}
func consolidate(mode string) (*ConsolidateData, error) {
q := url.Values{}
if mode != "" {
q.Set("mode", mode)
}
resp, err := http.Get("http://localhost:8080/api/wallets/consolidate?" + q.Encode())
if err != nil {
return nil, err
}
defer resp.Body.Close()
var k ConsolidateKickoff
if err := json.NewDecoder(resp.Body).Decode(&k); err != nil {
return nil, err
}
if !k.Success {
return nil, fmt.Errorf("kickoff failed: %s", k.Message)
}
for {
time.Sleep(2 * time.Second)
r, err := http.Get(k.URL)
if err != nil {
return nil, err
}
var t TaskResponse
if err := json.NewDecoder(r.Body).Decode(&t); err != nil {
r.Body.Close()
return nil, err
}
r.Body.Close()
if t.Payload.Status == "success" || t.Payload.Status == "failed" {
if t.Payload.Status == "failed" {
return nil, fmt.Errorf("task failed: %s", t.Payload.Message)
}
return &t.Payload.Data, nil
}
}
}
func main() {
d, err := consolidate("tidy")
if err != nil {
panic(err)
}
fmt.Printf("Joined %d coins into %d. Failed rounds: %d\n",
d.CoinsJoined, d.CoinsCreated, d.RoundsFailed)
fmt.Printf("Starting totals: %v\n", d.StartingTotals)
fmt.Printf("Ending totals: %v\n", d.EndingTotals)
}
Best Practices
When to Consolidate
- After receiving many small denomination transactions.
- Before making large payments — fewer coins means fewer signatures and faster RAIDA round-trips.
- When the wallet has accumulated 100+ coins.
- During low-activity windows; each round is a 25-RAIDA TCP fan-out.
- If you need to make change in specific small denominations soon —
tidystill leaves up to 10, butcompletecan leave 0–9. - While other wallet operations are mid-flight on the same wallet (e.g. an active deposit or transfer).
- If the network or specific RAIDAs are known to be unhealthy — partial-success runs are safe but will leave work undone.
Choosing a Mode
| Mode | Loop guard | Use case |
|---|---|---|
tidy |
count > 10 |
Default. Keeps a usable spread of every denomination while reducing clutter. |
complete (or full) |
count >= 10 |
Maximally consolidates. Use before storing a wallet long-term or before exporting. |
Value Preservation
Joining 10 coins of denomination D produces 1 coin of denomination D+1 with exactly the same face value. The RAIDA side of the operation is rejected if the sums don't match.
- Total wallet value: unchanged.
- Coin count: reduced by 9 per successful round.
- Serial numbers: the new coin gets a fresh SN allocated by RAIDA; the source SNs are retired.
Related Endpoints
- Show Register — view denomination breakdown before consolidating.
- Make Change — the inverse operation: break one coin into ten of the next-smaller denomination.
- System Tasks —
GET /api/system/tasks?task_id=...for polling consolidate's task to completion. - Authenticate — heal Fracked coins so they qualify for consolidate (which only operates on Bank).