# qalredeem — the QALS Redemption System

**Created:** 2026-09-09 · **Service:** `127.0.0.1:8835` · **Zero-dependency** (Python stdlib only)
**The operational half of the AU$1 story:** customers turn their loopd credit balance into real qalarc services — **services only, no cash-out, by policy** (see `content/token_supply.md` §Cash-out policy and `03_qals_architecture/QALS_BACKING_DESIGN.md` §1–2).

```
python3 qalredeem/qalredeem.py serve     # listens 127.0.0.1:8835
bash qalredeem/test_qalredeem.sh         # 57 end-to-end proofs (all green)
```

---

## 1. The flow

```
 wallet/agent                    qalredeem :8835                      loopd :8823
──────────────                ────────────────────                ─────────────────
 GET /catalog ────────────► pick sku, qty
 POST /redeem ────────────► verify X-Sig ─────────────────────────► hold(gross)      ~115% pre-auth
   {agent_id,sku,qty,         (HMAC of loopd secret)               │ 402 if balance/cap exceeded
    ref,ts,nonce,secret}      nonce replay guard                   ▼
                              provision()  ◄── HOOK POINT          settle(service, round5)
                              (v1: instant stub)                     │ doof dust → Church ledger
                              │                                      ▼
                              ├─────────────────────────────────► transfer  service value
                              │                                   (+release sweep) → qalredeem-ops
                              ├─ fee>0 ─────────────────────────► transfer  redemption fee
                              │                                      → qalredeem-fee (fee ledger)
                              │  record → data/redemptions.jsonl (append-only)
                              │  record → qalpipe anchor (sha256 on-chain, DataAnchor object)
                              ▼
 200 {redemption_id, receipt_id, anchored_tx, fulfilled:true, split…}
```

Every redemption is recorded in `data/redemptions.jsonl`, written as a per-redemption JSON file under `data/anchors/<redemption_id>.json`, and anchored on the qalnet devnet chain via `qalpipe` — the anchored hash is `sha256(sku|qty|cents|receipts|ts)`. Verify any record any time:

```
python3 qalpipe/qalpipe.py verify qalredeem/data/anchors/rdm_XXXXXXXXXX.json   # → PASS
```

## 2. Where every cent goes (fee + dust accounting)

For `gross = qty × price_cents_per_unit`:

| Slice | Formula | Lands in | Proof trail |
|---|---|---|---|
| **Service value** | `settled = gross − fee`, rounded **down to 5c** | `qalredeem-ops` agent balance (qalarc operating revenue — Reserve → Operations in the backing design) | loopd settle receipt (kind `service_redemption`) + ops transfer receipt |
| **Redemption fee** | `fee = ⌊gross × fee_bps / 10000⌋` (per-SKU, default **0**) | `qalredeem-fee` agent balance (the redemption-fee ledger) | loopd transfer receipt, memo `redemption fee <sku> (<bps>bps)` |
| **Church dust** | `dust = (gross − fee) mod 5` | Church of the Doof NFP via loopd's doof-rounding (`loopd/data/nfp.json`) | `dust_cents_to_nfp` on the settle receipt |

**Identity (the whole model in one line):** `service_settled + fee + dust == gross`, enforced in code before a record is written.

Worked example — `priority-consult-hour` (1003c, fee_bps 50), qty 1:

```
gross 1003c ─┬─ service 995c  (998 rounded down: 3c dust → Church)  → ops agent
             ├─ fee       5c   (⌊1003×50/10000⌋)                    → fee ledger
             └─ dust      3c   (doof rounding, the Giving)          → Church of the Doof
customer debited exactly 1000c; every cent of the 1003 accounted.
```

Balance-debit exactness: loopd (paper) credits the hold **release** back to the customer's balance, so qalredeem sweeps the release into the ops transfer — net customer debit is *exactly* `gross − dust`. In the Move phase, holds debit balance at hold-time and the sweep disappears.

## 3. Policy (services-only, machine-readable)

`GET /policy` returns the standing policy for auditors and apps:

- **services_only: true** — QALS credit is acquired for AUD and consumed by qalarc services. It is *interchangeable inside the ecosystem*, never converted back to fiat in Phase 1–2 (closed-loop prepaid carve-out per the AU Token Law Playbook; VASP/licensed phase may revisit).
- **redemption_fee** — the standing terminology for any fee on redemption paths (never "withdrawal fee", never "cash-out fee"). Per-SKU `fee_bps`, default 0, capped at 5000. Demo SKU: `priority-consult-hour` (50bps).
- **doof_rounding** — settle rounds to the 5c; doof dust is donated to the Church of the Doof (doof.ing NFP) by default, opt-out. Complete audit trail via loopd's nfp ledger + on-chain records.
- **refusal** — redeeming beyond balance fails at the loopd hold → **HTTP 402** with balance/cap/required info. No credit, no overdraft, no cash conversion.
- **units** — loopd counts AUD cents; 1 QALS = 100c = 1,000,000 doofs; 1 cent = 10,000 doofs.

