/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
⚠️ Not available yet — returns 501

File encryption is being rebuilt on the Type 10 format (encrypted files are exactly 612 bytes; byte 0 = 0x0A). The legacy interim scheme this page previously described has been removed, and the current build returns 501 Not Implemented from this endpoint until the Type 10 writer lands. The contract below (task_id + counts) is what returns then. GUIs should treat 501 as "feature not available yet".

Description

This is the Security-menu "Encrypt coins" action: it walks the wallet coin folders and rewrites every plaintext (Type 9) single-coin .bin as an encrypted Type 10 file 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_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.

A loaded password is required

If no password is loaded, the endpoint returns 400 immediately — without a key it has nothing to encrypt with. Call /api/system/load-password first. On the very first encryption the password is being established — the GUI must ask for it twice and warn that losing it means losing the coins.

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