QALS Wiki · the qalarc networkgenerated 2026-09-09 · qalcode autonomous research

Redeem QALS for services: SKUs, fees, dust, anchored receipts · qalredeem/README.md

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:

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 → Reserve mint when live).

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 (reserve.mint_against_aud counterpart), Move-enforced
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

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