## 4. API

| Route | Auth | What |
|---|---|---|
| `GET /health` | – | service + loopd + catalog status |
| `GET /catalog` | – | active catalog (seeded + admin-edited) |
| `GET /policy` | – | standing policy JSON |
| `GET /report` | – | `{redemptions_today, cents_redeemed, service_settled_cents, fees_collected_cents, dust_to_church_cents, by_sku}` |
| `GET /redemptions?agent_id=` | – | ledger query (last 100), or `GET /redemption/<id>` |
| `POST /catalog` | admin `X-Sig = HMAC(admin_key, raw body)` | upsert `{sku:{…}}` or `{skus:[…]}` |
| `POST /redeem` | customer `X-Sig = HMAC(loopd_secret, canonical payload)` | the redemption flow |

**Admin key:** env `QALREDEEM_ADMIN_KEY` wins; otherwise generated once, stored `data/admin_key.txt` (0600), printed once. CLI: `python3 qalredeem/qalredeem.py add-sku <sku> <name> <kind> <price_c> [fee_bps] [desc]`.

**`POST /redeem` contract** (paper phase): body `{agent_id, sku, qty, ref, ts, nonce, secret}` where `secret` is the customer's 64-hex loopd api secret, and `X-Sig = HMAC-SHA256(loopd_secret, canonical_json({agent_id,sku,qty,ref,ts,nonce}))` with `canonical_json` = `json.dumps(payload, sort_keys=True, separators=(",",":"))`. Verifying the signature proves the caller owns the very loopd credential qalredeem will spend against; `ts` must be within ±5 min and `nonce` single-use. On localhost (loopd's own paper model, no TLS) the secret-in-body is equivalent to session auth — upgrade path: delegated session keys / RFC 9421 once loopd grows signature verification.

**CLI:** `serve · catalog · policy · report · redemptions [agent_id] · add-sku … · redeem <agent_id> <sku> <qty> <secret|'-' for $QALREDEEM_SECRET> [ref] [nonce] · admin-key [--show]`

## 5. App integration (wallet "Redeem" screen)

1. `GET /catalog` → render cards (name, `price_cents_per_unit`, kind icon, fee badge if `fee_bps>0`). Prices are cents; display as `0.02 Qals` (= 2c).
2. User picks sku + qty → show the split preview: value / fee / (likely 0 dust).
3. `POST /redeem` with the device's loopd agent credentials (qalchat handles / seedkit-bound agents work as-is) → spinner ("provisioning…").
4. On `200`: show receipt + anchored tx link (`anchored_tx`, object on qalnet explorer). On `402`: "insufficient balance — top up first" (top-up = the credit-sale path: `/topup` at loopd → transfer from the pre-endowed issuer wallet `qals:issuer`; when it runs empty, sales stop — 402 "sellable supply exhausted").

## 6. Upgrade path

| Now (paper) | Next | Later (Phase 2+) |
|---|---|---|
| `provision()` instant stub | real hooks per kind: llm token grants, GPU scheduler seconds, render queue, storage quota, consult booking — fail *before* settle, hold auto-expires | metered fulfilment with ComputeReceipts (`07_compute_marketplace/`) |
| loopd ledger = the "chain" | records map 1:1 to CreditAccount/Hold/DataAnchor objects (loopd is the genesis importer) | **on-chain burn**: redemption burns B-QALS in `qal_reserve` (counterpart of the issuance gate), Move-enforced — note: the on-chain twin is being redesigned from mint-gated to genesis-preminted + transfer-gated at the cap-raise checklist |
| sha256 anchor via qalpipe on devnet | same pipeline, mainnet anchor objects | dispute recompute: expected output hash anchored with the redemption |
| secret-in-body localhost auth | delegated session keys | RFC 9421 / DID-bound signatures |
| redemption fee → fee agent | fees → FloorVault per backing design §3 | licensed-phase cash option: AU$1 − 0.5% **redemption fee** (terminology already correct) |

## 7. Operations

```bash
# qalredeem restart
pkill -f qalredeem.py; cd qalredeem && setsid nohup python3 qalredeem.py serve > /tmp/qalredeem.log 2>&1 &

# loopd restart (if :8823 down)
pkill -f loopd.py; cd loopd && nohup python3 loopd.py &

# chain (:9000) — see scripts/gen_devnet.sh; qalpipe needs qalpipe/pkg_id.txt
# (restored 2026-09-09 to 0xe3953c8e…74873 after a devnet regen left it blank)
```

Data (all under `qalredeem/data/`): `catalog.json` (seeded, admin-editable) · `redemptions.jsonl` (append-only) · `anchors/<id>.json` (anchored records) · `keys.json` (qalredeem's own ops/fee loopd agents) · `admin_key.txt`.

*Related: `loopd/loopd.py` (payment loop) · `qalpipe/qalpipe.py` (anchoring) · `03_qals_architecture/QALS_BACKING_DESIGN.md` · `content/token_supply.md`. Not financial advice.*
