/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 endpoint exists to close the "plaintext remnant" gap: when a user creates a wallet, deposits coins (which land plaintext on disk), and only later logs in for the first time, those pre-existing files are still plaintext on disk. Reading them works, but they are not encrypted. /api/system/encrypt_existing_files walks the wallet and rewrites each one through the encrypted writer.
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.
If no key is loaded, the endpoint returns 400 immediately — without a key it has nothing to encrypt with. Call /api/system/login first.
Parameters
Both parameters are optional. Send them in the POST body or query string.
| Parameter | Type | Required | Description |
|---|---|---|---|
wallet_path |
string | Optional | Restrict the run to one registered wallet's path (e.g. E:\Client_Data\Wallets\Default). Default: scan every registered wallet. |
folders |
string | Optional | Comma-separated folder names. Only Bank and Fracked are honored. Default: Bank,Fracked. Specifying neither returns 400. |
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",
"message": "Encryption started — poll /api/system/tasks?id= 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 } }
}
}
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",
"data": { "counts": { "processed": 45, "encrypted": 45, "skipped": 0, "errors": 0 } }
}
}
If any file failed (e.g. read error), status is "failed" and errors > 0. Successfully-encrypted files are NOT rolled back — the run is best-effort per file.
Counts
| Field | Description |
|---|---|
processed | Number of .bin files visited. |
encrypted | Number rewritten plaintext → encrypted. |
skipped | Number left untouched (already encrypted, or unexpected multi-coin in a wallet folder). |
errors | Number that failed to read or rewrite. Each error is logged to main.log. |
Error Responses
400 — No key set
{
"error": true,
"message": "No encryption key set — call /api/system/login first",
"code": 400
}
400 — Empty folder list
{
"error": true,
"message": "folders must include at least one of: Bank, Fracked",
"code": 400
}
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 (all wallets, both folders).
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/login — Required prerequisite. Without a key set, this endpoint returns 400.
- /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.