/api/system/encrypt_existing_files
POSTWalk Bank and Fracked folders and rewrite every plaintext .bin coin file as encrypted under the current login key. Skips files that are already encrypted. Returns a task_id immediately; the caller polls /api/system/tasks for completion.
Description
This is the Security-menu "Encrypt coins" action: it walks the wallet coin folders (Bank, Fracked, Limbo, Suspect, Grade, Pending, Import, Imported) and rewrites every plaintext (Type 9) single-coin .bin as an encrypted Type 10 file (612 bytes) under the loaded password. It also closes the "plaintext remnant" gap — coins deposited before encryption was enabled land plaintext on disk and get converted by this call.
Per file:
- Read the on-disk header. If
encryption_typeis non-zero, skip — the file is already encrypted (possibly under the same key, possibly under a different one; we do not touch existing ciphertext). - If the file is multi-coin (
token_count > 1) it is logged and skipped — multi-coin files in a wallet folder are not expected (multi-coin is export-only). - Otherwise, read the coin into memory, then rewrite atomically through
coin_file_write_atomicwhich encrypts under the currently loaded key. - The in-memory copy is zeroed before the worker moves to the next file.
The operation is idempotent: running it twice in a row encrypts what is plaintext the first time and skips everything the second time.
This rewrites every plaintext coin file in scope. The new bytes can only be read while the user is logged in with the same password. If the password is later forgotten, those coins are unrecoverable. Take a wallet backup via /api/wallets/backup before the first run.
- First encryption (no Type 10 files exist yet): a password must be held via /api/system/load-password (
key_state: "establishing_ready"), else400. The run establishes that password: the session moves toestablishingduring the run and toconfirmedon success. If the run fails, the establishment is rolled back and the password is retained for a retry. The GUI must ask for the password twice here and warn that losing it means losing the coins — there is no recovery. - Wallet already has Type 10 files: the session must be
confirmed(the password RAIDA-verified), else400. This prevents a second password from ever mixing into an encrypted wallet.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
wallet_path |
string | Optional | The wallet to encrypt. Must be a registered wallet, else 400. Default: the Default wallet. The run is per-wallet — call once per registered wallet to encrypt everything. |
Response
Kickoff — 200 OK
Returns immediately with a task_id. The actual work runs on a background thread.
{
"command": "encrypt-existing-files",
"success": true,
"task_id": "Apr-25-26_06-00-14-pm-a9b6",
"url": "http://localhost:8080/api/system/tasks?task_id=Apr-25-26_06-00-14-pm-a9b6",
"wallet_path": "E:\\Client_Data\\Wallets\\Default",
"key_state": "establishing",
"key_set": true,
"message": "Encryption started — poll task url for status"
}
Polling — GET /api/system/tasks?task_id=...
While running:
{
"status": "success",
"payload": {
"id": "Apr-25-26_06-00-14-pm-a9b6",
"status": "running",
"progress": 50,
"message": "Encrypting plaintext coin files",
"data": { "counts": { "processed": 12, "encrypted": 9, "skipped": 3, "errors": 0,
"already_target": 3, "skipped_multi": 0, "conflict": 0 } }
}
}
Done:
{
"status": "success",
"payload": {
"id": "Apr-25-26_06-00-14-pm-a9b6",
"status": "success",
"progress": 100,
"message": "Encrypt-existing-files done: processed=45 encrypted=45 skipped=0 errors=0 already_target=0",
"data": { "counts": { "processed": 45, "encrypted": 45, "skipped": 0, "errors": 0,
"already_target": 0, "skipped_multi": 0, "conflict": 0 } }
}
}
If any file failed (errors > 0) or a concurrent write collided (conflict > 0), the task status is "failed". Files already encrypted are NOT rolled back — the run is best-effort per file and idempotent, so re-running finishes the job. On a failed first encryption the password establishment is rolled back (the wallet does not become half-committed to an unverified password) but the typed password is retained in RAM for the retry.
Counts
| Field | Description |
|---|---|
processed | Number of .bin files visited. |
encrypted | Number rewritten plaintext → encrypted Type 10. |
skipped | Number left untouched, total (sum of the reasons below plus unreadable-header files). |
already_target | Skipped because already Type 10. |
skipped_multi | Skipped multi-coin files (not expected in wallet folders; export-only format). |
conflict | Files that changed on disk mid-run (concurrent writer). Any conflict fails the task; re-run to converge. |
errors | Number that failed to read or rewrite. Each error is logged to main.log. |
Error Responses
400 — No password held (first encryption)
{
"error": true,
"message": "No password held — call /api/system/load-password first",
"code": 400
}
400 — Confirmed key required (Type 10 files already present)
{
"error": true,
"message": "Type 10 files present; confirmed key required",
"code": 400,
"key_state": "candidate",
"key_set": true
}
400 — Corrupt Type 10 present
Corrupt Type 10 coin file present; repair before encrypting — collect the damaged files with copy_undecryptable first.
400 — Bad wallet
Invalid wallet_path or wallet_path is not a registered wallet.
405 — GET not allowed
Use POST.
503 — Cannot scan
Unable to scan wallet for encrypted files — transient; retry.
500 — Cannot start worker thread
Returned only on memory or thread-creation failure. Should not happen in normal operation.
Example Usage
# Kick off the run (Default wallet).
RESP=$(curl -s -X POST "http://localhost:8080/api/system/encrypt_existing_files")
echo "$RESP"
TID=$(echo "$RESP" | sed 's/.*task_id":"\([^"]*\)".*/\1/')
# Poll until done.
while :; do
STATE=$(curl -s "http://localhost:8080/api/system/tasks?task_id=$TID")
echo "$STATE"
echo "$STATE" | grep -q '"status":"running"' || break
sleep 1
done
const start = await fetch(
'http://localhost:8080/api/system/encrypt_existing_files',
{ method: 'POST' }
).then(r => r.json());
const id = start.task_id;
let done = false;
while (!done) {
const t = await fetch(
`http://localhost:8080/api/system/tasks?task_id=${id}`
).then(r => r.json());
console.log(t.payload.message, t.payload.data.counts);
done = t.payload.status !== 'running';
if (!done) await new Promise(r => setTimeout(r, 1000));
}
import requests, time
base = 'http://localhost:8080'
start = requests.post(f'{base}/api/system/encrypt_existing_files').json()
tid = start['task_id']
while True:
t = requests.get(f'{base}/api/system/tasks', params={'task_id': tid}).json()
p = t['payload']
print(p['status'], p['data']['counts'])
if p['status'] != 'running':
break
time.sleep(1)
Related Endpoints
- /api/system/load-password — Required prerequisite. Without a held (first run) or confirmed (later runs) password, this endpoint returns 400.
- /api/system/decrypt_existing_files — The reverse direction.
- /api/system/encryption-status — Surfaces the plaintext-remnant condition that motivates this call.
- /api/wallets/backup — Take a backup before the first encryption run.
- /api/system/tasks — Generic task polling endpoint.