From a059e3f5967ea2f30d36cfdd42cdb7405e1a9624 Mon Sep 17 00:00:00 2001 From: Padreug Date: Sat, 13 Jun 2026 22:30:05 +0200 Subject: [PATCH] refactor: rename extension identity to spirekeeper Fork of satmachineadmin's v2-bitspire line into its own repo. Renames both identifiers so this extension is fully independent of the original satmachineadmin install (which remains in service): - extension id satmachineadmin -> spirekeeper (router prefix, static path/static_url_for, module symbols, task names, templates dir, config/manifest paths) - database name satoshimachine -> spirekeeper (Database(ext_spirekeeper), all schema-qualified table refs) Also resets versioning to 0.1.0, sets the display name + manifest to spirekeeper/aiolabs, and fixes the placeholder pyproject description. Historical aiolabs/satmachineadmin#N issue references in comments are left pointing at the original repo where those issues live. Co-Authored-By: Claude Opus 4.8 (1M context) --- __init__.py | 36 ++-- bitspire.py | 4 +- cassette_transport.py | 4 +- config.json | 8 +- crud.py | 156 +++++++++--------- fee_transport.py | 4 +- manifest.json | 6 +- migrations.py | 90 +++++----- nostr_publish.py | 2 +- nostr_transport_roster.py | 12 +- pyproject.toml | 6 +- static/js/index.js | 6 +- tasks.py | 46 +++--- .../_api_docs.html | 0 .../index.html | 2 +- tests/conftest.py | 2 +- tests/test_cassette_configs.py | 2 +- tests/test_collision_guard.py | 2 +- tests/test_fee_mismatch_recording.py | 4 +- tests/test_roster_resolver.py | 6 +- views.py | 12 +- views_api.py | 74 ++++----- 22 files changed, 242 insertions(+), 242 deletions(-) rename templates/{satmachineadmin => spirekeeper}/_api_docs.html (100%) rename templates/{satmachineadmin => spirekeeper}/index.html (99%) diff --git a/__init__.py b/__init__.py index daff156..39de3d8 100644 --- a/__init__.py +++ b/__init__.py @@ -7,29 +7,29 @@ from loguru import logger from .crud import db from .nostr_transport_roster import register_with_lnbits as register_roster_with_lnbits from .tasks import wait_for_cassette_state_events, wait_for_paid_invoices -from .views import satmachineadmin_generic_router -from .views_api import satmachineadmin_api_router +from .views import spirekeeper_generic_router +from .views_api import spirekeeper_api_router -logger.info("satmachineadmin v2 loaded") +logger.info("spirekeeper v2 loaded") -satmachineadmin_ext: APIRouter = APIRouter( - prefix="/satmachineadmin", tags=["DCA Admin"] +spirekeeper_ext: APIRouter = APIRouter( + prefix="/spirekeeper", tags=["DCA Admin"] ) -satmachineadmin_ext.include_router(satmachineadmin_generic_router) -satmachineadmin_ext.include_router(satmachineadmin_api_router) +spirekeeper_ext.include_router(spirekeeper_generic_router) +spirekeeper_ext.include_router(spirekeeper_api_router) -satmachineadmin_static_files = [ +spirekeeper_static_files = [ { - "path": "/satmachineadmin/static", - "name": "satmachineadmin_static", + "path": "/spirekeeper/static", + "name": "spirekeeper_static", } ] scheduled_tasks: list[asyncio.Task] = [] -def satmachineadmin_stop(): +def spirekeeper_stop(): for task in scheduled_tasks: try: task.cancel() @@ -37,10 +37,10 @@ def satmachineadmin_stop(): logger.warning(ex) -def satmachineadmin_start(): +def spirekeeper_start(): # bitSpire invoice listener — replaces the v1 SSH/PostgreSQL poller. invoice_task = create_permanent_unique_task( - "ext_satmachineadmin", wait_for_paid_invoices + "ext_spirekeeper", wait_for_paid_invoices ) scheduled_tasks.append(invoice_task) # Cassette bootstrap consumer (#29 v1) — subscribes to @@ -48,7 +48,7 @@ def satmachineadmin_start(): # cassette_configs on receipt. Soft-fails if nostrclient isn't # installed (logs + backs off, never crashes). cassette_task = create_permanent_unique_task( - "ext_satmachineadmin_cassette_bootstrap", wait_for_cassette_state_events + "ext_spirekeeper_cassette_bootstrap", wait_for_cassette_state_events ) scheduled_tasks.append(cassette_task) # Path-B wallet-routing hook (#20 / coord-log 2026-05-31T15:25Z): @@ -61,8 +61,8 @@ def satmachineadmin_start(): __all__ = [ "db", - "satmachineadmin_ext", - "satmachineadmin_start", - "satmachineadmin_static_files", - "satmachineadmin_stop", + "spirekeeper_ext", + "spirekeeper_start", + "spirekeeper_static_files", + "spirekeeper_stop", ] diff --git a/bitspire.py b/bitspire.py index 59192b2..9e0c70c 100644 --- a/bitspire.py +++ b/bitspire.py @@ -94,7 +94,7 @@ class SettlementMetadataError(ValueError): Raised by `parse_settlement`. Caller records the settlement as 'rejected' with the exception message in `error_message`. Operator investigates the ATM that issued the invoice — a bitSpire ATM that - landed on a satmachineadmin-managed wallet without stamping the + landed on a spirekeeper-managed wallet without stamping the canonical fields is a real upstream bug (lamassu-next side), not a graceful-degradation case. Pre-v2 reverse-derivation from the wire amount + a machine-level fallback rate is no longer supported: @@ -279,7 +279,7 @@ def parse_settlement( ) # Phase-1 observability per aiolabs/satmachineadmin#38 + coord-log # §2026-06-01T07:00Z (option A locked): compare bitspire's reported - # fee_sats against satmachineadmin's recompute, log on out-of- + # fee_sats against spirekeeper's recompute, log on out-of- # tolerance drift, record the delta unconditionally for triage. # Phase 2 (settlement-reject) lands after observability data. fee_mismatch_sats = fee_sats - (platform_fee_sats + operator_fee_sats) diff --git a/cassette_transport.py b/cassette_transport.py index 7d98906..9f60d9a 100644 --- a/cassette_transport.py +++ b/cassette_transport.py @@ -33,7 +33,7 @@ task (tasks.py) calls `decrypt_and_parse_state_event` per incoming event; the API endpoint (views_api.py) calls `publish_to_atm` per operator submit. The `` placeholder semantics (load-bearing per the 2026-05-30T11:50Z -coord-log entry): always the ATM's hex pubkey, NEVER satmachineadmin's +coord-log entry): always the ATM's hex pubkey, NEVER spirekeeper's internal dca_machines.id UUID. Helper `_atm_hex_pubkey(machine)` centralises the canonicalisation via lnbits.utils.nostr.normalize_public_key. """ @@ -146,7 +146,7 @@ def build_state_d_tags_for_machines(machines: list[Machine]) -> list[str]: # ============================================================================= -# Publish — operator → ATM (the satmachineadmin API path) +# Publish — operator → ATM (the spirekeeper API path) # ============================================================================= diff --git a/config.json b/config.json index 8c1f39e..86c2e42 100644 --- a/config.json +++ b/config.json @@ -1,7 +1,7 @@ { - "name": "DCA Admin", + "name": "spirekeeper", "short_description": "Dollar Cost Averaging administration for Lamassu ATM integration", - "tile": "/satmachineadmin/static/image/aio.png", + "tile": "/spirekeeper/static/image/aio.png", "min_lnbits_version": "1.0.0", "contributors": [ { @@ -26,7 +26,7 @@ } ], "images": [], - "description_md": "/satmachineadmin/description.md", - "terms_and_conditions_md": "/satmachineadmin/toc.md", + "description_md": "/spirekeeper/description.md", + "terms_and_conditions_md": "/spirekeeper/toc.md", "license": "MIT" } diff --git a/crud.py b/crud.py index 444a984..9646f8d 100644 --- a/crud.py +++ b/crud.py @@ -38,7 +38,7 @@ from .models import ( UpsertDcaLpData, ) -db = Database("ext_satoshimachine") +db = Database("ext_spirekeeper") # ============================================================================= @@ -48,7 +48,7 @@ db = Database("ext_satoshimachine") async def get_super_config() -> SuperConfig | None: return await db.fetchone( - "SELECT * FROM satoshimachine.super_config WHERE id = :id", + "SELECT * FROM spirekeeper.super_config WHERE id = :id", {"id": "default"}, SuperConfig, ) @@ -62,7 +62,7 @@ async def update_super_config(data: UpdateSuperConfigData) -> SuperConfig | None set_clause = ", ".join(f"{k} = :{k}" for k in update_data) update_data["id"] = "default" await db.execute( - f"UPDATE satoshimachine.super_config SET {set_clause} WHERE id = :id", + f"UPDATE spirekeeper.super_config SET {set_clause} WHERE id = :id", update_data, ) return await get_super_config() @@ -78,7 +78,7 @@ async def create_machine(operator_user_id: str, data: CreateMachineData) -> Mach now = datetime.now() await db.execute( """ - INSERT INTO satoshimachine.dca_machines + INSERT INTO spirekeeper.dca_machines (id, operator_user_id, machine_npub, wallet_id, name, location, fiat_code, is_active, operator_cash_in_fee_fraction, operator_cash_out_fee_fraction, @@ -110,7 +110,7 @@ async def create_machine(operator_user_id: str, data: CreateMachineData) -> Mach async def get_machine(machine_id: str) -> Machine | None: return await db.fetchone( - "SELECT * FROM satoshimachine.dca_machines WHERE id = :id", + "SELECT * FROM spirekeeper.dca_machines WHERE id = :id", {"id": machine_id}, Machine, ) @@ -118,7 +118,7 @@ async def get_machine(machine_id: str) -> Machine | None: async def get_machine_by_npub(machine_npub: str) -> Machine | None: return await db.fetchone( - "SELECT * FROM satoshimachine.dca_machines WHERE machine_npub = :npub", + "SELECT * FROM spirekeeper.dca_machines WHERE machine_npub = :npub", {"npub": machine_npub}, Machine, ) @@ -128,7 +128,7 @@ async def get_active_machine_by_wallet_id(wallet_id: str) -> Machine | None: """Used by the invoice listener to route an incoming payment to a machine.""" return await db.fetchone( """ - SELECT * FROM satoshimachine.dca_machines + SELECT * FROM spirekeeper.dca_machines WHERE wallet_id = :wid AND is_active = true LIMIT 1 """, @@ -140,7 +140,7 @@ async def get_active_machine_by_wallet_id(wallet_id: str) -> Machine | None: async def get_machines_for_operator(operator_user_id: str) -> list[Machine]: return await db.fetchall( """ - SELECT * FROM satoshimachine.dca_machines + SELECT * FROM spirekeeper.dca_machines WHERE operator_user_id = :uid ORDER BY created_at DESC """, @@ -157,7 +157,7 @@ async def list_all_active_machines() -> list[Machine]: """ return await db.fetchall( """ - SELECT * FROM satoshimachine.dca_machines + SELECT * FROM spirekeeper.dca_machines WHERE is_active = true ORDER BY created_at DESC """, @@ -196,7 +196,7 @@ async def update_machine(machine_id: str, data: UpdateMachineData) -> Machine | set_clause = ", ".join(f"{k} = :{k}" for k in update_data) update_data["id"] = machine_id await db.execute( - f"UPDATE satoshimachine.dca_machines SET {set_clause} WHERE id = :id", + f"UPDATE spirekeeper.dca_machines SET {set_clause} WHERE id = :id", update_data, ) return await get_machine(machine_id) @@ -204,7 +204,7 @@ async def update_machine(machine_id: str, data: UpdateMachineData) -> Machine | async def delete_machine(machine_id: str) -> None: await db.execute( - "DELETE FROM satoshimachine.dca_machines WHERE id = :id", + "DELETE FROM spirekeeper.dca_machines WHERE id = :id", {"id": machine_id}, ) @@ -226,7 +226,7 @@ async def create_dca_client(data: CreateDcaClientData) -> DcaClient: now = datetime.now() await db.execute( """ - INSERT INTO satoshimachine.dca_clients + INSERT INTO spirekeeper.dca_clients (id, machine_id, user_id, username, status, created_at, updated_at) VALUES (:id, :machine_id, :user_id, :username, :status, :created_at, :updated_at) @@ -255,8 +255,8 @@ _CLIENT_SELECT = """ (lp.user_id IS NOT NULL) AS lp_onboarded """ _CLIENT_FROM = ( - "satoshimachine.dca_clients c " - "LEFT JOIN satoshimachine.dca_lp lp ON lp.user_id = c.user_id" + "spirekeeper.dca_clients c " + "LEFT JOIN spirekeeper.dca_lp lp ON lp.user_id = c.user_id" ) @@ -299,7 +299,7 @@ async def get_dca_clients_for_operator(operator_user_id: str) -> list[DcaClient] f""" SELECT {_CLIENT_SELECT} FROM {_CLIENT_FROM} - JOIN satoshimachine.dca_machines m ON m.id = c.machine_id + JOIN spirekeeper.dca_machines m ON m.id = c.machine_id WHERE m.operator_user_id = :uid ORDER BY c.created_at DESC """, @@ -332,8 +332,8 @@ async def get_flow_mode_clients_for_machine(machine_id: str) -> list[DcaClient]: return await db.fetchall( """ SELECT c.* - FROM satoshimachine.dca_clients c - JOIN satoshimachine.dca_lp lp ON lp.user_id = c.user_id + FROM spirekeeper.dca_clients c + JOIN spirekeeper.dca_lp lp ON lp.user_id = c.user_id WHERE c.machine_id = :machine_id AND lp.default_dca_mode = 'flow' AND c.status = 'active' @@ -353,7 +353,7 @@ async def get_dca_lp(user_id: str) -> DcaLpPreferences | None: """Return the LP's preferences row, or None if they haven't onboarded via satmachineclient yet.""" return await db.fetchone( - "SELECT * FROM satoshimachine.dca_lp WHERE user_id = :uid", + "SELECT * FROM spirekeeper.dca_lp WHERE user_id = :uid", {"uid": user_id}, DcaLpPreferences, ) @@ -362,7 +362,7 @@ async def get_dca_lp(user_id: str) -> DcaLpPreferences | None: async def lp_is_onboarded(user_id: str) -> bool: """Cheap existence check used by the deposit-creation gate.""" row = await db.fetchone( - "SELECT user_id FROM satoshimachine.dca_lp WHERE user_id = :uid", + "SELECT user_id FROM spirekeeper.dca_lp WHERE user_id = :uid", {"uid": user_id}, ) return row is not None @@ -392,7 +392,7 @@ async def upsert_dca_lp( ) await db.execute( """ - INSERT INTO satoshimachine.dca_lp + INSERT INTO spirekeeper.dca_lp (user_id, dca_wallet_id, default_dca_mode, fixed_mode_daily_limit, autoforward_ln_address, autoforward_enabled, created_at, updated_at) @@ -417,7 +417,7 @@ async def upsert_dca_lp( set_clause = ", ".join(f"{k} = :{k}" for k in update_data) update_data["uid"] = user_id await db.execute( - f"UPDATE satoshimachine.dca_lp SET {set_clause} WHERE user_id = :uid", + f"UPDATE spirekeeper.dca_lp SET {set_clause} WHERE user_id = :uid", update_data, ) refreshed = await get_dca_lp(user_id) @@ -435,7 +435,7 @@ async def update_dca_client( set_clause = ", ".join(f"{k} = :{k}" for k in update_data) update_data["id"] = client_id await db.execute( - f"UPDATE satoshimachine.dca_clients SET {set_clause} WHERE id = :id", + f"UPDATE spirekeeper.dca_clients SET {set_clause} WHERE id = :id", update_data, ) return await get_dca_client(client_id) @@ -443,7 +443,7 @@ async def update_dca_client( async def delete_dca_client(client_id: str) -> None: await db.execute( - "DELETE FROM satoshimachine.dca_clients WHERE id = :id", + "DELETE FROM spirekeeper.dca_clients WHERE id = :id", {"id": client_id}, ) @@ -466,7 +466,7 @@ async def create_deposit( deposit_id = urlsafe_short_hash() await db.execute( """ - INSERT INTO satoshimachine.dca_deposits + INSERT INTO spirekeeper.dca_deposits (id, client_id, machine_id, creator_user_id, amount, currency, status, notes, created_at) VALUES (:id, :client_id, :machine_id, :creator_user_id, :amount, @@ -491,7 +491,7 @@ async def create_deposit( async def get_deposit(deposit_id: str) -> DcaDeposit | None: return await db.fetchone( - "SELECT * FROM satoshimachine.dca_deposits WHERE id = :id", + "SELECT * FROM spirekeeper.dca_deposits WHERE id = :id", {"id": deposit_id}, DcaDeposit, ) @@ -500,7 +500,7 @@ async def get_deposit(deposit_id: str) -> DcaDeposit | None: async def get_deposits_for_client(client_id: str) -> list[DcaDeposit]: return await db.fetchall( """ - SELECT * FROM satoshimachine.dca_deposits + SELECT * FROM spirekeeper.dca_deposits WHERE client_id = :client_id ORDER BY created_at DESC """, @@ -513,8 +513,8 @@ async def get_deposits_for_operator(operator_user_id: str) -> list[DcaDeposit]: return await db.fetchall( """ SELECT d.* - FROM satoshimachine.dca_deposits d - JOIN satoshimachine.dca_machines m ON m.id = d.machine_id + FROM spirekeeper.dca_deposits d + JOIN spirekeeper.dca_machines m ON m.id = d.machine_id WHERE m.operator_user_id = :uid ORDER BY d.created_at DESC """, @@ -532,7 +532,7 @@ async def update_deposit( set_clause = ", ".join(f"{k} = :{k}" for k in update_data) update_data["id"] = deposit_id await db.execute( - f"UPDATE satoshimachine.dca_deposits SET {set_clause} WHERE id = :id", + f"UPDATE spirekeeper.dca_deposits SET {set_clause} WHERE id = :id", update_data, ) return await get_deposit(deposit_id) @@ -549,7 +549,7 @@ async def update_deposit_status( } await db.execute( """ - UPDATE satoshimachine.dca_deposits + UPDATE spirekeeper.dca_deposits SET status = :status, notes = COALESCE(:notes, notes), confirmed_at = COALESCE(:confirmed_at, confirmed_at) @@ -562,7 +562,7 @@ async def update_deposit_status( async def delete_deposit(deposit_id: str) -> None: await db.execute( - "DELETE FROM satoshimachine.dca_deposits WHERE id = :id", + "DELETE FROM spirekeeper.dca_deposits WHERE id = :id", {"id": deposit_id}, ) @@ -598,7 +598,7 @@ async def create_settlement_idempotent( settlement_id = urlsafe_short_hash() await db.execute( """ - INSERT INTO satoshimachine.dca_settlements + INSERT INTO spirekeeper.dca_settlements (id, machine_id, payment_hash, bitspire_event_id, bitspire_txid, wire_sats, fiat_amount, fiat_code, exchange_rate, principal_sats, fee_sats, platform_fee_sats, operator_fee_sats, fee_mismatch_sats, @@ -639,7 +639,7 @@ async def create_settlement_idempotent( async def get_settlement(settlement_id: str) -> DcaSettlement | None: return await db.fetchone( - "SELECT * FROM satoshimachine.dca_settlements WHERE id = :id", + "SELECT * FROM spirekeeper.dca_settlements WHERE id = :id", {"id": settlement_id}, DcaSettlement, ) @@ -650,7 +650,7 @@ async def get_settlement_by_payment_hash( ) -> DcaSettlement | None: return await db.fetchone( """ - SELECT * FROM satoshimachine.dca_settlements + SELECT * FROM spirekeeper.dca_settlements WHERE payment_hash = :hash """, {"hash": payment_hash}, @@ -663,7 +663,7 @@ async def get_settlements_for_machine( ) -> list[DcaSettlement]: return await db.fetchall( """ - SELECT * FROM satoshimachine.dca_settlements + SELECT * FROM spirekeeper.dca_settlements WHERE machine_id = :machine_id ORDER BY created_at DESC LIMIT :lim @@ -698,8 +698,8 @@ async def get_stuck_settlements_for_operator( rejected = await db.fetchall( """ SELECT s.* - FROM satoshimachine.dca_settlements s - JOIN satoshimachine.dca_machines m ON m.id = s.machine_id + FROM spirekeeper.dca_settlements s + JOIN spirekeeper.dca_machines m ON m.id = s.machine_id WHERE m.operator_user_id = :uid AND s.status = 'rejected' ORDER BY s.created_at DESC """, @@ -709,8 +709,8 @@ async def get_stuck_settlements_for_operator( errored = await db.fetchall( """ SELECT s.* - FROM satoshimachine.dca_settlements s - JOIN satoshimachine.dca_machines m ON m.id = s.machine_id + FROM spirekeeper.dca_settlements s + JOIN spirekeeper.dca_machines m ON m.id = s.machine_id WHERE m.operator_user_id = :uid AND s.status = 'errored' ORDER BY s.created_at DESC """, @@ -720,8 +720,8 @@ async def get_stuck_settlements_for_operator( stuck_pending = await db.fetchall( """ SELECT s.* - FROM satoshimachine.dca_settlements s - JOIN satoshimachine.dca_machines m ON m.id = s.machine_id + FROM spirekeeper.dca_settlements s + JOIN spirekeeper.dca_machines m ON m.id = s.machine_id WHERE m.operator_user_id = :uid AND s.status = 'pending' AND s.created_at < :threshold @@ -733,8 +733,8 @@ async def get_stuck_settlements_for_operator( stuck_processing = await db.fetchall( """ SELECT s.* - FROM satoshimachine.dca_settlements s - JOIN satoshimachine.dca_machines m ON m.id = s.machine_id + FROM spirekeeper.dca_settlements s + JOIN spirekeeper.dca_machines m ON m.id = s.machine_id WHERE m.operator_user_id = :uid AND s.status = 'processing' AND s.created_at < :threshold @@ -763,7 +763,7 @@ async def force_reset_stuck_settlement( decision.""" await db.execute( """ - UPDATE satoshimachine.dca_settlements + UPDATE spirekeeper.dca_settlements SET status = 'errored', processing_claim = NULL, error_message = 'force-reset by operator (was stuck)' @@ -780,8 +780,8 @@ async def get_settlements_for_operator( return await db.fetchall( """ SELECT s.* - FROM satoshimachine.dca_settlements s - JOIN satoshimachine.dca_machines m ON m.id = s.machine_id + FROM spirekeeper.dca_settlements s + JOIN spirekeeper.dca_machines m ON m.id = s.machine_id WHERE m.operator_user_id = :uid ORDER BY s.created_at DESC LIMIT :lim @@ -801,7 +801,7 @@ async def mark_settlement_status( fresh claim attempt won't see a stale token.""" await db.execute( """ - UPDATE satoshimachine.dca_settlements + UPDATE spirekeeper.dca_settlements SET status = :status, error_message = :err, processed_at = CASE @@ -840,7 +840,7 @@ async def claim_settlement_for_processing( token = urlsafe_short_hash() await db.execute( """ - UPDATE satoshimachine.dca_settlements + UPDATE spirekeeper.dca_settlements SET status = 'processing', processing_claim = :token WHERE id = :id AND status = 'pending' """, @@ -862,7 +862,7 @@ async def reset_settlement_for_retry( are left in place — we never re-pay sats that already moved.""" await db.execute( """ - UPDATE satoshimachine.dca_payments + UPDATE spirekeeper.dca_payments SET status = 'voided' WHERE settlement_id = :sid AND status = 'failed' """, @@ -870,7 +870,7 @@ async def reset_settlement_for_retry( ) await db.execute( """ - UPDATE satoshimachine.dca_settlements + UPDATE spirekeeper.dca_settlements SET status = 'pending', error_message = NULL, processing_claim = NULL, @@ -902,7 +902,7 @@ async def apply_partial_dispense( can re-distribute via the existing idempotent path.""" await db.execute( """ - UPDATE satoshimachine.dca_settlements + UPDATE spirekeeper.dca_settlements SET wire_sats = :gross, principal_sats = :principal, fee_sats = :commission, @@ -937,7 +937,7 @@ async def count_completed_legs_for_settlement(settlement_id: str) -> int: successfully moved sats (Lightning payments can't be clawed back).""" row = await db.fetchone( """ - SELECT COUNT(*) AS n FROM satoshimachine.dca_payments + SELECT COUNT(*) AS n FROM spirekeeper.dca_payments WHERE settlement_id = :sid AND status = 'completed' """, {"sid": settlement_id}, @@ -957,7 +957,7 @@ async def append_settlement_note( formatted = f"[{ts} by {author_user_id}] {note}" await db.execute( """ - UPDATE satoshimachine.dca_settlements + UPDATE spirekeeper.dca_settlements SET notes = CASE WHEN notes IS NULL OR notes = '' THEN :note ELSE :note || char(10) || char(10) || notes @@ -977,7 +977,7 @@ async def void_open_legs_for_settlement(settlement_id: str) -> None: writes its own (possibly different) skipped reasons.""" await db.execute( """ - UPDATE satoshimachine.dca_payments + UPDATE spirekeeper.dca_payments SET status = 'voided' WHERE settlement_id = :sid AND status IN ('pending', 'failed', 'skipped') @@ -1002,7 +1002,7 @@ async def get_commission_splits( if machine_id is None: return await db.fetchall( """ - SELECT * FROM satoshimachine.dca_commission_splits + SELECT * FROM spirekeeper.dca_commission_splits WHERE operator_user_id = :uid AND machine_id IS NULL ORDER BY sort_order ASC """, @@ -1011,7 +1011,7 @@ async def get_commission_splits( ) return await db.fetchall( """ - SELECT * FROM satoshimachine.dca_commission_splits + SELECT * FROM spirekeeper.dca_commission_splits WHERE operator_user_id = :uid AND machine_id = :mid ORDER BY sort_order ASC """, @@ -1040,7 +1040,7 @@ async def replace_commission_splits( if machine_id is None: await db.execute( """ - DELETE FROM satoshimachine.dca_commission_splits + DELETE FROM spirekeeper.dca_commission_splits WHERE operator_user_id = :uid AND machine_id IS NULL """, {"uid": operator_user_id}, @@ -1048,7 +1048,7 @@ async def replace_commission_splits( else: await db.execute( """ - DELETE FROM satoshimachine.dca_commission_splits + DELETE FROM spirekeeper.dca_commission_splits WHERE operator_user_id = :uid AND machine_id = :mid """, {"uid": operator_user_id, "mid": machine_id}, @@ -1057,7 +1057,7 @@ async def replace_commission_splits( for leg in legs: await db.execute( """ - INSERT INTO satoshimachine.dca_commission_splits + INSERT INTO spirekeeper.dca_commission_splits (id, machine_id, operator_user_id, target, label, fraction, sort_order, created_at) VALUES (:id, :machine_id, :uid, :target, :label, :fraction, @@ -1086,7 +1086,7 @@ async def create_dca_payment(data: CreateDcaPaymentData) -> DcaPayment: payment_id = urlsafe_short_hash() await db.execute( """ - INSERT INTO satoshimachine.dca_payments + INSERT INTO spirekeeper.dca_payments (id, settlement_id, client_id, machine_id, operator_user_id, leg_type, destination_wallet_id, destination_ln_address, amount_sats, amount_fiat, exchange_rate, transaction_time, @@ -1122,7 +1122,7 @@ async def create_dca_payment(data: CreateDcaPaymentData) -> DcaPayment: async def get_dca_payment(payment_id: str) -> DcaPayment | None: return await db.fetchone( - "SELECT * FROM satoshimachine.dca_payments WHERE id = :id", + "SELECT * FROM spirekeeper.dca_payments WHERE id = :id", {"id": payment_id}, DcaPayment, ) @@ -1131,7 +1131,7 @@ async def get_dca_payment(payment_id: str) -> DcaPayment | None: async def get_payments_for_settlement(settlement_id: str) -> list[DcaPayment]: return await db.fetchall( """ - SELECT * FROM satoshimachine.dca_payments + SELECT * FROM spirekeeper.dca_payments WHERE settlement_id = :sid ORDER BY created_at ASC """, @@ -1143,7 +1143,7 @@ async def get_payments_for_settlement(settlement_id: str) -> list[DcaPayment]: async def get_payments_for_client(client_id: str) -> list[DcaPayment]: return await db.fetchall( """ - SELECT * FROM satoshimachine.dca_payments + SELECT * FROM spirekeeper.dca_payments WHERE client_id = :cid ORDER BY created_at DESC """, @@ -1158,7 +1158,7 @@ async def get_payments_for_operator( if leg_type is None: return await db.fetchall( """ - SELECT * FROM satoshimachine.dca_payments + SELECT * FROM spirekeeper.dca_payments WHERE operator_user_id = :uid ORDER BY created_at DESC LIMIT :lim @@ -1168,7 +1168,7 @@ async def get_payments_for_operator( ) return await db.fetchall( """ - SELECT * FROM satoshimachine.dca_payments + SELECT * FROM spirekeeper.dca_payments WHERE operator_user_id = :uid AND leg_type = :leg ORDER BY created_at DESC LIMIT :lim @@ -1186,7 +1186,7 @@ async def update_payment_status( ) -> DcaPayment | None: await db.execute( """ - UPDATE satoshimachine.dca_payments + UPDATE spirekeeper.dca_payments SET status = :status, external_payment_hash = COALESCE(:hash, external_payment_hash), error_message = :err @@ -1221,7 +1221,7 @@ async def get_client_balance_summary( deposits_row = await db.fetchone( """ SELECT COALESCE(SUM(amount), 0) AS total - FROM satoshimachine.dca_deposits + FROM spirekeeper.dca_deposits WHERE client_id = :cid AND status = 'confirmed' """, {"cid": client_id}, @@ -1231,7 +1231,7 @@ async def get_client_balance_summary( payments_row = await db.fetchone( """ SELECT COALESCE(SUM(amount_fiat), 0) AS total - FROM satoshimachine.dca_payments + FROM spirekeeper.dca_payments WHERE client_id = :cid AND leg_type IN ('dca', 'settlement') AND status = 'completed' @@ -1260,7 +1260,7 @@ async def get_client_balance_summary( async def get_telemetry(machine_id: str) -> TelemetrySnapshot | None: return await db.fetchone( - "SELECT * FROM satoshimachine.dca_telemetry WHERE machine_id = :mid", + "SELECT * FROM spirekeeper.dca_telemetry WHERE machine_id = :mid", {"mid": machine_id}, TelemetrySnapshot, ) @@ -1290,7 +1290,7 @@ async def upsert_beacon_snapshot( if existing is None: await db.execute( """ - INSERT INTO satoshimachine.dca_telemetry + INSERT INTO spirekeeper.dca_telemetry (machine_id, beacon_cash_in, beacon_cash_out, beacon_cash_level, beacon_fiat, beacon_model, beacon_name, beacon_location, beacon_geo, beacon_fees_json, beacon_limits_json, @@ -1319,7 +1319,7 @@ async def upsert_beacon_snapshot( else: await db.execute( """ - UPDATE satoshimachine.dca_telemetry SET + UPDATE spirekeeper.dca_telemetry SET beacon_cash_in = COALESCE(:cash_in, beacon_cash_in), beacon_cash_out = COALESCE(:cash_out, beacon_cash_out), beacon_cash_level = COALESCE(:cash_level, beacon_cash_level), @@ -1366,7 +1366,7 @@ async def upsert_fleet_snapshot( if existing is None: await db.execute( """ - INSERT INTO satoshimachine.dca_telemetry + INSERT INTO spirekeeper.dca_telemetry (machine_id, telemetry_json, telemetry_received_at) VALUES (:mid, :json, :now) """, @@ -1375,7 +1375,7 @@ async def upsert_fleet_snapshot( else: await db.execute( """ - UPDATE satoshimachine.dca_telemetry + UPDATE spirekeeper.dca_telemetry SET telemetry_json = :json, telemetry_received_at = :now WHERE machine_id = :mid """, @@ -1416,7 +1416,7 @@ async def get_cassette_config( machine_id: str, position: int ) -> CassetteConfig | None: return await db.fetchone( - "SELECT * FROM satoshimachine.cassette_configs " + "SELECT * FROM spirekeeper.cassette_configs " "WHERE machine_id = :mid AND position = :pos", {"mid": machine_id, "pos": position}, CassetteConfig, @@ -1427,7 +1427,7 @@ async def list_cassette_configs_for_machine( machine_id: str, ) -> list[CassetteConfig]: return await db.fetchall( - "SELECT * FROM satoshimachine.cassette_configs " + "SELECT * FROM spirekeeper.cassette_configs " "WHERE machine_id = :mid ORDER BY position", {"mid": machine_id}, CassetteConfig, @@ -1459,7 +1459,7 @@ async def update_cassette_config( update_data["mid"] = machine_id update_data["pos"] = position await db.execute( - f"UPDATE satoshimachine.cassette_configs SET {set_clause} " + f"UPDATE spirekeeper.cassette_configs SET {set_clause} " "WHERE machine_id = :mid AND position = :pos", update_data, ) @@ -1487,7 +1487,7 @@ async def apply_bootstrap_state( events land + the operator subsequently edits. """ existing_first: dict | None = await db.fetchone( - "SELECT state_event_id FROM satoshimachine.cassette_configs " + "SELECT state_event_id FROM spirekeeper.cassette_configs " "WHERE machine_id = :mid LIMIT 1", {"mid": machine_id}, ) @@ -1505,7 +1505,7 @@ async def apply_bootstrap_state( for pos, row in payload.positions.items(): await db.execute( """ - INSERT INTO satoshimachine.cassette_configs + INSERT INTO spirekeeper.cassette_configs (machine_id, position, denomination, count, updated_at, updated_by, state_denomination, state_count, state_at, state_event_id) diff --git a/fee_transport.py b/fee_transport.py index 1dc0ec7..0911b12 100644 --- a/fee_transport.py +++ b/fee_transport.py @@ -132,7 +132,7 @@ async def publish_fee_config( ) except (SignerUnavailable, RelayUnavailable) as exc: logger.warning( - f"satmachineadmin: fee-config publish soft-fail for machine " + f"spirekeeper: fee-config publish soft-fail for machine " f"{machine.id} ({machine.name or machine.machine_npub[:12]}): " f"{type(exc).__name__}: {exc}. Underlying CRUD operation " "succeeded; operator can re-trigger publish via the next " @@ -144,7 +144,7 @@ async def publish_fee_config( # don't break the caller's CRUD path; a future publish attempt # (next machine edit / next super edit) will retry. logger.warning( - f"satmachineadmin: fee-config publish unexpected transport " + f"spirekeeper: fee-config publish unexpected transport " f"error for machine {machine.id}: {type(exc).__name__}: {exc}" ) return None diff --git a/manifest.json b/manifest.json index a989ad2..d161d2e 100644 --- a/manifest.json +++ b/manifest.json @@ -1,9 +1,9 @@ { "repos": [ { - "id": "satmachineadmin", - "organisation": "atitlanio", - "repository": "satmachineadmin" + "id": "spirekeeper", + "organisation": "aiolabs", + "repository": "spirekeeper" } ] } diff --git a/migrations.py b/migrations.py index 97e4a68..5cbe5be 100644 --- a/migrations.py +++ b/migrations.py @@ -56,11 +56,11 @@ async def m001_satmachine_v2_initial(db): "dca_deposits", "dca_clients", ): - await db.execute(f"DROP TABLE IF EXISTS satoshimachine.{table}") + await db.execute(f"DROP TABLE IF EXISTS spirekeeper.{table}") # 2. super_config — singleton (id='default') with platform-fee config. await db.execute(f""" - CREATE TABLE IF NOT EXISTS satoshimachine.super_config ( + CREATE TABLE IF NOT EXISTS spirekeeper.super_config ( id TEXT PRIMARY KEY, super_fee_fraction DECIMAL(10,4) NOT NULL DEFAULT 0.0000, super_fee_wallet_id TEXT, @@ -68,18 +68,18 @@ async def m001_satmachine_v2_initial(db): ); """) existing = await db.fetchone( - "SELECT id FROM satoshimachine.super_config WHERE id = 'default'" + "SELECT id FROM spirekeeper.super_config WHERE id = 'default'" ) if not existing: await db.execute( - "INSERT INTO satoshimachine.super_config (id, super_fee_fraction) " + "INSERT INTO spirekeeper.super_config (id, super_fee_fraction) " "VALUES ('default', 0.0000)" ) # 3. dca_machines — one row per bitSpire ATM, owned by exactly one # operator. wallet_id UNIQUE prevents the IDOR funds-theft vector. await db.execute(f""" - CREATE TABLE IF NOT EXISTS satoshimachine.dca_machines ( + CREATE TABLE IF NOT EXISTS spirekeeper.dca_machines ( id TEXT PRIMARY KEY, operator_user_id TEXT NOT NULL, machine_npub TEXT NOT NULL UNIQUE, @@ -107,7 +107,7 @@ async def m001_satmachine_v2_initial(db): # just decides "this LP is enrolled at my machine"; everything # delivery-related is the LP's own preference. await db.execute(f""" - CREATE TABLE IF NOT EXISTS satoshimachine.dca_clients ( + CREATE TABLE IF NOT EXISTS spirekeeper.dca_clients ( id TEXT PRIMARY KEY, machine_id TEXT NOT NULL, user_id TEXT NOT NULL, @@ -129,17 +129,17 @@ async def m001_satmachine_v2_initial(db): # user that has onboarded as a Liquidity Provider, regardless of # how many machines they're enrolled at. Owned by the LP (writes # come from the satmachineclient extension under the LP's session), - # read by satmachineadmin during distribution to resolve "where do + # read by spirekeeper during distribution to resolve "where do # DCA payouts for this LP go?" # - # Gating: satmachineadmin refuses to create deposits for an LP who + # Gating: spirekeeper refuses to create deposits for an LP who # doesn't have a dca_lp row yet. The LP must onboard via # satmachineclient first (which auto-creates the row with their # default LNbits wallet on first dashboard visit). Forces every # LP through a "yes, I am here and this is where I want my sats" # gesture before any fiat starts accumulating against them. await db.execute(f""" - CREATE TABLE IF NOT EXISTS satoshimachine.dca_lp ( + CREATE TABLE IF NOT EXISTS spirekeeper.dca_lp ( user_id TEXT PRIMARY KEY, dca_wallet_id TEXT NOT NULL, default_dca_mode TEXT NOT NULL DEFAULT 'flow', @@ -154,7 +154,7 @@ async def m001_satmachine_v2_initial(db): # 5. dca_deposits — fiat the operator (or super) records against an LP # at a machine. creator_user_id preserves audit trail. await db.execute(f""" - CREATE TABLE IF NOT EXISTS satoshimachine.dca_deposits ( + CREATE TABLE IF NOT EXISTS spirekeeper.dca_deposits ( id TEXT PRIMARY KEY, client_id TEXT NOT NULL, machine_id TEXT NOT NULL, @@ -184,7 +184,7 @@ async def m001_satmachine_v2_initial(db): # forgave what per transaction. Do not collapse them into a single # fee_fraction. See plan section "Customer discounts" and #10. await db.execute(f""" - CREATE TABLE IF NOT EXISTS satoshimachine.dca_settlements ( + CREATE TABLE IF NOT EXISTS spirekeeper.dca_settlements ( id TEXT PRIMARY KEY, machine_id TEXT NOT NULL, payment_hash TEXT NOT NULL UNIQUE, @@ -227,7 +227,7 @@ async def m001_satmachine_v2_initial(db): # - LNURL string (bech32 LNURL...) # Resolution lives in distribution._pay_one_split_leg. await db.execute(f""" - CREATE TABLE IF NOT EXISTS satoshimachine.dca_commission_splits ( + CREATE TABLE IF NOT EXISTS spirekeeper.dca_commission_splits ( id TEXT PRIMARY KEY, machine_id TEXT, operator_user_id TEXT NOT NULL, @@ -248,7 +248,7 @@ async def m001_satmachine_v2_initial(db): # autoforward | refund. status enum: pending | completed | failed | # voided | skipped | refunded. await db.execute(f""" - CREATE TABLE IF NOT EXISTS satoshimachine.dca_payments ( + CREATE TABLE IF NOT EXISTS spirekeeper.dca_payments ( id TEXT PRIMARY KEY, settlement_id TEXT, client_id TEXT, @@ -287,7 +287,7 @@ async def m001_satmachine_v2_initial(db): # (name, location, geo, fees, limits, denominations, version) are # nullable until that upstream issue lands. Ingest opportunistically. await db.execute(""" - CREATE TABLE IF NOT EXISTS satoshimachine.dca_telemetry ( + CREATE TABLE IF NOT EXISTS spirekeeper.dca_telemetry ( machine_id TEXT PRIMARY KEY, beacon_cash_in BOOLEAN, beacon_cash_out BOOLEAN, @@ -315,7 +315,7 @@ async def m002_rename_commission_split_wallet_id_to_target(db): EXISTS`, which is a no-op when the table already exists — so the schema drift survives the documented uninstall + reinstall workflow because LNbits' uninstall wipes the dbversions tracker but NOT the - satoshimachine.sqlite3 file on disk. + spirekeeper.sqlite3 file on disk. Idempotent: probes for the `wallet_id` column via a SELECT. If the probe succeeds the column still exists and we RENAME it; otherwise @@ -326,14 +326,14 @@ async def m002_rename_commission_split_wallet_id_to_target(db): """ try: await db.fetchone( - "SELECT wallet_id FROM satoshimachine.dca_commission_splits LIMIT 1" + "SELECT wallet_id FROM spirekeeper.dca_commission_splits LIMIT 1" ) except Exception: # wallet_id column doesn't exist; either m001 produced the correct # schema on a fresh install or the rename already landed. return await db.execute( - "ALTER TABLE satoshimachine.dca_commission_splits " + "ALTER TABLE spirekeeper.dca_commission_splits " "RENAME COLUMN wallet_id TO target" ) @@ -350,11 +350,11 @@ async def m003_rename_settlements_net_sats_to_principal_sats(db): Idempotent: probes for the old `net_sats` column. If present, rename. """ try: - await db.fetchone("SELECT net_sats FROM satoshimachine.dca_settlements LIMIT 1") + await db.fetchone("SELECT net_sats FROM spirekeeper.dca_settlements LIMIT 1") except Exception: return await db.execute( - "ALTER TABLE satoshimachine.dca_settlements " + "ALTER TABLE spirekeeper.dca_settlements " "RENAME COLUMN net_sats TO principal_sats" ) @@ -383,14 +383,14 @@ async def m004_introduce_dca_lp_table(db): absent the install already on the new shape; no-op. """ try: - await db.fetchone("SELECT wallet_id FROM satoshimachine.dca_clients LIMIT 1") + await db.fetchone("SELECT wallet_id FROM spirekeeper.dca_clients LIMIT 1") except Exception: return # Step 1: create dca_lp if it doesn't exist yet. m001 on a fresh install # already created it; on a pre-m004 install we're creating it here. await db.execute(f""" - CREATE TABLE IF NOT EXISTS satoshimachine.dca_lp ( + CREATE TABLE IF NOT EXISTS spirekeeper.dca_lp ( user_id TEXT PRIMARY KEY, dca_wallet_id TEXT NOT NULL, default_dca_mode TEXT NOT NULL DEFAULT 'flow', @@ -407,7 +407,7 @@ async def m004_introduce_dca_lp_table(db): # enrolled at multiple machines — that row reflects their most # recent intent. ROW_NUMBER() OVER (...) requires SQLite 3.25+ (2018). await db.execute(""" - INSERT OR IGNORE INTO satoshimachine.dca_lp + INSERT OR IGNORE INTO spirekeeper.dca_lp (user_id, dca_wallet_id, default_dca_mode, fixed_mode_daily_limit, autoforward_ln_address, autoforward_enabled, created_at, updated_at) @@ -419,7 +419,7 @@ async def m004_introduce_dca_lp_table(db): PARTITION BY user_id ORDER BY updated_at DESC, created_at DESC ) AS rn - FROM satoshimachine.dca_clients + FROM spirekeeper.dca_clients ) ranked WHERE rn = 1 """) @@ -434,7 +434,7 @@ async def m004_introduce_dca_lp_table(db): "autoforward_ln_address", "autoforward_enabled", ): - await db.execute(f"ALTER TABLE satoshimachine.dca_clients DROP COLUMN {col}") + await db.execute(f"ALTER TABLE spirekeeper.dca_clients DROP COLUMN {col}") async def m006_rename_to_canonical_sat_vocabulary(db): @@ -474,13 +474,13 @@ async def m006_rename_to_canonical_sat_vocabulary(db): ] for table, old_col, new_col in renames: try: - await db.fetchone(f"SELECT {old_col} FROM satoshimachine.{table} LIMIT 1") + await db.fetchone(f"SELECT {old_col} FROM spirekeeper.{table} LIMIT 1") except Exception: # old column doesn't exist; either rename already landed or # m001 produced the canonical schema directly on fresh install. continue await db.execute( - f"ALTER TABLE satoshimachine.{table} " + f"ALTER TABLE spirekeeper.{table} " f"RENAME COLUMN {old_col} TO {new_col}" ) @@ -494,11 +494,11 @@ async def m006_rename_to_canonical_sat_vocabulary(db): ] for table, col in drops: try: - await db.fetchone(f"SELECT {col} FROM satoshimachine.{table} LIMIT 1") + await db.fetchone(f"SELECT {col} FROM spirekeeper.{table} LIMIT 1") except Exception: # column doesn't exist; either already dropped or never present. continue - await db.execute(f"ALTER TABLE satoshimachine.{table} DROP COLUMN {col}") + await db.execute(f"ALTER TABLE spirekeeper.{table} DROP COLUMN {col}") async def m005_lock_deposit_currency_to_machine_fiat_code(db): @@ -518,15 +518,15 @@ async def m005_lock_deposit_currency_to_machine_fiat_code(db): no mismatches it's a no-op UPDATE. """ await db.execute(""" - UPDATE satoshimachine.dca_deposits AS d + UPDATE spirekeeper.dca_deposits AS d SET currency = ( SELECT m.fiat_code - FROM satoshimachine.dca_machines m + FROM spirekeeper.dca_machines m WHERE m.id = d.machine_id ) WHERE EXISTS ( SELECT 1 - FROM satoshimachine.dca_machines m + FROM spirekeeper.dca_machines m WHERE m.id = d.machine_id AND m.fiat_code IS NOT NULL AND m.fiat_code != d.currency @@ -538,7 +538,7 @@ async def m007_add_cassette_configs(db): """Add cassette_configs table for operator-driven ATM cassette inventory. Tracks per-machine cassette state (denomination, count, position) editable - via the satmachineadmin dashboard and published to the ATM as encrypted + via the spirekeeper dashboard and published to the ATM as encrypted kind-30078 events. See aiolabs/satmachineadmin#29 + lamassu-next#56. Schema choice: PK (machine_id, denomination) mirrors the ATM-side @@ -556,7 +556,7 @@ async def m007_add_cassette_configs(db): render them; v2 reconciliation UI consumes them without a migration. """ await db.execute(f""" - CREATE TABLE IF NOT EXISTS satoshimachine.cassette_configs ( + CREATE TABLE IF NOT EXISTS spirekeeper.cassette_configs ( machine_id TEXT NOT NULL, denomination INTEGER NOT NULL, count INTEGER NOT NULL, @@ -600,14 +600,14 @@ async def m008_flip_cassette_configs_pk_to_position(db): # Probe: does the old PK shape still exist? If state_denomination # column already exists, m008 already ran — no-op. await db.fetchone( - "SELECT state_denomination FROM satoshimachine.cassette_configs " "LIMIT 1" + "SELECT state_denomination FROM spirekeeper.cassette_configs " "LIMIT 1" ) return except Exception: pass await db.execute(f""" - CREATE TABLE IF NOT EXISTS satoshimachine.cassette_configs_new ( + CREATE TABLE IF NOT EXISTS spirekeeper.cassette_configs_new ( machine_id TEXT NOT NULL, position INTEGER NOT NULL, denomination INTEGER NOT NULL, @@ -630,19 +630,19 @@ async def m008_flip_cassette_configs_pk_to_position(db): # = current denomination as a best-guess baseline; the next bootstrap # event re-populates the state_* columns authoritatively. await db.execute(""" - INSERT INTO satoshimachine.cassette_configs_new + INSERT INTO spirekeeper.cassette_configs_new (machine_id, position, denomination, count, updated_at, updated_by, state_denomination, state_count, state_at, state_event_id) SELECT machine_id, position, denomination, count, updated_at, updated_by, denomination, state_count, state_at, state_event_id - FROM satoshimachine.cassette_configs + FROM spirekeeper.cassette_configs """) - await db.execute("DROP TABLE satoshimachine.cassette_configs") + await db.execute("DROP TABLE spirekeeper.cassette_configs") await db.execute( - "ALTER TABLE satoshimachine.cassette_configs_new " "RENAME TO cassette_configs" + "ALTER TABLE spirekeeper.cassette_configs_new " "RENAME TO cassette_configs" ) @@ -676,7 +676,7 @@ async def m009_split_fee_fractions_by_direction(db): operators set via the new UI surface). - dca_settlements gains fee_mismatch_sats BIGINT NULL — records bitspire-reported fee minus expected per - satmachineadmin's principal-based recompute. + spirekeeper's principal-based recompute. Phase 1 observability: log + record, never reject (per coord-log §2026-06-01T07:00Z lnbits advisory; option A locked). @@ -697,13 +697,13 @@ async def m009_split_fee_fractions_by_direction(db): ] for table, col, coltype in additions: try: - await db.fetchone(f"SELECT {col} FROM satoshimachine.{table} LIMIT 1") + await db.fetchone(f"SELECT {col} FROM spirekeeper.{table} LIMIT 1") # column already present — migration partially-ran previously, skip continue except Exception: pass await db.execute( - f"ALTER TABLE satoshimachine.{table} ADD COLUMN {col} {coltype}" + f"ALTER TABLE spirekeeper.{table} ADD COLUMN {col} {coltype}" ) # Backfill + drop the legacy singleton, gated on the column still @@ -711,7 +711,7 @@ async def m009_split_fee_fractions_by_direction(db): # steps cleanly. try: await db.fetchone( - "SELECT super_fee_fraction FROM satoshimachine.super_config LIMIT 1" + "SELECT super_fee_fraction FROM spirekeeper.super_config LIMIT 1" ) legacy_present = True except Exception: @@ -724,7 +724,7 @@ async def m009_split_fee_fractions_by_direction(db): # still at DEFAULT 0). await db.execute( """ - UPDATE satoshimachine.super_config + UPDATE spirekeeper.super_config SET super_cash_in_fee_fraction = super_fee_fraction, super_cash_out_fee_fraction = super_fee_fraction WHERE super_cash_in_fee_fraction = 0 @@ -733,5 +733,5 @@ async def m009_split_fee_fractions_by_direction(db): """ ) await db.execute( - "ALTER TABLE satoshimachine.super_config DROP COLUMN super_fee_fraction" + "ALTER TABLE spirekeeper.super_config DROP COLUMN super_fee_fraction" ) diff --git a/nostr_publish.py b/nostr_publish.py index b36b39c..102b2f8 100644 --- a/nostr_publish.py +++ b/nostr_publish.py @@ -288,7 +288,7 @@ async def publish_encrypted_kind_30078( await publish_signed_event(signed) prefix = f"{log_context}: " if log_context else "" logger.info( - f"satmachineadmin: {prefix}published kind-30078 to ATM " + f"spirekeeper: {prefix}published kind-30078 to ATM " f"{recipient_pubkey_hex[:12]}... d-tag={d_tag} " f"event_id={signed['id'][:12]}..." ) diff --git a/nostr_transport_roster.py b/nostr_transport_roster.py index 6603e50..46bd6da 100644 --- a/nostr_transport_roster.py +++ b/nostr_transport_roster.py @@ -11,7 +11,7 @@ on a match. The hook is registered with lnbits' `nostr_transport` at extension-init time via `register_with_lnbits()`. Until the lnbits side ships `lnbits.core.services.nostr_transport.register_roster_resolver`, the -registration call lazily-imports + soft-fails so satmachineadmin keeps +registration call lazily-imports + soft-fails so spirekeeper keeps loading cleanly on any lnbits version. When the lnbits implementation lands + the satmachine instance has @@ -37,7 +37,7 @@ from loguru import logger from .crud import get_machine_by_atm_pubkey_hex -_SOURCE_EXTENSION = "satmachineadmin" +_SOURCE_EXTENSION = "spirekeeper" @dataclass(frozen=True) @@ -111,11 +111,11 @@ def register_with_lnbits() -> bool: Returns True if the registration landed (lnbits surface available + call succeeded), False if soft-failed because lnbits hasn't shipped `register_roster_resolver` yet — that's the expected - state until the path-B lnbits PR lands. Either way satmachineadmin + state until the path-B lnbits PR lands. Either way spirekeeper boots cleanly; only the routing-via-roster behavior is gated on the lnbits side being present. - Called once from `satmachineadmin_start()`. Idempotent on the + Called once from `spirekeeper_start()`. Idempotent on the lnbits side per their 15:15Z spec ("re-registration on extension reload replaces cleanly"). """ @@ -125,7 +125,7 @@ def register_with_lnbits() -> bool: ) except ImportError: logger.info( - "satmachineadmin: nostr-transport roster-resolver hook not " + "spirekeeper: nostr-transport roster-resolver hook not " "available on this lnbits version (pre-path-B); ATM-npub " "routing falls through to lnbits' default auto-account-from-" "npub behaviour. See aiolabs/satmachineadmin#20 / coord-log " @@ -134,7 +134,7 @@ def register_with_lnbits() -> bool: return False register_roster_resolver(_SOURCE_EXTENSION, resolve) logger.info( - f"satmachineadmin: registered '{_SOURCE_EXTENSION}' roster " + f"spirekeeper: registered '{_SOURCE_EXTENSION}' roster " "resolver with lnbits nostr-transport — inbound kind-21000 " "from a registered ATM npub will route to the operator's wallet " "directly. (Behavior gated server-side by " diff --git a/pyproject.toml b/pyproject.toml index 885fe45..a7521f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] -name = "satmachineadmin" -version = "0.0.4" -description = "Eightball is a simple API that allows you to create a random number generator." +name = "spirekeeper" +version = "0.1.0" +description = "bitSpire admin — Lightning DCA + Lamassu ATM operator administration for LNbits" authors = ["benarc", "dni "] [tool.poetry.dependencies] diff --git a/static/js/index.js b/static/js/index.js index 4da7ee6..f510e9c 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -1,6 +1,6 @@ // Satoshi Machine v2 — operator dashboard (P9a foundation). // -// Vue 3 + Quasar UMD app. Talks to the v2 satmachineadmin REST surface +// Vue 3 + Quasar UMD app. Talks to the v2 spirekeeper REST surface // (machines / clients / deposits / settlements / commission-splits / // super-config). All endpoints are operator-scoped via the LNbits session. // @@ -12,7 +12,7 @@ // - For pale backgrounds (bg-*-1), pair with explicit dark text class // so dark-mode users don't get unreadable white-on-cream. -const API = '/satmachineadmin/api/v1/dca' +const API = '/spirekeeper/api/v1/dca' const SUPER_FEE_PATH = `${API}/super-config` const MACHINES_PATH = `${API}/machines` const SETTLEMENTS_PATH = `${API}/settlements` @@ -198,7 +198,7 @@ window.app = Vue.createApp({ settlements: [], // Cassettes sub-tab state (#29 v1) — see openCassettePublishConfirm / // submitCassettePublish methods + the cassettes panel in - // templates/satmachineadmin/index.html. + // templates/spirekeeper/index.html. activeTab: 'settlements', cassetteEdits: [], // editable working copy of cassette_configs rows cassettesPristine: [], // last-known-clean snapshot for revert diff --git a/tasks.py b/tasks.py index 314c244..4685c84 100644 --- a/tasks.py +++ b/tasks.py @@ -45,7 +45,7 @@ from .crud import ( from .distribution import process_settlement from .models import CreateDcaSettlementData, Machine -LISTENER_NAME = "ext_satmachineadmin" +LISTENER_NAME = "ext_spirekeeper" # Holds strong refs to in-flight distribution tasks so Python's GC doesn't # collect them mid-flight (asyncio.create_task only weakly references its @@ -58,7 +58,7 @@ async def wait_for_paid_invoices() -> None: invoice_queue: asyncio.Queue = asyncio.Queue() register_invoice_listener(invoice_queue, LISTENER_NAME) logger.info( - "satmachineadmin v2: invoice listener registered as " + "spirekeeper v2: invoice listener registered as " f"`{LISTENER_NAME}` — waiting for bitSpire settlements." ) while True: @@ -67,7 +67,7 @@ async def wait_for_paid_invoices() -> None: await _handle_payment(payment) except Exception as exc: # listener must never die logger.error( - f"satmachineadmin: error handling payment " + f"spirekeeper: error handling payment " f"{payment.payment_hash[:12]}...: {exc}" ) @@ -169,12 +169,12 @@ async def _handle_payment(payment: Payment) -> None: settlement = await create_settlement_idempotent(data, initial_status="pending") if settlement is None: logger.error( - f"satmachineadmin: failed to insert settlement for " + f"spirekeeper: failed to insert settlement for " f"payment_hash={payment.payment_hash[:12]}..." ) return logger.info( - f"satmachineadmin: landed settlement {settlement.id} for " + f"spirekeeper: landed settlement {settlement.id} for " f"machine={machine.machine_npub[:12]}... " f"wire={data.wire_sats}sats principal={data.principal_sats}sats " f"fee={data.fee_sats}sats " @@ -224,12 +224,12 @@ async def _record_rejected(payment: Payment, machine: Machine, exc: Exception) - ) if rejected is None: logger.error( - f"satmachineadmin: failed to insert rejected settlement for " + f"spirekeeper: failed to insert rejected settlement for " f"payment_hash={payment.payment_hash[:12]}..." ) return logger.error( - f"satmachineadmin: rejected settlement {rejected.id} " + f"spirekeeper: rejected settlement {rejected.id} " f"(machine={machine.machine_npub[:12]}..., " f"payment_hash={payment.payment_hash[:12]}...): {exc}" ) @@ -245,7 +245,7 @@ async def _record_rejected(payment: Payment, machine: Machine, exc: Exception) - # upserts cassette_configs via apply_bootstrap_state. # # v1 = one-shot per machine (ATM's meta.bootstrapPublishedAt makes the -# publish idempotent on ATM-side restart; satmachineadmin's apply_bootstrap_ +# publish idempotent on ATM-side restart; spirekeeper's apply_bootstrap_ # state dedups on state_event_id for relay re-delivery). # # v2 (separate issue) = continuous reverse-channel consumer with a @@ -258,7 +258,7 @@ async def _record_rejected(payment: Payment, machine: Machine, exc: Exception) - # websocket. The relay manager is the same singleton publish_to_atm uses, # so add_subscription registers a filter against the same relay pool. -CASSETTE_BOOTSTRAP_SUB_ID = "satmachineadmin-cassette-bootstrap" +CASSETTE_BOOTSTRAP_SUB_ID = "spirekeeper-cassette-bootstrap" _CASSETTE_POLL_INTERVAL_S = 2.0 _CASSETTE_BACKOFF_S = 30.0 # when nostrclient isn't installed yet @@ -280,7 +280,7 @@ async def wait_for_cassette_state_events() -> None: - apply_bootstrap_state errors → log + skip """ logger.info( - "satmachineadmin v2: cassette bootstrap consumer starting " + "spirekeeper v2: cassette bootstrap consumer starting " f"(sub_id={CASSETTE_BOOTSTRAP_SUB_ID})" ) current_filter_key: str | None = None @@ -290,7 +290,7 @@ async def wait_for_cassette_state_events() -> None: await asyncio.sleep(_CASSETTE_POLL_INTERVAL_S) except _NostrclientUnavailable: logger.warning( - "satmachineadmin: nostrclient extension not installed; " + "spirekeeper: nostrclient extension not installed; " f"cassette bootstrap consumer sleeping {_CASSETTE_BACKOFF_S}s " "before retry. Install + activate nostrclient on this " "LNbits instance." @@ -299,7 +299,7 @@ async def wait_for_cassette_state_events() -> None: await asyncio.sleep(_CASSETTE_BACKOFF_S) except Exception as exc: # listener must never die logger.error( - f"satmachineadmin: cassette consumer loop error (continuing): " f"{exc}" + f"spirekeeper: cassette consumer loop error (continuing): " f"{exc}" ) await asyncio.sleep(_CASSETTE_POLL_INTERVAL_S) @@ -346,13 +346,13 @@ async def _cassette_consumer_tick(current_filter_key: str | None) -> str: CASSETTE_BOOTSTRAP_SUB_ID, filters # type: ignore[arg-type] ) logger.info( - "satmachineadmin: (re)registered cassette bootstrap " + "spirekeeper: (re)registered cassette bootstrap " f"subscription with {len(d_tags)} d-tag(s)" ) else: nostr_client.relay_manager.close_subscription(CASSETTE_BOOTSTRAP_SUB_ID) logger.info( - "satmachineadmin: no active machines; closed cassette " + "spirekeeper: no active machines; closed cassette " "bootstrap subscription" ) @@ -368,7 +368,7 @@ async def _cassette_consumer_tick(current_filter_key: str | None) -> str: ) except Exception as exc: logger.warning( - f"satmachineadmin: cassette state event handler " + f"spirekeeper: cassette state event handler " f"failed (skipping): {exc}" ) @@ -419,14 +419,14 @@ async def _handle_cassette_state_event( event_obj = event_raw else: logger.warning( - f"satmachineadmin: cassette event of unexpected type " + f"spirekeeper: cassette event of unexpected type " f"{type(event_raw).__name__}; skipping" ) return if not verify_event(event_obj): logger.warning( - f"satmachineadmin: cassette state event sig verify failed " + f"spirekeeper: cassette state event sig verify failed " f"(id={event_obj.get('id', '?')[:12]}...)" ) return @@ -437,7 +437,7 @@ async def _handle_cassette_state_event( # Unknown sender — could be relay noise or an attacker. Don't # treat as our problem. logger.warning( - f"satmachineadmin: cassette state event from unknown ATM " + f"spirekeeper: cassette state event from unknown ATM " f"pubkey {sender_pubkey[:12]}... (not in dca_machines); " "skipping" ) @@ -448,7 +448,7 @@ async def _handle_cassette_state_event( except CassetteTransportError as exc: # OperatorIdentityMissing / SignerUnavailable — log + skip. logger.warning( - f"satmachineadmin: can't resolve signer for operator " + f"spirekeeper: can't resolve signer for operator " f"{machine.operator_user_id[:8]}... (machine {machine.id}): " f"{exc}" ) @@ -458,13 +458,13 @@ async def _handle_cassette_state_event( payload = await decrypt_and_parse_state_event(event_obj, account, signer) except CassetteEventTransientError as exc: logger.info( - f"satmachineadmin: cassette state event for machine {machine.id} " + f"spirekeeper: cassette state event for machine {machine.id} " f"hit a transient signer error (will retry next poll): {exc}" ) return except CassetteEventDecodeError as exc: logger.warning( - f"satmachineadmin: cassette state event decode failed for " + f"spirekeeper: cassette state event decode failed for " f"machine {machine.id} (id={event_obj.get('id', '?')[:12]}...): " f"{exc}" ) @@ -479,12 +479,12 @@ async def _handle_cassette_state_event( ) if applied: logger.info( - f"satmachineadmin: applied bootstrap state event {event_id[:12]}... " + f"spirekeeper: applied bootstrap state event {event_id[:12]}... " f"to machine {machine.id} ({len(payload.positions)} cassettes)" ) else: # Replay: event_id already on file. Normal on relay reconnect. logger.debug( - f"satmachineadmin: cassette state event {event_id[:12]}... " + f"spirekeeper: cassette state event {event_id[:12]}... " f"already applied to machine {machine.id} (replay no-op)" ) diff --git a/templates/satmachineadmin/_api_docs.html b/templates/spirekeeper/_api_docs.html similarity index 100% rename from templates/satmachineadmin/_api_docs.html rename to templates/spirekeeper/_api_docs.html diff --git a/templates/satmachineadmin/index.html b/templates/spirekeeper/index.html similarity index 99% rename from templates/satmachineadmin/index.html rename to templates/spirekeeper/index.html index ffdb730..054bc77 100644 --- a/templates/satmachineadmin/index.html +++ b/templates/spirekeeper/index.html @@ -3,7 +3,7 @@ {% block scripts %} {{ window_vars(user) }} - + {% endblock %} {% block page %} diff --git a/tests/conftest.py b/tests/conftest.py index 39fe975..65d0c7e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,5 @@ """ -Pytest configuration for the satmachineadmin extension test suite. +Pytest configuration for the spirekeeper extension test suite. Provides a `loguru_capture` fixture for tests that need to verify loguru WARN/ERROR side-effects. Loguru attaches its default sink to diff --git a/tests/test_cassette_configs.py b/tests/test_cassette_configs.py index 08a7e00..703c3bb 100644 --- a/tests/test_cassette_configs.py +++ b/tests/test_cassette_configs.py @@ -210,7 +210,7 @@ class TestShouldApplyBootstrapState: def test_skips_when_existing_event_id_matches(self): """The same bootstrap event re-delivered after a relay reconnect - or satmachineadmin restart should no-op, not re-upsert the same + or spirekeeper restart should no-op, not re-upsert the same rows (which would clobber any operator edits since).""" assert _should_apply_bootstrap_state("same-event", "same-event") is False diff --git a/tests/test_collision_guard.py b/tests/test_collision_guard.py index 0f1a236..544d5d1 100644 --- a/tests/test_collision_guard.py +++ b/tests/test_collision_guard.py @@ -10,7 +10,7 @@ auto-account wallet. The guard refuses to register a machine whose npub matches any LNbits operator account's `accounts.pubkey`, so this state cannot be entered -through the satmachineadmin UI in the first place. +through the spirekeeper UI in the first place. Monkeypatches `views_api.get_account_by_pubkey` to avoid needing a live LNbits DB; this matches the assertion-style of tests/test_nostr_attribution diff --git a/tests/test_fee_mismatch_recording.py b/tests/test_fee_mismatch_recording.py index 7e5bb80..589aa7b 100644 --- a/tests/test_fee_mismatch_recording.py +++ b/tests/test_fee_mismatch_recording.py @@ -7,7 +7,7 @@ Each settlement records: fee_mismatch_sats = bitspire_fee_sats - (platform_fee_sats + operator_fee_sats) -Positive = bitspire over-reported (claimed more fee than satmachineadmin +Positive = bitspire over-reported (claimed more fee than spirekeeper recomputed against principal). Negative = bitspire under-reported. Zero = exact match. @@ -116,7 +116,7 @@ class TestFeeMismatchSatsRecording: """Real-world Phase-1 scenario before Layer 3 (lamassu-next#57) ships: ATM hardcodes 7.77% cash-out; operator configures 5% operator + 3% super = 8% total. Bitspire reports - 100_000 * 0.0777 = 7_770 sats; satmachineadmin recomputes 8_000. + 100_000 * 0.0777 = 7_770 sats; spirekeeper recomputes 8_000. Delta is large and visible for triage; behavior unchanged.""" machine = _machine(op_out=0.05) super_cfg = _super_config(out_frac=0.03) diff --git a/tests/test_roster_resolver.py b/tests/test_roster_resolver.py index 70eb2d3..8849fa7 100644 --- a/tests/test_roster_resolver.py +++ b/tests/test_roster_resolver.py @@ -1,6 +1,6 @@ """ Tests for `nostr_transport_roster.resolve` — the lookup function -satmachineadmin hands lnbits' nostr-transport via +spirekeeper hands lnbits' nostr-transport via `register_roster_resolver` (path-B wallet-routing fix, #20 / coord-log 2026-05-31T15:25Z). @@ -73,7 +73,7 @@ def test_resolve_known_atm_returns_route_hit(monkeypatch): assert result is not None assert result.operator_user_id == "op-123" assert result.wallet_id == "wallet-abc" - assert result.source_extension == "satmachineadmin" + assert result.source_extension == "spirekeeper" assert captured["pubkey_hex"] == _ATM_PUB_HEX @@ -141,7 +141,7 @@ def test_resolve_raises_on_malformed_input(monkeypatch): def test_register_with_lnbits_soft_fails_without_hook(monkeypatch): """Until the lnbits-side path-B PR lands, the registration call must soft-fail cleanly (returns False, no exception) so - satmachineadmin keeps booting on every lnbits version.""" + spirekeeper keeps booting on every lnbits version.""" real_import = ( __builtins__["__import__"] if isinstance(__builtins__, dict) diff --git a/views.py b/views.py index 1061e9f..db8bffc 100644 --- a/views.py +++ b/views.py @@ -10,16 +10,16 @@ from lnbits.core.models import User from lnbits.decorators import check_user_exists from lnbits.helpers import template_renderer -satmachineadmin_generic_router = APIRouter() +spirekeeper_generic_router = APIRouter() -def satmachineadmin_renderer(): - return template_renderer(["satmachineadmin/templates"]) +def spirekeeper_renderer(): + return template_renderer(["spirekeeper/templates"]) -@satmachineadmin_generic_router.get("/", response_class=HTMLResponse) +@spirekeeper_generic_router.get("/", response_class=HTMLResponse) async def index(req: Request, user: User = Depends(check_user_exists)): - return satmachineadmin_renderer().TemplateResponse( - "satmachineadmin/index.html", + return spirekeeper_renderer().TemplateResponse( + "spirekeeper/index.html", {"request": req, "user": user.json()}, ) diff --git a/views_api.py b/views_api.py index 3c4c6cc..a8ed965 100644 --- a/views_api.py +++ b/views_api.py @@ -94,7 +94,7 @@ from .models import ( UpsertCassetteConfigData, ) -satmachineadmin_api_router = APIRouter() +spirekeeper_api_router = APIRouter() async def _assert_wallet_owned_by(wallet_id: str, user_id: str) -> None: @@ -254,7 +254,7 @@ async def _assert_super_config_cap_safe( # ============================================================================= -@satmachineadmin_api_router.post("/api/v1/dca/machines", response_model=Machine) +@spirekeeper_api_router.post("/api/v1/dca/machines", response_model=Machine) async def api_create_machine( data: CreateMachineData, user: User = Depends(check_user_exists) ) -> Machine: @@ -274,14 +274,14 @@ async def api_create_machine( return machine -@satmachineadmin_api_router.get("/api/v1/dca/machines", response_model=list[Machine]) +@spirekeeper_api_router.get("/api/v1/dca/machines", response_model=list[Machine]) async def api_list_machines( user: User = Depends(check_user_exists), ) -> list[Machine]: return await get_machines_for_operator(user.id) -@satmachineadmin_api_router.get( +@spirekeeper_api_router.get( "/api/v1/dca/machines/{machine_id}", response_model=Machine ) async def api_get_machine( @@ -293,7 +293,7 @@ async def api_get_machine( return machine -@satmachineadmin_api_router.put( +@spirekeeper_api_router.put( "/api/v1/dca/machines/{machine_id}", response_model=Machine ) async def api_update_machine( @@ -341,7 +341,7 @@ async def api_update_machine( return updated -@satmachineadmin_api_router.delete( +@spirekeeper_api_router.delete( "/api/v1/dca/machines/{machine_id}", status_code=HTTPStatus.NO_CONTENT ) async def api_delete_machine( @@ -379,7 +379,7 @@ async def _client_owned_by(client_id: str, user_id: str) -> DcaClient: return client -@satmachineadmin_api_router.post("/api/v1/dca/clients", response_model=DcaClient) +@spirekeeper_api_router.post("/api/v1/dca/clients", response_model=DcaClient) async def api_create_client( data: CreateDcaClientData, user: User = Depends(check_user_exists) ) -> DcaClient: @@ -388,7 +388,7 @@ async def api_create_client( return await create_dca_client(data) -@satmachineadmin_api_router.get("/api/v1/dca/clients", response_model=list[DcaClient]) +@spirekeeper_api_router.get("/api/v1/dca/clients", response_model=list[DcaClient]) async def api_list_clients( machine_id: str | None = None, user: User = Depends(check_user_exists), @@ -402,7 +402,7 @@ async def api_list_clients( return await get_dca_clients_for_machine(machine_id) -@satmachineadmin_api_router.get( +@spirekeeper_api_router.get( "/api/v1/dca/clients/{client_id}", response_model=DcaClient ) async def api_get_client( @@ -411,7 +411,7 @@ async def api_get_client( return await _client_owned_by(client_id, user.id) -@satmachineadmin_api_router.put( +@spirekeeper_api_router.put( "/api/v1/dca/clients/{client_id}", response_model=DcaClient ) async def api_update_client( @@ -426,7 +426,7 @@ async def api_update_client( return updated -@satmachineadmin_api_router.delete( +@spirekeeper_api_router.delete( "/api/v1/dca/clients/{client_id}", status_code=HTTPStatus.NO_CONTENT ) async def api_delete_client( @@ -436,7 +436,7 @@ async def api_delete_client( await delete_dca_client(client_id) -@satmachineadmin_api_router.get( +@spirekeeper_api_router.get( "/api/v1/dca/clients/{client_id}/balance", response_model=ClientBalanceSummary, ) @@ -450,7 +450,7 @@ async def api_get_client_balance( return summary -@satmachineadmin_api_router.post( +@spirekeeper_api_router.post( "/api/v1/dca/clients/{client_id}/settle", response_model=DcaPayment ) async def api_settle_client_balance( @@ -498,7 +498,7 @@ async def _deposit_owned_by(deposit_id: str, user_id: str) -> DcaDeposit: return deposit -@satmachineadmin_api_router.post("/api/v1/dca/deposits", response_model=DcaDeposit) +@spirekeeper_api_router.post("/api/v1/dca/deposits", response_model=DcaDeposit) async def api_create_deposit( data: CreateDepositData, user: User = Depends(check_user_exists) ) -> DcaDeposit: @@ -531,7 +531,7 @@ async def api_create_deposit( return await create_deposit(user.id, data, currency=machine.fiat_code) -@satmachineadmin_api_router.get("/api/v1/dca/deposits", response_model=list[DcaDeposit]) +@spirekeeper_api_router.get("/api/v1/dca/deposits", response_model=list[DcaDeposit]) async def api_list_deposits( client_id: str | None = None, user: User = Depends(check_user_exists), @@ -544,7 +544,7 @@ async def api_list_deposits( return await get_deposits_for_operator(user.id) -@satmachineadmin_api_router.get( +@spirekeeper_api_router.get( "/api/v1/dca/deposits/{deposit_id}", response_model=DcaDeposit ) async def api_get_deposit( @@ -553,7 +553,7 @@ async def api_get_deposit( return await _deposit_owned_by(deposit_id, user.id) -@satmachineadmin_api_router.put( +@spirekeeper_api_router.put( "/api/v1/dca/deposits/{deposit_id}", response_model=DcaDeposit ) async def api_update_deposit( @@ -573,7 +573,7 @@ async def api_update_deposit( return updated -@satmachineadmin_api_router.put( +@spirekeeper_api_router.put( "/api/v1/dca/deposits/{deposit_id}/status", response_model=DcaDeposit ) async def api_update_deposit_status( @@ -588,7 +588,7 @@ async def api_update_deposit_status( return updated -@satmachineadmin_api_router.delete( +@spirekeeper_api_router.delete( "/api/v1/dca/deposits/{deposit_id}", status_code=HTTPStatus.NO_CONTENT ) async def api_delete_deposit( @@ -608,7 +608,7 @@ async def api_delete_deposit( # ============================================================================= -@satmachineadmin_api_router.get( +@spirekeeper_api_router.get( "/api/v1/dca/settlements", response_model=list[DcaSettlement] ) async def api_list_settlements( @@ -617,7 +617,7 @@ async def api_list_settlements( return await get_settlements_for_operator(user.id) -@satmachineadmin_api_router.get( +@spirekeeper_api_router.get( "/api/v1/dca/machines/{machine_id}/settlements", response_model=list[DcaSettlement], ) @@ -637,7 +637,7 @@ async def api_list_settlements_for_machine( # order). -@satmachineadmin_api_router.get( +@spirekeeper_api_router.get( "/api/v1/dca/settlements/stuck", response_model=StuckSettlementsResponse ) async def api_list_stuck_settlements( @@ -668,7 +668,7 @@ async def api_list_stuck_settlements( ) -@satmachineadmin_api_router.get( +@spirekeeper_api_router.get( "/api/v1/dca/settlements/{settlement_id}", response_model=DcaSettlement ) async def api_get_settlement( @@ -683,7 +683,7 @@ async def api_get_settlement( return settlement -@satmachineadmin_api_router.post( +@spirekeeper_api_router.post( "/api/v1/dca/settlements/{settlement_id}/partial-dispense", response_model=DcaSettlement, ) @@ -718,7 +718,7 @@ async def api_partial_dispense( raise HTTPException(HTTPStatus.BAD_REQUEST, str(exc)) from exc -@satmachineadmin_api_router.post( +@spirekeeper_api_router.post( "/api/v1/dca/settlements/{settlement_id}/force-reset", response_model=DcaSettlement, ) @@ -768,7 +768,7 @@ async def api_force_reset_settlement( return updated -@satmachineadmin_api_router.post( +@spirekeeper_api_router.post( "/api/v1/dca/settlements/{settlement_id}/retry", response_model=DcaSettlement, ) @@ -821,7 +821,7 @@ async def api_retry_settlement( return after if after is not None else updated -@satmachineadmin_api_router.post( +@spirekeeper_api_router.post( "/api/v1/dca/settlements/{settlement_id}/notes", response_model=DcaSettlement, ) @@ -854,7 +854,7 @@ async def api_append_settlement_note( # ============================================================================= -@satmachineadmin_api_router.get("/api/v1/dca/payments", response_model=list[DcaPayment]) +@spirekeeper_api_router.get("/api/v1/dca/payments", response_model=list[DcaPayment]) async def api_list_payments( leg_type: str | None = None, user: User = Depends(check_user_exists), @@ -869,7 +869,7 @@ async def api_list_payments( # ============================================================================= -@satmachineadmin_api_router.get( +@spirekeeper_api_router.get( "/api/v1/dca/commission-splits", response_model=list[CommissionSplit] ) async def api_get_commission_splits( @@ -889,7 +889,7 @@ async def api_get_commission_splits( return await get_commission_splits(user.id, None) -@satmachineadmin_api_router.put( +@spirekeeper_api_router.put( "/api/v1/dca/commission-splits", response_model=list[CommissionSplit] ) async def api_replace_commission_splits( @@ -905,7 +905,7 @@ async def api_replace_commission_splits( return await replace_commission_splits(user.id, data.machine_id, data.legs) -@satmachineadmin_api_router.delete( +@spirekeeper_api_router.delete( "/api/v1/dca/commission-splits", status_code=HTTPStatus.NO_CONTENT, ) @@ -927,7 +927,7 @@ async def api_delete_commission_splits( # ============================================================================= -@satmachineadmin_api_router.get("/api/v1/dca/super-config", response_model=SuperConfig) +@spirekeeper_api_router.get("/api/v1/dca/super-config", response_model=SuperConfig) async def api_get_super_config( _user: User = Depends(check_user_exists), ) -> SuperConfig: @@ -940,7 +940,7 @@ async def api_get_super_config( return config -@satmachineadmin_api_router.put("/api/v1/dca/super-config", response_model=SuperConfig) +@spirekeeper_api_router.put("/api/v1/dca/super-config", response_model=SuperConfig) async def api_update_super_config( data: UpdateSuperConfigData, _user: User = Depends(check_super_user), @@ -988,7 +988,7 @@ async def api_update_super_config( # fields per row are denomination and count. -@satmachineadmin_api_router.get( +@spirekeeper_api_router.get( "/api/v1/dca/machines/{machine_id}/cassettes", response_model=list[CassetteConfig], ) @@ -1003,7 +1003,7 @@ async def api_list_machine_cassettes( return await list_cassette_configs_for_machine(machine_id) -@satmachineadmin_api_router.post( +@spirekeeper_api_router.post( "/api/v1/dca/machines/{machine_id}/cassettes/publish", response_model=list[CassetteConfig], ) @@ -1049,7 +1049,7 @@ async def api_publish_machine_cassettes( "No cassette_configs rows exist for this machine yet — " "waiting for the ATM's bootstrap state event. Power on the " "ATM and confirm it has reached the configured relay; " - "satmachineadmin will auto-populate cassette_configs on " + "spirekeeper will auto-populate cassette_configs on " "receipt." ), ) @@ -1068,7 +1068,7 @@ async def api_publish_machine_cassettes( ) # Apply each per-row edit so the operator-believed state on - # satmachineadmin reflects the published payload, even if the ATM + # spirekeeper reflects the published payload, even if the ATM # ack lands later (v2). updated_by audit-stamps the operator user id. for pos, row in payload.positions.items(): updated = await update_cassette_config(