/api/system/encrypt_existing_files

POST

Walk 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.

POST http://localhost:8080/api/system/encrypt_existing_files

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_type is 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_atomic which 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.

⚠️ Take a backup first

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.

Login is required

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

FieldDescription
processedNumber of .bin files visited.
encryptedNumber rewritten plaintext → encrypted.
skippedNumber left untouched (already encrypted, or unexpected multi-coin in a wallet folder).
errorsNumber 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