docs: update functional identifier refs to spirekeeper
Some checks failed
ci.yml / docs: update functional identifier refs to spirekeeper (push) Failing after 0s
Some checks failed
ci.yml / docs: update functional identifier refs to spirekeeper (push) Failing after 0s
Runbook SQL (spirekeeper.dca_*), ext URL paths, code-location paths, and the DB-schema name in docs/CLAUDE/README move to the new identity. Rewrites the placeholder description.md with a real one. Historical aiolabs/satmachineadmin#N issue/repo links stay pointing at the original repo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a059e3f596
commit
4ac640a499
6 changed files with 142 additions and 143 deletions
|
|
@ -213,7 +213,7 @@ commission_amount = 266800 - 258835 = 7,965 sats (to commission wallet)
|
||||||
### Security Considerations
|
### Security Considerations
|
||||||
- **Superuser Authentication**: Admin extension requires LNBits superuser login
|
- **Superuser Authentication**: Admin extension requires LNBits superuser login
|
||||||
- **Wallet Admin Keys**: Client extension uses wallet admin keys for user operations
|
- **Wallet Admin Keys**: Client extension uses wallet admin keys for user operations
|
||||||
- **Database Access**: Only superusers can write to satoshimachine database
|
- **Database Access**: Only superusers can write to spirekeeper database
|
||||||
- SSH tunnel encryption for database connectivity
|
- SSH tunnel encryption for database connectivity
|
||||||
- Read-only database permissions for Lamassu access
|
- Read-only database permissions for Lamassu access
|
||||||
- Input sanitization and type validation
|
- Input sanitization and type validation
|
||||||
|
|
@ -230,7 +230,7 @@ on existing installs:
|
||||||
```sql
|
```sql
|
||||||
SELECT a.id, a.username, a.pubkey, m.id, m.machine_npub
|
SELECT a.id, a.username, a.pubkey, m.id, m.machine_npub
|
||||||
FROM accounts a
|
FROM accounts a
|
||||||
JOIN ext_satoshimachine.dca_machines m
|
JOIN ext_spirekeeper.dca_machines m
|
||||||
ON LOWER(a.pubkey) = LOWER(m.machine_npub);
|
ON LOWER(a.pubkey) = LOWER(m.machine_npub);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,7 @@ The extension creates several tables:
|
||||||
├── config.json # Extension configuration
|
├── config.json # Extension configuration
|
||||||
├── manifest.json # Extension manifest
|
├── manifest.json # Extension manifest
|
||||||
├── templates/
|
├── templates/
|
||||||
│ └── satmachineadmin/
|
│ └── spirekeeper/
|
||||||
│ └── index.html # Main UI template
|
│ └── index.html # Main UI template
|
||||||
└── static/
|
└── static/
|
||||||
└── js/
|
└── js/
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
SatMachineAdmin can be used as a template for building new extensions, it includes a bunch of functions that can be edited/deleted as you need them.
|
spirekeeper is the operator-side administration extension for bitSpire — it manages one or more bitSpire ATMs and runs the Dollar Cost Averaging (DCA) distribution that pays registered clients out of their confirmed deposit balances.
|
||||||
|
|
||||||
This is a longform description that will be used in the advanced description when users click on the "more" button on the extension cards.
|
It listens for settlement events published by each ATM over Nostr (kind-21000, NIP-44 v2 encrypted), pays the resulting invoices through LNbits, and splits the proceeds across the principal, operator-fee, and commission legs according to the per-machine and super-config fee settings.
|
||||||
|
|
||||||
Adding some bullets is nice covering:
|
- **ATM fleet management** — register and configure each bitSpire by its machine npub; publish fee config and cassette state over Nostr.
|
||||||
|
- **DCA distribution** — proportional ("flow") allocation across clients based on their remaining deposit balances, with a full per-leg audit trail.
|
||||||
|
- **Operator administration** — deposit confirmation workflow, client balance tracking, settlement history with drill-down, and CSV export for accounting.
|
||||||
|
|
||||||
- Functionality
|
Requires LNbits superuser access for administration. The Lightning ATM side is served by the bitSpire device; this extension is the back office.
|
||||||
- Use cases
|
|
||||||
|
|
||||||
...and some other text about just how great this etension is.
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
## 0 · Why this document exists
|
## 0 · Why this document exists
|
||||||
|
|
||||||
Today the satoshi‑machine code lives at `~/dev/shared/extensions/satmachineadmin` on branch `v2-bitspire`. v2 swapped the legacy Lamassu SSH/PostgreSQL polling model for a Nostr‑native one: bitSpire publishes invoices over kind‑21000 NIP‑44 v2 events, LNbits pays them, and our extension hooks the resulting `Payment` object.
|
Today the satoshi‑machine code lives at `~/dev/shared/extensions/spirekeeper` on branch `v2-bitspire`. v2 swapped the legacy Lamassu SSH/PostgreSQL polling model for a Nostr‑native one: bitSpire publishes invoices over kind‑21000 NIP‑44 v2 events, LNbits pays them, and our extension hooks the resulting `Payment` object.
|
||||||
|
|
||||||
The hard truth: the *settlement* itself uses Lightning (so it can't be forged once a preimage lands), but everything *around* the settlement — who the ATM is, what operator it belongs to, what the principal/commission split was, and what fiat was dispensed — currently rides on **mutable, unauthenticated metadata** (`Payment.extra`) plus a **stopgap that has the ATM hold the operator's own Nostr private key**. The latter means physical possession of the ATM = total compromise of the operator's LNbits account.
|
The hard truth: the *settlement* itself uses Lightning (so it can't be forged once a preimage lands), but everything *around* the settlement — who the ATM is, what operator it belongs to, what the principal/commission split was, and what fiat was dispensed — currently rides on **mutable, unauthenticated metadata** (`Payment.extra`) plus a **stopgap that has the ATM hold the operator's own Nostr private key**. The latter means physical possession of the ATM = total compromise of the operator's LNbits account.
|
||||||
|
|
||||||
|
|
@ -66,7 +66,7 @@ Lamassu's old answer here was TLS cert pinning. We have a richer toolbox — Nos
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
register_invoice_listener fires
|
register_invoice_listener fires
|
||||||
satmachineadmin/tasks.py:_handle_payment
|
spirekeeper/tasks.py:_handle_payment
|
||||||
│
|
│
|
||||||
┌─────────────────────────────────┴────────────────────────────┐
|
┌─────────────────────────────────┴────────────────────────────┐
|
||||||
▼ ▼
|
▼ ▼
|
||||||
|
|
@ -144,7 +144,7 @@ T3, T5, T6 are the ones that keep the hardware honest. T3 + T6 are *the* reason
|
||||||
|
|
||||||
## 4 · Audit findings — current state inventory
|
## 4 · Audit findings — current state inventory
|
||||||
|
|
||||||
Pulled from the two recent code‑level audits of `~/dev/shared/extensions/satmachineadmin` (operator‑scoping inventory) and `~/dev/lnbits/nostr-transport` (transport primitives).
|
Pulled from the two recent code‑level audits of `~/dev/shared/extensions/spirekeeper` (operator‑scoping inventory) and `~/dev/lnbits/nostr-transport` (transport primitives).
|
||||||
|
|
||||||
### 4.1 What's already strong
|
### 4.1 What's already strong
|
||||||
|
|
||||||
|
|
@ -307,7 +307,7 @@ None of those need to change. The new layers slot in *above* them.
|
||||||
| **S4 — NIP‑78 per‑machine config + fleet roster** | Operator publishes `kind:30078` config + `kind:30000` fleet list. Handler cross‑checks ATM npub ∈ fleet; reads max‑withdraw/fee policy from config. | G1, G9 | 1 week | Define config schema; backwards‑compat path for pre‑NIP‑78 machines. |
|
| **S4 — NIP‑78 per‑machine config + fleet roster** | Operator publishes `kind:30078` config + `kind:30000` fleet list. Handler cross‑checks ATM npub ∈ fleet; reads max‑withdraw/fee policy from config. | G1, G9 | 1 week | Define config schema; backwards‑compat path for pre‑NIP‑78 machines. |
|
||||||
| **S5 — `sender_pubkey` persistence + signed metadata in Payment.extra** | When the dispatcher writes a Payment row, it stamps `Payment.extra.sender_pubkey`, `delegation_root`, and an HMAC over the key fields keyed by the LNbits server's own secret. Mutation post‑write breaks the HMAC. | G2 (DB‑side), G5, G6 | 3–5 days | LNbits PR — fairly localised. |
|
| **S5 — `sender_pubkey` persistence + signed metadata in Payment.extra** | When the dispatcher writes a Payment row, it stamps `Payment.extra.sender_pubkey`, `delegation_root`, and an HMAC over the key fields keyed by the LNbits server's own secret. Mutation post‑write breaks the HMAC. | G2 (DB‑side), G5, G6 | 3–5 days | LNbits PR — fairly localised. |
|
||||||
| **S6 — Rate limiting + roster‑gated auto‑account** | Auto‑account‑from‑npub only fires if the npub appears in some operator's NIP‑78 fleet OR if an explicit "open enrollment" flag is set. Relay/handler‑level rate limit per pubkey. | G8, G9 | 1 week | LNbits PR. |
|
| **S6 — Rate limiting + roster‑gated auto‑account** | Auto‑account‑from‑npub only fires if the npub appears in some operator's NIP‑78 fleet OR if an explicit "open enrollment" flag is set. Relay/handler‑level rate limit per pubkey. | G8, G9 | 1 week | LNbits PR. |
|
||||||
| **S7 — NIP‑46 bunker option** | Operator can pair satmachineadmin with a Bunker (Amber, Nunchuk Custody, etc.). Operator's nsec leaves LNbits' DB; LNbits stores only the bunker connection. | G6, partial G5 | 4–6 weeks | Largest. Defer until S0–S5 land. |
|
| **S7 — NIP‑46 bunker option** | Operator can pair spirekeeper with a Bunker (Amber, Nunchuk Custody, etc.). Operator's nsec leaves LNbits' DB; LNbits stores only the bunker connection. | G6, partial G5 | 4–6 weeks | Largest. Defer until S0–S5 land. |
|
||||||
| **S8 — Cash‑in path** | Wire `is_out=True` cash‑in handling: LNURL‑withdraw with expiration matching the kind‑21000 invoice TTL, attestation receipt on settle, refund queue for stale links. | G10 | 2 weeks | Out of scope for this security doc but tracked here for completeness. |
|
| **S8 — Cash‑in path** | Wire `is_out=True` cash‑in handling: LNURL‑withdraw with expiration matching the kind‑21000 invoice TTL, attestation receipt on settle, refund queue for stale links. | G10 | 2 weeks | Out of scope for this security doc but tracked here for completeness. |
|
||||||
|
|
||||||
Recommended sequencing for the *next sprint*: **S0 + S1 + S5**. They give us the biggest security delta with no upstream LNbits dependency for S0/S1 and a small, well‑scoped LNbits patch for S5. S2/S3/S4 are the proper Nostr‑native layer and should land in the sprint after.
|
Recommended sequencing for the *next sprint*: **S0 + S1 + S5**. They give us the biggest security delta with no upstream LNbits dependency for S0/S1 and a small, well‑scoped LNbits patch for S5. S2/S3/S4 are the proper Nostr‑native layer and should land in the sprint after.
|
||||||
|
|
@ -359,12 +359,12 @@ For an auditor or new contributor doing a walk‑through:
|
||||||
|
|
||||||
| File | Role | Note |
|
| File | Role | Note |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `~/dev/shared/extensions/satmachineadmin/tasks.py` | LNbits invoice listener. Entry point for all settlements today. | `_handle_payment:56-95` — load‑bearing routing. |
|
| `~/dev/shared/extensions/spirekeeper/tasks.py` | LNbits invoice listener. Entry point for all settlements today. | `_handle_payment:56-95` — load‑bearing routing. |
|
||||||
| `~/dev/shared/extensions/satmachineadmin/bitspire.py` | Parses Payment.extra. The trust boundary. | `parse_settlement:68-92` — happy vs fallback path. |
|
| `~/dev/shared/extensions/spirekeeper/bitspire.py` | Parses Payment.extra. The trust boundary. | `parse_settlement:68-92` — happy vs fallback path. |
|
||||||
| `~/dev/shared/extensions/satmachineadmin/distribution.py` | Three‑leg distribution chain. | `process_settlement` — uses claim pattern. |
|
| `~/dev/shared/extensions/spirekeeper/distribution.py` | Three‑leg distribution chain. | `process_settlement` — uses claim pattern. |
|
||||||
| `~/dev/shared/extensions/satmachineadmin/crud.py` | Operator‑scoped DB layer. | `claim_settlement_for_processing`, `_machine_owned_by`. |
|
| `~/dev/shared/extensions/spirekeeper/crud.py` | Operator‑scoped DB layer. | `claim_settlement_for_processing`, `_machine_owned_by`. |
|
||||||
| `~/dev/shared/extensions/satmachineadmin/views_api.py` | 33 routes, all `check_user_exists` except super‑config PUT. | `_assert_wallet_owned_by` is the wallet‑IDOR fix. |
|
| `~/dev/shared/extensions/spirekeeper/views_api.py` | 33 routes, all `check_user_exists` except super‑config PUT. | `_assert_wallet_owned_by` is the wallet‑IDOR fix. |
|
||||||
| `~/dev/shared/extensions/satmachineadmin/migrations.py` | Schema. | `dca_settlements` is the audit row; `dca_payments` is the leg row. |
|
| `~/dev/shared/extensions/spirekeeper/migrations.py` | Schema. | `dca_settlements` is the audit row; `dca_payments` is the leg row. |
|
||||||
| `~/dev/shocknet/lamassu-next/deploy/nixos/provision-atm.sh` | Where keys land on the ATM today. | `:81-99` — `VITE_ATM_PRIVATE_KEY` and the Option‑1 stopgap. |
|
| `~/dev/shocknet/lamassu-next/deploy/nixos/provision-atm.sh` | Where keys land on the ATM today. | `:81-99` — `VITE_ATM_PRIVATE_KEY` and the Option‑1 stopgap. |
|
||||||
| `~/dev/lnbits/nostr-transport/lnbits/core/services/nostr_transport/` | LNbits transport handler (upstream we depend on). | NIP‑44 v2 crypto here; G5/G6/G7 fixes will live here. |
|
| `~/dev/lnbits/nostr-transport/lnbits/core/services/nostr_transport/` | LNbits transport handler (upstream we depend on). | NIP‑44 v2 crypto here; G5/G6/G7 fixes will live here. |
|
||||||
| `~/dev/nostr-protocol/nips/26.md` | Delegation. | Source for S2. |
|
| `~/dev/nostr-protocol/nips/26.md` | Delegation. | Source for S2. |
|
||||||
|
|
@ -397,7 +397,7 @@ How we'd test the proposed design end‑to‑end, once S0–S5 land:
|
||||||
|
|
||||||
Once approved:
|
Once approved:
|
||||||
|
|
||||||
1. The PDF for printing will be generated post‑plan‑mode (requires shell exec). Recommended path: render the markdown via `pandoc` to `~/dev/shared/extensions/satmachineadmin/docs/security-pathway-v1.pdf`; the markdown source will live at `~/dev/shared/extensions/satmachineadmin/docs/security-pathway-v1.md` so future contributors edit it in‑repo.
|
1. The PDF for printing will be generated post‑plan‑mode (requires shell exec). Recommended path: render the markdown via `pandoc` to `~/dev/shared/extensions/spirekeeper/docs/security-pathway-v1.pdf`; the markdown source will live at `~/dev/shared/extensions/spirekeeper/docs/security-pathway-v1.md` so future contributors edit it in‑repo.
|
||||||
2. Open Forgejo epics on `aiolabs/satmachineadmin` linking back to existing `#9/#11/#12` and adding a new one for "Security pathway hardening (S0–S7)."
|
2. Open Forgejo epics on `aiolabs/satmachineadmin` linking back to existing `#9/#11/#12` and adding a new one for "Security pathway hardening (S0–S7)."
|
||||||
3. Open a tracking issue on `aiolabs/lnbits` against the `nostr-transport` branch for the LNbits‑side primitives (S2, S5, S6).
|
3. Open a tracking issue on `aiolabs/lnbits` against the `nostr-transport` branch for the LNbits‑side primitives (S2, S5, S6).
|
||||||
4. Sequence sprint: **S0 + S1 + S5 first** (highest ratio of security delta to upstream coupling). S2/S3/S4 in the following sprint.
|
4. Sequence sprint: **S0 + S1 + S5 first** (highest ratio of security delta to upstream coupling). S2/S3/S4 in the following sprint.
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
-- Find duplicate transactions
|
-- Find duplicate transactions
|
||||||
SELECT transaction_id, COUNT(*) as count,
|
SELECT transaction_id, COUNT(*) as count,
|
||||||
STRING_AGG(id::text, ', ') as record_ids
|
STRING_AGG(id::text, ', ') as record_ids
|
||||||
FROM satoshimachine.lamassu_transactions
|
FROM spirekeeper.lamassu_transactions
|
||||||
GROUP BY transaction_id
|
GROUP BY transaction_id
|
||||||
HAVING COUNT(*) > 1;
|
HAVING COUNT(*) > 1;
|
||||||
```
|
```
|
||||||
|
|
@ -60,16 +60,16 @@ HAVING COUNT(*) > 1;
|
||||||
-- Step 1: Identify duplicate distributions
|
-- Step 1: Identify duplicate distributions
|
||||||
SELECT lt.transaction_id, lt.id, lt.created_at, lt.base_amount,
|
SELECT lt.transaction_id, lt.id, lt.created_at, lt.base_amount,
|
||||||
COUNT(dp.id) as distribution_count
|
COUNT(dp.id) as distribution_count
|
||||||
FROM satoshimachine.lamassu_transactions lt
|
FROM spirekeeper.lamassu_transactions lt
|
||||||
LEFT JOIN satoshimachine.dca_payments dp ON dp.lamassu_transaction_id = lt.id
|
LEFT JOIN spirekeeper.dca_payments dp ON dp.lamassu_transaction_id = lt.id
|
||||||
GROUP BY lt.id
|
GROUP BY lt.id
|
||||||
HAVING COUNT(dp.id) > (SELECT COUNT(*) FROM satoshimachine.dca_clients WHERE remaining_balance > 0);
|
HAVING COUNT(dp.id) > (SELECT COUNT(*) FROM spirekeeper.dca_clients WHERE remaining_balance > 0);
|
||||||
|
|
||||||
-- Step 2: Calculate over-distributed amounts per client
|
-- Step 2: Calculate over-distributed amounts per client
|
||||||
SELECT client_id,
|
SELECT client_id,
|
||||||
SUM(amount_sats) as total_received,
|
SUM(amount_sats) as total_received,
|
||||||
-- Manual calculation of expected amount needed here
|
-- Manual calculation of expected amount needed here
|
||||||
FROM satoshimachine.dca_payments
|
FROM spirekeeper.dca_payments
|
||||||
WHERE lamassu_transaction_id IN (SELECT id FROM duplicates_table)
|
WHERE lamassu_transaction_id IN (SELECT id FROM duplicates_table)
|
||||||
GROUP BY client_id;
|
GROUP BY client_id;
|
||||||
```
|
```
|
||||||
|
|
@ -94,7 +94,7 @@ if existing:
|
||||||
|
|
||||||
**Required Database Change**:
|
**Required Database Change**:
|
||||||
```sql
|
```sql
|
||||||
ALTER TABLE satoshimachine.lamassu_transactions
|
ALTER TABLE spirekeeper.lamassu_transactions
|
||||||
ADD CONSTRAINT unique_transaction_id UNIQUE (transaction_id);
|
ADD CONSTRAINT unique_transaction_id UNIQUE (transaction_id);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -117,7 +117,7 @@ ADD CONSTRAINT unique_transaction_id UNIQUE (transaction_id);
|
||||||
-- Check last successful poll
|
-- Check last successful poll
|
||||||
SELECT MAX(created_at) as last_transaction,
|
SELECT MAX(created_at) as last_transaction,
|
||||||
EXTRACT(EPOCH FROM (NOW() - MAX(created_at)))/3600 as hours_since_last
|
EXTRACT(EPOCH FROM (NOW() - MAX(created_at)))/3600 as hours_since_last
|
||||||
FROM satoshimachine.lamassu_transactions;
|
FROM spirekeeper.lamassu_transactions;
|
||||||
|
|
||||||
-- If hours_since_last > 24, investigate immediately
|
-- If hours_since_last > 24, investigate immediately
|
||||||
```
|
```
|
||||||
|
|
@ -194,8 +194,8 @@ ssh-keygen -t ed25519 -f ~/.ssh/satmachine_lamassu -C "satmachine-polling"
|
||||||
-- Find stuck/failed payments (older than 1 hour, not completed)
|
-- Find stuck/failed payments (older than 1 hour, not completed)
|
||||||
SELECT dp.id, dp.client_id, dp.amount_sats, dp.status, dp.created_at,
|
SELECT dp.id, dp.client_id, dp.amount_sats, dp.status, dp.created_at,
|
||||||
c.username, dp.payment_hash
|
c.username, dp.payment_hash
|
||||||
FROM satoshimachine.dca_payments dp
|
FROM spirekeeper.dca_payments dp
|
||||||
JOIN satoshimachine.dca_clients c ON dp.client_id = c.id
|
JOIN spirekeeper.dca_clients c ON dp.client_id = c.id
|
||||||
WHERE dp.status != 'completed'
|
WHERE dp.status != 'completed'
|
||||||
AND dp.created_at < NOW() - INTERVAL '1 hour'
|
AND dp.created_at < NOW() - INTERVAL '1 hour'
|
||||||
ORDER BY dp.created_at DESC;
|
ORDER BY dp.created_at DESC;
|
||||||
|
|
@ -207,7 +207,7 @@ ORDER BY dp.created_at DESC;
|
||||||
SELECT
|
SELECT
|
||||||
SUM(commission_amount) as total_commission_expected,
|
SUM(commission_amount) as total_commission_expected,
|
||||||
-- Manually check actual wallet balance in LNBits
|
-- Manually check actual wallet balance in LNBits
|
||||||
FROM satoshimachine.lamassu_transactions;
|
FROM spirekeeper.lamassu_transactions;
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Immediate Response
|
#### Immediate Response
|
||||||
|
|
@ -258,9 +258,9 @@ SELECT
|
||||||
c.remaining_balance,
|
c.remaining_balance,
|
||||||
COALESCE(SUM(d.amount), 0) as total_deposits,
|
COALESCE(SUM(d.amount), 0) as total_deposits,
|
||||||
COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0) as total_payments
|
COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0) as total_payments
|
||||||
FROM satoshimachine.dca_clients c
|
FROM spirekeeper.dca_clients c
|
||||||
LEFT JOIN satoshimachine.dca_deposits d ON c.id = d.client_id AND d.status = 'confirmed'
|
LEFT JOIN spirekeeper.dca_deposits d ON c.id = d.client_id AND d.status = 'confirmed'
|
||||||
LEFT JOIN satoshimachine.dca_payments p ON c.id = p.client_id
|
LEFT JOIN spirekeeper.dca_payments p ON c.id = p.client_id
|
||||||
GROUP BY c.id;
|
GROUP BY c.id;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -319,9 +319,9 @@ SELECT
|
||||||
COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0) as total_distributed,
|
COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0) as total_distributed,
|
||||||
COALESCE(SUM(d.amount), 0) - COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0) as calculated_balance,
|
COALESCE(SUM(d.amount), 0) - COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0) as calculated_balance,
|
||||||
c.remaining_balance - (COALESCE(SUM(d.amount), 0) - COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0)) as discrepancy
|
c.remaining_balance - (COALESCE(SUM(d.amount), 0) - COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0)) as discrepancy
|
||||||
FROM satoshimachine.dca_clients c
|
FROM spirekeeper.dca_clients c
|
||||||
LEFT JOIN satoshimachine.dca_deposits d ON c.id = d.client_id AND d.status = 'confirmed'
|
LEFT JOIN spirekeeper.dca_deposits d ON c.id = d.client_id AND d.status = 'confirmed'
|
||||||
LEFT JOIN satoshimachine.dca_payments p ON c.id = p.client_id
|
LEFT JOIN spirekeeper.dca_payments p ON c.id = p.client_id
|
||||||
GROUP BY c.id, c.username, c.remaining_balance
|
GROUP BY c.id, c.username, c.remaining_balance
|
||||||
HAVING ABS(c.remaining_balance - (COALESCE(SUM(d.amount), 0) - COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0))) > 1;
|
HAVING ABS(c.remaining_balance - (COALESCE(SUM(d.amount), 0) - COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0))) > 1;
|
||||||
```
|
```
|
||||||
|
|
@ -341,11 +341,11 @@ HAVING ABS(c.remaining_balance - (COALESCE(SUM(d.amount), 0) - COALESCE(SUM(CASE
|
||||||
```sql
|
```sql
|
||||||
-- Get complete transaction history for client
|
-- Get complete transaction history for client
|
||||||
SELECT 'DEPOSIT' as type, id, amount, status, created_at, confirmed_at
|
SELECT 'DEPOSIT' as type, id, amount, status, created_at, confirmed_at
|
||||||
FROM satoshimachine.dca_deposits
|
FROM spirekeeper.dca_deposits
|
||||||
WHERE client_id = <client_id>
|
WHERE client_id = <client_id>
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT 'PAYMENT' as type, id, amount_sats, status, created_at, NULL
|
SELECT 'PAYMENT' as type, id, amount_sats, status, created_at, NULL
|
||||||
FROM satoshimachine.dca_payments
|
FROM spirekeeper.dca_payments
|
||||||
WHERE client_id = <client_id>
|
WHERE client_id = <client_id>
|
||||||
ORDER BY created_at;
|
ORDER BY created_at;
|
||||||
```
|
```
|
||||||
|
|
@ -361,18 +361,18 @@ ORDER BY created_at;
|
||||||
**Option A: Adjustment Entry** (Recommended)
|
**Option A: Adjustment Entry** (Recommended)
|
||||||
```sql
|
```sql
|
||||||
-- Create compensating deposit for positive discrepancy
|
-- Create compensating deposit for positive discrepancy
|
||||||
INSERT INTO satoshimachine.dca_deposits (client_id, amount, status, note)
|
INSERT INTO spirekeeper.dca_deposits (client_id, amount, status, note)
|
||||||
VALUES (<client_id>, <adjustment_amount>, 'confirmed', 'Balance correction - reconciliation 2025-10-19');
|
VALUES (<client_id>, <adjustment_amount>, 'confirmed', 'Balance correction - reconciliation 2025-10-19');
|
||||||
|
|
||||||
-- OR Create compensating payment for negative discrepancy
|
-- OR Create compensating payment for negative discrepancy
|
||||||
INSERT INTO satoshimachine.dca_payments (client_id, amount_sats, status, note)
|
INSERT INTO spirekeeper.dca_payments (client_id, amount_sats, status, note)
|
||||||
VALUES (<client_id>, <adjustment_amount>, 'completed', 'Balance correction - reconciliation 2025-10-19');
|
VALUES (<client_id>, <adjustment_amount>, 'completed', 'Balance correction - reconciliation 2025-10-19');
|
||||||
```
|
```
|
||||||
|
|
||||||
**Option B: Direct Balance Update** (Use with extreme caution)
|
**Option B: Direct Balance Update** (Use with extreme caution)
|
||||||
```sql
|
```sql
|
||||||
-- ONLY if audit trail is complete and discrepancy is unexplained
|
-- ONLY if audit trail is complete and discrepancy is unexplained
|
||||||
UPDATE satoshimachine.dca_clients
|
UPDATE spirekeeper.dca_clients
|
||||||
SET remaining_balance = <correct_balance>,
|
SET remaining_balance = <correct_balance>,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE id = <client_id>;
|
WHERE id = <client_id>;
|
||||||
|
|
@ -396,11 +396,11 @@ async def daily_reconciliation_check():
|
||||||
**Database Constraints**:
|
**Database Constraints**:
|
||||||
```sql
|
```sql
|
||||||
-- Prevent negative balances
|
-- Prevent negative balances
|
||||||
ALTER TABLE satoshimachine.dca_clients
|
ALTER TABLE spirekeeper.dca_clients
|
||||||
ADD CONSTRAINT positive_balance CHECK (remaining_balance >= 0);
|
ADD CONSTRAINT positive_balance CHECK (remaining_balance >= 0);
|
||||||
|
|
||||||
-- Prevent confirmed deposits with zero amount
|
-- Prevent confirmed deposits with zero amount
|
||||||
ALTER TABLE satoshimachine.dca_deposits
|
ALTER TABLE spirekeeper.dca_deposits
|
||||||
ADD CONSTRAINT positive_deposit CHECK (amount > 0);
|
ADD CONSTRAINT positive_deposit CHECK (amount > 0);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -435,7 +435,7 @@ SELECT
|
||||||
-- Calculate differences
|
-- Calculate differences
|
||||||
base_amount - ROUND(crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100))) as base_difference,
|
base_amount - ROUND(crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100))) as base_difference,
|
||||||
commission_amount - ROUND(crypto_atoms - (crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100)))) as commission_difference
|
commission_amount - ROUND(crypto_atoms - (crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100)))) as commission_difference
|
||||||
FROM satoshimachine.lamassu_transactions
|
FROM spirekeeper.lamassu_transactions
|
||||||
WHERE ABS(base_amount - ROUND(crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100)))) > 1
|
WHERE ABS(base_amount - ROUND(crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100)))) > 1
|
||||||
OR ABS(commission_amount - ROUND(crypto_atoms - (crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100))))) > 1;
|
OR ABS(commission_amount - ROUND(crypto_atoms - (crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100))))) > 1;
|
||||||
```
|
```
|
||||||
|
|
@ -485,9 +485,9 @@ SELECT
|
||||||
SUM(lt.base_amount) as total_distributed,
|
SUM(lt.base_amount) as total_distributed,
|
||||||
SUM(expected_base) as should_have_distributed,
|
SUM(expected_base) as should_have_distributed,
|
||||||
SUM(expected_base - lt.base_amount) as client_impact
|
SUM(expected_base - lt.base_amount) as client_impact
|
||||||
FROM satoshimachine.lamassu_transactions lt
|
FROM spirekeeper.lamassu_transactions lt
|
||||||
JOIN satoshimachine.dca_payments dp ON dp.lamassu_transaction_id = lt.id
|
JOIN spirekeeper.dca_payments dp ON dp.lamassu_transaction_id = lt.id
|
||||||
JOIN satoshimachine.dca_clients c ON dp.client_id = c.id
|
JOIN spirekeeper.dca_clients c ON dp.client_id = c.id
|
||||||
WHERE -- filter for affected transactions
|
WHERE -- filter for affected transactions
|
||||||
GROUP BY c.id;
|
GROUP BY c.id;
|
||||||
```
|
```
|
||||||
|
|
@ -505,7 +505,7 @@ GROUP BY c.id;
|
||||||
- Create compensating payments to affected clients:
|
- Create compensating payments to affected clients:
|
||||||
```sql
|
```sql
|
||||||
-- Add to client balances
|
-- Add to client balances
|
||||||
UPDATE satoshimachine.dca_clients c
|
UPDATE spirekeeper.dca_clients c
|
||||||
SET remaining_balance = remaining_balance + adjustment.amount
|
SET remaining_balance = remaining_balance + adjustment.amount
|
||||||
FROM (
|
FROM (
|
||||||
-- Calculate adjustment per client
|
-- Calculate adjustment per client
|
||||||
|
|
@ -572,7 +572,7 @@ assert abs(calculated_total - crypto_atoms) <= 1, "Commission calculation error
|
||||||
SELECT
|
SELECT
|
||||||
SUM(commission_amount) as expected_total_commission,
|
SUM(commission_amount) as expected_total_commission,
|
||||||
-- Compare to actual wallet balance in LNBits dashboard
|
-- Compare to actual wallet balance in LNBits dashboard
|
||||||
FROM satoshimachine.lamassu_transactions
|
FROM spirekeeper.lamassu_transactions
|
||||||
WHERE created_at > '<date-of-last-known-good-balance>';
|
WHERE created_at > '<date-of-last-known-good-balance>';
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -607,7 +607,7 @@ curl -X GET https://<lnbits-host>/api/v1/wallet \
|
||||||
SELECT commission_wallet_id,
|
SELECT commission_wallet_id,
|
||||||
LEFT(commission_wallet_adminkey, 10) || '...' as key_preview,
|
LEFT(commission_wallet_adminkey, 10) || '...' as key_preview,
|
||||||
updated_at
|
updated_at
|
||||||
FROM satoshimachine.lamassu_config
|
FROM spirekeeper.lamassu_config
|
||||||
ORDER BY updated_at DESC
|
ORDER BY updated_at DESC
|
||||||
LIMIT 1;
|
LIMIT 1;
|
||||||
```
|
```
|
||||||
|
|
@ -783,9 +783,9 @@ WITH client_financials AS (
|
||||||
COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0) as total_payments,
|
COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0) as total_payments,
|
||||||
COUNT(DISTINCT d.id) as deposit_count,
|
COUNT(DISTINCT d.id) as deposit_count,
|
||||||
COUNT(DISTINCT p.id) as payment_count
|
COUNT(DISTINCT p.id) as payment_count
|
||||||
FROM satoshimachine.dca_clients c
|
FROM spirekeeper.dca_clients c
|
||||||
LEFT JOIN satoshimachine.dca_deposits d ON c.id = d.client_id AND d.status = 'confirmed'
|
LEFT JOIN spirekeeper.dca_deposits d ON c.id = d.client_id AND d.status = 'confirmed'
|
||||||
LEFT JOIN satoshimachine.dca_payments p ON c.id = p.client_id
|
LEFT JOIN spirekeeper.dca_payments p ON c.id = p.client_id
|
||||||
GROUP BY c.id
|
GROUP BY c.id
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
|
|
@ -813,7 +813,7 @@ SELECT
|
||||||
SUM(commission_amount) as total_commission,
|
SUM(commission_amount) as total_commission,
|
||||||
MIN(created_at) as first_transaction,
|
MIN(created_at) as first_transaction,
|
||||||
MAX(created_at) as last_transaction
|
MAX(created_at) as last_transaction
|
||||||
FROM satoshimachine.lamassu_transactions;
|
FROM spirekeeper.lamassu_transactions;
|
||||||
|
|
||||||
-- 3. Failed/Pending Payments Check
|
-- 3. Failed/Pending Payments Check
|
||||||
SELECT
|
SELECT
|
||||||
|
|
@ -822,7 +822,7 @@ SELECT
|
||||||
SUM(amount_sats) as total_amount,
|
SUM(amount_sats) as total_amount,
|
||||||
MIN(created_at) as oldest,
|
MIN(created_at) as oldest,
|
||||||
MAX(created_at) as newest
|
MAX(created_at) as newest
|
||||||
FROM satoshimachine.dca_payments
|
FROM spirekeeper.dca_payments
|
||||||
GROUP BY status
|
GROUP BY status
|
||||||
ORDER BY
|
ORDER BY
|
||||||
CASE status
|
CASE status
|
||||||
|
|
@ -839,7 +839,7 @@ SELECT
|
||||||
status,
|
status,
|
||||||
created_at,
|
created_at,
|
||||||
EXTRACT(EPOCH FROM (NOW() - created_at))/3600 as hours_pending
|
EXTRACT(EPOCH FROM (NOW() - created_at))/3600 as hours_pending
|
||||||
FROM satoshimachine.dca_deposits
|
FROM spirekeeper.dca_deposits
|
||||||
WHERE status = 'pending'
|
WHERE status = 'pending'
|
||||||
AND created_at < NOW() - INTERVAL '48 hours'
|
AND created_at < NOW() - INTERVAL '48 hours'
|
||||||
ORDER BY created_at;
|
ORDER BY created_at;
|
||||||
|
|
@ -855,7 +855,7 @@ SELECT
|
||||||
commission_amount,
|
commission_amount,
|
||||||
ROUND(crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100))) as expected_base,
|
ROUND(crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100))) as expected_base,
|
||||||
base_amount - ROUND(crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100))) as difference
|
base_amount - ROUND(crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100))) as difference
|
||||||
FROM satoshimachine.lamassu_transactions
|
FROM spirekeeper.lamassu_transactions
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
LIMIT 20;
|
LIMIT 20;
|
||||||
```
|
```
|
||||||
|
|
@ -876,15 +876,15 @@ psql -h localhost -U lnbits -d lnbits
|
||||||
2. **Direct Configuration Update**:
|
2. **Direct Configuration Update**:
|
||||||
```sql
|
```sql
|
||||||
-- Update Lamassu config directly
|
-- Update Lamassu config directly
|
||||||
UPDATE satoshimachine.lamassu_config
|
UPDATE spirekeeper.lamassu_config
|
||||||
SET polling_enabled = false
|
SET polling_enabled = false
|
||||||
WHERE id = (SELECT MAX(id) FROM satoshimachine.lamassu_config);
|
WHERE id = (SELECT MAX(id) FROM spirekeeper.lamassu_config);
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Manual Client Balance Update**:
|
3. **Manual Client Balance Update**:
|
||||||
```sql
|
```sql
|
||||||
-- ONLY in emergency when dashboard unavailable
|
-- ONLY in emergency when dashboard unavailable
|
||||||
UPDATE satoshimachine.dca_clients
|
UPDATE spirekeeper.dca_clients
|
||||||
SET remaining_balance = <correct_amount>
|
SET remaining_balance = <correct_amount>
|
||||||
WHERE id = <client_id>;
|
WHERE id = <client_id>;
|
||||||
-- MUST document this action in incident log
|
-- MUST document this action in incident log
|
||||||
|
|
@ -910,19 +910,19 @@ uv run lnbits
|
||||||
```bash
|
```bash
|
||||||
# Export all DCA-related tables to CSV
|
# Export all DCA-related tables to CSV
|
||||||
sqlite3 -header -csv /path/to/lnbits/database.sqlite \
|
sqlite3 -header -csv /path/to/lnbits/database.sqlite \
|
||||||
"SELECT * FROM satoshimachine.lamassu_transactions;" \
|
"SELECT * FROM spirekeeper.lamassu_transactions;" \
|
||||||
> lamassu_transactions_export_$(date +%Y%m%d_%H%M%S).csv
|
> lamassu_transactions_export_$(date +%Y%m%d_%H%M%S).csv
|
||||||
|
|
||||||
sqlite3 -header -csv /path/to/lnbits/database.sqlite \
|
sqlite3 -header -csv /path/to/lnbits/database.sqlite \
|
||||||
"SELECT * FROM satoshimachine.dca_payments;" \
|
"SELECT * FROM spirekeeper.dca_payments;" \
|
||||||
> dca_payments_export_$(date +%Y%m%d_%H%M%S).csv
|
> dca_payments_export_$(date +%Y%m%d_%H%M%S).csv
|
||||||
|
|
||||||
sqlite3 -header -csv /path/to/lnbits/database.sqlite \
|
sqlite3 -header -csv /path/to/lnbits/database.sqlite \
|
||||||
"SELECT * FROM satoshimachine.dca_deposits;" \
|
"SELECT * FROM spirekeeper.dca_deposits;" \
|
||||||
> dca_deposits_export_$(date +%Y%m%d_%H%M%S).csv
|
> dca_deposits_export_$(date +%Y%m%d_%H%M%S).csv
|
||||||
|
|
||||||
sqlite3 -header -csv /path/to/lnbits/database.sqlite \
|
sqlite3 -header -csv /path/to/lnbits/database.sqlite \
|
||||||
"SELECT * FROM satoshimachine.dca_clients;" \
|
"SELECT * FROM spirekeeper.dca_clients;" \
|
||||||
> dca_clients_export_$(date +%Y%m%d_%H%M%S).csv
|
> dca_clients_export_$(date +%Y%m%d_%H%M%S).csv
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -946,9 +946,9 @@ SELECT
|
||||||
dp.amount_sats as client_received,
|
dp.amount_sats as client_received,
|
||||||
dp.status as payment_status,
|
dp.status as payment_status,
|
||||||
dp.payment_hash
|
dp.payment_hash
|
||||||
FROM satoshimachine.lamassu_transactions lt
|
FROM spirekeeper.lamassu_transactions lt
|
||||||
LEFT JOIN satoshimachine.dca_payments dp ON dp.lamassu_transaction_id = lt.id
|
LEFT JOIN spirekeeper.dca_payments dp ON dp.lamassu_transaction_id = lt.id
|
||||||
LEFT JOIN satoshimachine.dca_clients c ON dp.client_id = c.id
|
LEFT JOIN spirekeeper.dca_clients c ON dp.client_id = c.id
|
||||||
ORDER BY lt.created_at DESC, c.username;
|
ORDER BY lt.created_at DESC, c.username;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -981,18 +981,18 @@ async def process_lamassu_transaction(txn_data: dict) -> Optional[LamassuTransac
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- Add unique constraint on transaction_id
|
-- Add unique constraint on transaction_id
|
||||||
ALTER TABLE satoshimachine.lamassu_transactions
|
ALTER TABLE spirekeeper.lamassu_transactions
|
||||||
ADD CONSTRAINT unique_transaction_id UNIQUE (transaction_id);
|
ADD CONSTRAINT unique_transaction_id UNIQUE (transaction_id);
|
||||||
|
|
||||||
-- Prevent negative balances
|
-- Prevent negative balances
|
||||||
ALTER TABLE satoshimachine.dca_clients
|
ALTER TABLE spirekeeper.dca_clients
|
||||||
ADD CONSTRAINT positive_balance CHECK (remaining_balance >= 0);
|
ADD CONSTRAINT positive_balance CHECK (remaining_balance >= 0);
|
||||||
|
|
||||||
-- Ensure positive amounts
|
-- Ensure positive amounts
|
||||||
ALTER TABLE satoshimachine.dca_deposits
|
ALTER TABLE spirekeeper.dca_deposits
|
||||||
ADD CONSTRAINT positive_deposit CHECK (amount > 0);
|
ADD CONSTRAINT positive_deposit CHECK (amount > 0);
|
||||||
|
|
||||||
ALTER TABLE satoshimachine.dca_payments
|
ALTER TABLE spirekeeper.dca_payments
|
||||||
ADD CONSTRAINT positive_payment CHECK (amount_sats > 0);
|
ADD CONSTRAINT positive_payment CHECK (amount_sats > 0);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -1196,7 +1196,7 @@ grep "wallet.*api\|payment_hash" lnbits.log | tail -50
|
||||||
### System Access
|
### System Access
|
||||||
|
|
||||||
**LNBits Admin Dashboard**:
|
**LNBits Admin Dashboard**:
|
||||||
- URL: `https://<your-lnbits-host>/satoshimachine`
|
- URL: `https://<your-lnbits-host>/spirekeeper`
|
||||||
- Requires superuser authentication
|
- Requires superuser authentication
|
||||||
|
|
||||||
**Database Access**:
|
**Database Access**:
|
||||||
|
|
@ -1205,7 +1205,7 @@ grep "wallet.*api\|payment_hash" lnbits.log | tail -50
|
||||||
sqlite3 /home/padreug/AioLabs/Git/lnbits-extensions/lnbits/data/database.sqlite
|
sqlite3 /home/padreug/AioLabs/Git/lnbits-extensions/lnbits/data/database.sqlite
|
||||||
|
|
||||||
# Direct table access
|
# Direct table access
|
||||||
sqlite3 /path/to/db "SELECT * FROM satoshimachine.<table_name>;"
|
sqlite3 /path/to/db "SELECT * FROM spirekeeper.<table_name>;"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Log Files**:
|
**Log Files**:
|
||||||
|
|
@ -1274,7 +1274,7 @@ pkill -f lnbits
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- Disable automatic polling
|
-- Disable automatic polling
|
||||||
UPDATE satoshimachine.lamassu_config
|
UPDATE spirekeeper.lamassu_config
|
||||||
SET polling_enabled = false;
|
SET polling_enabled = false;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -1283,7 +1283,7 @@ SET polling_enabled = false;
|
||||||
```sql
|
```sql
|
||||||
-- All client balances summary
|
-- All client balances summary
|
||||||
SELECT id, username, remaining_balance, created_at
|
SELECT id, username, remaining_balance, created_at
|
||||||
FROM satoshimachine.dca_clients
|
FROM spirekeeper.dca_clients
|
||||||
ORDER BY remaining_balance DESC;
|
ORDER BY remaining_balance DESC;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -1291,7 +1291,7 @@ ORDER BY remaining_balance DESC;
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT id, transaction_id, created_at, crypto_atoms, base_amount, commission_amount
|
SELECT id, transaction_id, created_at, crypto_atoms, base_amount, commission_amount
|
||||||
FROM satoshimachine.lamassu_transactions
|
FROM spirekeeper.lamassu_transactions
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
LIMIT 10;
|
LIMIT 10;
|
||||||
```
|
```
|
||||||
|
|
@ -1299,7 +1299,7 @@ LIMIT 10;
|
||||||
### Failed Payments
|
### Failed Payments
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT * FROM satoshimachine.dca_payments
|
SELECT * FROM spirekeeper.dca_payments
|
||||||
WHERE status != 'completed'
|
WHERE status != 'completed'
|
||||||
ORDER BY created_at DESC;
|
ORDER BY created_at DESC;
|
||||||
```
|
```
|
||||||
|
|
@ -1314,19 +1314,19 @@ BACKUP_DIR="emergency_backup_${DATE}"
|
||||||
mkdir -p $BACKUP_DIR
|
mkdir -p $BACKUP_DIR
|
||||||
|
|
||||||
sqlite3 -header -csv /path/to/database.sqlite \
|
sqlite3 -header -csv /path/to/database.sqlite \
|
||||||
"SELECT * FROM satoshimachine.lamassu_transactions;" \
|
"SELECT * FROM spirekeeper.lamassu_transactions;" \
|
||||||
> ${BACKUP_DIR}/lamassu_transactions.csv
|
> ${BACKUP_DIR}/lamassu_transactions.csv
|
||||||
|
|
||||||
sqlite3 -header -csv /path/to/database.sqlite \
|
sqlite3 -header -csv /path/to/database.sqlite \
|
||||||
"SELECT * FROM satoshimachine.dca_payments;" \
|
"SELECT * FROM spirekeeper.dca_payments;" \
|
||||||
> ${BACKUP_DIR}/dca_payments.csv
|
> ${BACKUP_DIR}/dca_payments.csv
|
||||||
|
|
||||||
sqlite3 -header -csv /path/to/database.sqlite \
|
sqlite3 -header -csv /path/to/database.sqlite \
|
||||||
"SELECT * FROM satoshimachine.dca_deposits;" \
|
"SELECT * FROM spirekeeper.dca_deposits;" \
|
||||||
> ${BACKUP_DIR}/dca_deposits.csv
|
> ${BACKUP_DIR}/dca_deposits.csv
|
||||||
|
|
||||||
sqlite3 -header -csv /path/to/database.sqlite \
|
sqlite3 -header -csv /path/to/database.sqlite \
|
||||||
"SELECT * FROM satoshimachine.dca_clients;" \
|
"SELECT * FROM spirekeeper.dca_clients;" \
|
||||||
> ${BACKUP_DIR}/dca_clients.csv
|
> ${BACKUP_DIR}/dca_clients.csv
|
||||||
|
|
||||||
echo "Backup complete in ${BACKUP_DIR}/"
|
echo "Backup complete in ${BACKUP_DIR}/"
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
-- Find duplicate transactions
|
-- Find duplicate transactions
|
||||||
SELECT transaction_id, COUNT(*) as count,
|
SELECT transaction_id, COUNT(*) as count,
|
||||||
STRING_AGG(id::text, ', ') as record_ids
|
STRING_AGG(id::text, ', ') as record_ids
|
||||||
FROM satoshimachine.lamassu_transactions
|
FROM spirekeeper.lamassu_transactions
|
||||||
GROUP BY transaction_id
|
GROUP BY transaction_id
|
||||||
HAVING COUNT(*) > 1;
|
HAVING COUNT(*) > 1;
|
||||||
```
|
```
|
||||||
|
|
@ -60,16 +60,16 @@ HAVING COUNT(*) > 1;
|
||||||
-- Step 1: Identify duplicate distributions
|
-- Step 1: Identify duplicate distributions
|
||||||
SELECT lt.transaction_id, lt.id, lt.created_at, lt.base_amount,
|
SELECT lt.transaction_id, lt.id, lt.created_at, lt.base_amount,
|
||||||
COUNT(dp.id) as distribution_count
|
COUNT(dp.id) as distribution_count
|
||||||
FROM satoshimachine.lamassu_transactions lt
|
FROM spirekeeper.lamassu_transactions lt
|
||||||
LEFT JOIN satoshimachine.dca_payments dp ON dp.lamassu_transaction_id = lt.id
|
LEFT JOIN spirekeeper.dca_payments dp ON dp.lamassu_transaction_id = lt.id
|
||||||
GROUP BY lt.id
|
GROUP BY lt.id
|
||||||
HAVING COUNT(dp.id) > (SELECT COUNT(*) FROM satoshimachine.dca_clients WHERE remaining_balance > 0);
|
HAVING COUNT(dp.id) > (SELECT COUNT(*) FROM spirekeeper.dca_clients WHERE remaining_balance > 0);
|
||||||
|
|
||||||
-- Step 2: Calculate over-distributed amounts per client
|
-- Step 2: Calculate over-distributed amounts per client
|
||||||
SELECT client_id,
|
SELECT client_id,
|
||||||
SUM(amount_sats) as total_received,
|
SUM(amount_sats) as total_received,
|
||||||
-- Manual calculation of expected amount needed here
|
-- Manual calculation of expected amount needed here
|
||||||
FROM satoshimachine.dca_payments
|
FROM spirekeeper.dca_payments
|
||||||
WHERE lamassu_transaction_id IN (SELECT id FROM duplicates_table)
|
WHERE lamassu_transaction_id IN (SELECT id FROM duplicates_table)
|
||||||
GROUP BY client_id;
|
GROUP BY client_id;
|
||||||
```
|
```
|
||||||
|
|
@ -94,7 +94,7 @@ if existing:
|
||||||
|
|
||||||
**Required Database Change**:
|
**Required Database Change**:
|
||||||
```sql
|
```sql
|
||||||
ALTER TABLE satoshimachine.lamassu_transactions
|
ALTER TABLE spirekeeper.lamassu_transactions
|
||||||
ADD CONSTRAINT unique_transaction_id UNIQUE (transaction_id);
|
ADD CONSTRAINT unique_transaction_id UNIQUE (transaction_id);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -117,7 +117,7 @@ ADD CONSTRAINT unique_transaction_id UNIQUE (transaction_id);
|
||||||
-- Check last successful poll
|
-- Check last successful poll
|
||||||
SELECT MAX(created_at) as last_transaction,
|
SELECT MAX(created_at) as last_transaction,
|
||||||
EXTRACT(EPOCH FROM (NOW() - MAX(created_at)))/3600 as hours_since_last
|
EXTRACT(EPOCH FROM (NOW() - MAX(created_at)))/3600 as hours_since_last
|
||||||
FROM satoshimachine.lamassu_transactions;
|
FROM spirekeeper.lamassu_transactions;
|
||||||
|
|
||||||
-- If hours_since_last > 24, investigate immediately
|
-- If hours_since_last > 24, investigate immediately
|
||||||
```
|
```
|
||||||
|
|
@ -194,8 +194,8 @@ ssh-keygen -t ed25519 -f ~/.ssh/satmachine_lamassu -C "satmachine-polling"
|
||||||
-- Find stuck/failed payments (older than 1 hour, not completed)
|
-- Find stuck/failed payments (older than 1 hour, not completed)
|
||||||
SELECT dp.id, dp.client_id, dp.amount_sats, dp.status, dp.created_at,
|
SELECT dp.id, dp.client_id, dp.amount_sats, dp.status, dp.created_at,
|
||||||
c.username, dp.payment_hash
|
c.username, dp.payment_hash
|
||||||
FROM satoshimachine.dca_payments dp
|
FROM spirekeeper.dca_payments dp
|
||||||
JOIN satoshimachine.dca_clients c ON dp.client_id = c.id
|
JOIN spirekeeper.dca_clients c ON dp.client_id = c.id
|
||||||
WHERE dp.status != 'completed'
|
WHERE dp.status != 'completed'
|
||||||
AND dp.created_at < NOW() - INTERVAL '1 hour'
|
AND dp.created_at < NOW() - INTERVAL '1 hour'
|
||||||
ORDER BY dp.created_at DESC;
|
ORDER BY dp.created_at DESC;
|
||||||
|
|
@ -207,7 +207,7 @@ ORDER BY dp.created_at DESC;
|
||||||
SELECT
|
SELECT
|
||||||
SUM(commission_amount) as total_commission_expected,
|
SUM(commission_amount) as total_commission_expected,
|
||||||
-- Manually check actual wallet balance in LNBits
|
-- Manually check actual wallet balance in LNBits
|
||||||
FROM satoshimachine.lamassu_transactions;
|
FROM spirekeeper.lamassu_transactions;
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Immediate Response
|
#### Immediate Response
|
||||||
|
|
@ -258,9 +258,9 @@ SELECT
|
||||||
c.remaining_balance,
|
c.remaining_balance,
|
||||||
COALESCE(SUM(d.amount), 0) as total_deposits,
|
COALESCE(SUM(d.amount), 0) as total_deposits,
|
||||||
COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0) as total_payments
|
COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0) as total_payments
|
||||||
FROM satoshimachine.dca_clients c
|
FROM spirekeeper.dca_clients c
|
||||||
LEFT JOIN satoshimachine.dca_deposits d ON c.id = d.client_id AND d.status = 'confirmed'
|
LEFT JOIN spirekeeper.dca_deposits d ON c.id = d.client_id AND d.status = 'confirmed'
|
||||||
LEFT JOIN satoshimachine.dca_payments p ON c.id = p.client_id
|
LEFT JOIN spirekeeper.dca_payments p ON c.id = p.client_id
|
||||||
GROUP BY c.id;
|
GROUP BY c.id;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -319,9 +319,9 @@ SELECT
|
||||||
COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0) as total_distributed,
|
COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0) as total_distributed,
|
||||||
COALESCE(SUM(d.amount), 0) - COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0) as calculated_balance,
|
COALESCE(SUM(d.amount), 0) - COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0) as calculated_balance,
|
||||||
c.remaining_balance - (COALESCE(SUM(d.amount), 0) - COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0)) as discrepancy
|
c.remaining_balance - (COALESCE(SUM(d.amount), 0) - COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0)) as discrepancy
|
||||||
FROM satoshimachine.dca_clients c
|
FROM spirekeeper.dca_clients c
|
||||||
LEFT JOIN satoshimachine.dca_deposits d ON c.id = d.client_id AND d.status = 'confirmed'
|
LEFT JOIN spirekeeper.dca_deposits d ON c.id = d.client_id AND d.status = 'confirmed'
|
||||||
LEFT JOIN satoshimachine.dca_payments p ON c.id = p.client_id
|
LEFT JOIN spirekeeper.dca_payments p ON c.id = p.client_id
|
||||||
GROUP BY c.id, c.username, c.remaining_balance
|
GROUP BY c.id, c.username, c.remaining_balance
|
||||||
HAVING ABS(c.remaining_balance - (COALESCE(SUM(d.amount), 0) - COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0))) > 1;
|
HAVING ABS(c.remaining_balance - (COALESCE(SUM(d.amount), 0) - COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0))) > 1;
|
||||||
```
|
```
|
||||||
|
|
@ -341,11 +341,11 @@ HAVING ABS(c.remaining_balance - (COALESCE(SUM(d.amount), 0) - COALESCE(SUM(CASE
|
||||||
```sql
|
```sql
|
||||||
-- Get complete transaction history for client
|
-- Get complete transaction history for client
|
||||||
SELECT 'DEPOSIT' as type, id, amount, status, created_at, confirmed_at
|
SELECT 'DEPOSIT' as type, id, amount, status, created_at, confirmed_at
|
||||||
FROM satoshimachine.dca_deposits
|
FROM spirekeeper.dca_deposits
|
||||||
WHERE client_id = <client_id>
|
WHERE client_id = <client_id>
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT 'PAYMENT' as type, id, amount_sats, status, created_at, NULL
|
SELECT 'PAYMENT' as type, id, amount_sats, status, created_at, NULL
|
||||||
FROM satoshimachine.dca_payments
|
FROM spirekeeper.dca_payments
|
||||||
WHERE client_id = <client_id>
|
WHERE client_id = <client_id>
|
||||||
ORDER BY created_at;
|
ORDER BY created_at;
|
||||||
```
|
```
|
||||||
|
|
@ -361,18 +361,18 @@ ORDER BY created_at;
|
||||||
**Option A: Adjustment Entry** (Recommended)
|
**Option A: Adjustment Entry** (Recommended)
|
||||||
```sql
|
```sql
|
||||||
-- Create compensating deposit for positive discrepancy
|
-- Create compensating deposit for positive discrepancy
|
||||||
INSERT INTO satoshimachine.dca_deposits (client_id, amount, status, note)
|
INSERT INTO spirekeeper.dca_deposits (client_id, amount, status, note)
|
||||||
VALUES (<client_id>, <adjustment_amount>, 'confirmed', 'Balance correction - reconciliation 2025-10-19');
|
VALUES (<client_id>, <adjustment_amount>, 'confirmed', 'Balance correction - reconciliation 2025-10-19');
|
||||||
|
|
||||||
-- OR Create compensating payment for negative discrepancy
|
-- OR Create compensating payment for negative discrepancy
|
||||||
INSERT INTO satoshimachine.dca_payments (client_id, amount_sats, status, note)
|
INSERT INTO spirekeeper.dca_payments (client_id, amount_sats, status, note)
|
||||||
VALUES (<client_id>, <adjustment_amount>, 'completed', 'Balance correction - reconciliation 2025-10-19');
|
VALUES (<client_id>, <adjustment_amount>, 'completed', 'Balance correction - reconciliation 2025-10-19');
|
||||||
```
|
```
|
||||||
|
|
||||||
**Option B: Direct Balance Update** (Use with extreme caution)
|
**Option B: Direct Balance Update** (Use with extreme caution)
|
||||||
```sql
|
```sql
|
||||||
-- ONLY if audit trail is complete and discrepancy is unexplained
|
-- ONLY if audit trail is complete and discrepancy is unexplained
|
||||||
UPDATE satoshimachine.dca_clients
|
UPDATE spirekeeper.dca_clients
|
||||||
SET remaining_balance = <correct_balance>,
|
SET remaining_balance = <correct_balance>,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE id = <client_id>;
|
WHERE id = <client_id>;
|
||||||
|
|
@ -396,11 +396,11 @@ async def daily_reconciliation_check():
|
||||||
**Database Constraints**:
|
**Database Constraints**:
|
||||||
```sql
|
```sql
|
||||||
-- Prevent negative balances
|
-- Prevent negative balances
|
||||||
ALTER TABLE satoshimachine.dca_clients
|
ALTER TABLE spirekeeper.dca_clients
|
||||||
ADD CONSTRAINT positive_balance CHECK (remaining_balance >= 0);
|
ADD CONSTRAINT positive_balance CHECK (remaining_balance >= 0);
|
||||||
|
|
||||||
-- Prevent confirmed deposits with zero amount
|
-- Prevent confirmed deposits with zero amount
|
||||||
ALTER TABLE satoshimachine.dca_deposits
|
ALTER TABLE spirekeeper.dca_deposits
|
||||||
ADD CONSTRAINT positive_deposit CHECK (amount > 0);
|
ADD CONSTRAINT positive_deposit CHECK (amount > 0);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -435,7 +435,7 @@ SELECT
|
||||||
-- Calculate differences
|
-- Calculate differences
|
||||||
base_amount - ROUND(crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100))) as base_difference,
|
base_amount - ROUND(crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100))) as base_difference,
|
||||||
commission_amount - ROUND(crypto_atoms - (crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100)))) as commission_difference
|
commission_amount - ROUND(crypto_atoms - (crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100)))) as commission_difference
|
||||||
FROM satoshimachine.lamassu_transactions
|
FROM spirekeeper.lamassu_transactions
|
||||||
WHERE ABS(base_amount - ROUND(crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100)))) > 1
|
WHERE ABS(base_amount - ROUND(crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100)))) > 1
|
||||||
OR ABS(commission_amount - ROUND(crypto_atoms - (crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100))))) > 1;
|
OR ABS(commission_amount - ROUND(crypto_atoms - (crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100))))) > 1;
|
||||||
```
|
```
|
||||||
|
|
@ -485,9 +485,9 @@ SELECT
|
||||||
SUM(lt.base_amount) as total_distributed,
|
SUM(lt.base_amount) as total_distributed,
|
||||||
SUM(expected_base) as should_have_distributed,
|
SUM(expected_base) as should_have_distributed,
|
||||||
SUM(expected_base - lt.base_amount) as client_impact
|
SUM(expected_base - lt.base_amount) as client_impact
|
||||||
FROM satoshimachine.lamassu_transactions lt
|
FROM spirekeeper.lamassu_transactions lt
|
||||||
JOIN satoshimachine.dca_payments dp ON dp.lamassu_transaction_id = lt.id
|
JOIN spirekeeper.dca_payments dp ON dp.lamassu_transaction_id = lt.id
|
||||||
JOIN satoshimachine.dca_clients c ON dp.client_id = c.id
|
JOIN spirekeeper.dca_clients c ON dp.client_id = c.id
|
||||||
WHERE -- filter for affected transactions
|
WHERE -- filter for affected transactions
|
||||||
GROUP BY c.id;
|
GROUP BY c.id;
|
||||||
```
|
```
|
||||||
|
|
@ -505,7 +505,7 @@ GROUP BY c.id;
|
||||||
- Create compensating payments to affected clients:
|
- Create compensating payments to affected clients:
|
||||||
```sql
|
```sql
|
||||||
-- Add to client balances
|
-- Add to client balances
|
||||||
UPDATE satoshimachine.dca_clients c
|
UPDATE spirekeeper.dca_clients c
|
||||||
SET remaining_balance = remaining_balance + adjustment.amount
|
SET remaining_balance = remaining_balance + adjustment.amount
|
||||||
FROM (
|
FROM (
|
||||||
-- Calculate adjustment per client
|
-- Calculate adjustment per client
|
||||||
|
|
@ -572,7 +572,7 @@ assert abs(calculated_total - crypto_atoms) <= 1, "Commission calculation error
|
||||||
SELECT
|
SELECT
|
||||||
SUM(commission_amount) as expected_total_commission,
|
SUM(commission_amount) as expected_total_commission,
|
||||||
-- Compare to actual wallet balance in LNBits dashboard
|
-- Compare to actual wallet balance in LNBits dashboard
|
||||||
FROM satoshimachine.lamassu_transactions
|
FROM spirekeeper.lamassu_transactions
|
||||||
WHERE created_at > '<date-of-last-known-good-balance>';
|
WHERE created_at > '<date-of-last-known-good-balance>';
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -607,7 +607,7 @@ curl -X GET https://<lnbits-host>/api/v1/wallet \
|
||||||
SELECT commission_wallet_id,
|
SELECT commission_wallet_id,
|
||||||
LEFT(commission_wallet_adminkey, 10) || '...' as key_preview,
|
LEFT(commission_wallet_adminkey, 10) || '...' as key_preview,
|
||||||
updated_at
|
updated_at
|
||||||
FROM satoshimachine.lamassu_config
|
FROM spirekeeper.lamassu_config
|
||||||
ORDER BY updated_at DESC
|
ORDER BY updated_at DESC
|
||||||
LIMIT 1;
|
LIMIT 1;
|
||||||
```
|
```
|
||||||
|
|
@ -783,9 +783,9 @@ WITH client_financials AS (
|
||||||
COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0) as total_payments,
|
COALESCE(SUM(CASE WHEN p.status = 'completed' THEN p.amount_sats ELSE 0 END), 0) as total_payments,
|
||||||
COUNT(DISTINCT d.id) as deposit_count,
|
COUNT(DISTINCT d.id) as deposit_count,
|
||||||
COUNT(DISTINCT p.id) as payment_count
|
COUNT(DISTINCT p.id) as payment_count
|
||||||
FROM satoshimachine.dca_clients c
|
FROM spirekeeper.dca_clients c
|
||||||
LEFT JOIN satoshimachine.dca_deposits d ON c.id = d.client_id AND d.status = 'confirmed'
|
LEFT JOIN spirekeeper.dca_deposits d ON c.id = d.client_id AND d.status = 'confirmed'
|
||||||
LEFT JOIN satoshimachine.dca_payments p ON c.id = p.client_id
|
LEFT JOIN spirekeeper.dca_payments p ON c.id = p.client_id
|
||||||
GROUP BY c.id
|
GROUP BY c.id
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
|
|
@ -813,7 +813,7 @@ SELECT
|
||||||
SUM(commission_amount) as total_commission,
|
SUM(commission_amount) as total_commission,
|
||||||
MIN(created_at) as first_transaction,
|
MIN(created_at) as first_transaction,
|
||||||
MAX(created_at) as last_transaction
|
MAX(created_at) as last_transaction
|
||||||
FROM satoshimachine.lamassu_transactions;
|
FROM spirekeeper.lamassu_transactions;
|
||||||
|
|
||||||
-- 3. Failed/Pending Payments Check
|
-- 3. Failed/Pending Payments Check
|
||||||
SELECT
|
SELECT
|
||||||
|
|
@ -822,7 +822,7 @@ SELECT
|
||||||
SUM(amount_sats) as total_amount,
|
SUM(amount_sats) as total_amount,
|
||||||
MIN(created_at) as oldest,
|
MIN(created_at) as oldest,
|
||||||
MAX(created_at) as newest
|
MAX(created_at) as newest
|
||||||
FROM satoshimachine.dca_payments
|
FROM spirekeeper.dca_payments
|
||||||
GROUP BY status
|
GROUP BY status
|
||||||
ORDER BY
|
ORDER BY
|
||||||
CASE status
|
CASE status
|
||||||
|
|
@ -839,7 +839,7 @@ SELECT
|
||||||
status,
|
status,
|
||||||
created_at,
|
created_at,
|
||||||
EXTRACT(EPOCH FROM (NOW() - created_at))/3600 as hours_pending
|
EXTRACT(EPOCH FROM (NOW() - created_at))/3600 as hours_pending
|
||||||
FROM satoshimachine.dca_deposits
|
FROM spirekeeper.dca_deposits
|
||||||
WHERE status = 'pending'
|
WHERE status = 'pending'
|
||||||
AND created_at < NOW() - INTERVAL '48 hours'
|
AND created_at < NOW() - INTERVAL '48 hours'
|
||||||
ORDER BY created_at;
|
ORDER BY created_at;
|
||||||
|
|
@ -855,7 +855,7 @@ SELECT
|
||||||
commission_amount,
|
commission_amount,
|
||||||
ROUND(crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100))) as expected_base,
|
ROUND(crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100))) as expected_base,
|
||||||
base_amount - ROUND(crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100))) as difference
|
base_amount - ROUND(crypto_atoms / (1 + (commission_percentage * (100 - discount) / 100))) as difference
|
||||||
FROM satoshimachine.lamassu_transactions
|
FROM spirekeeper.lamassu_transactions
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
LIMIT 20;
|
LIMIT 20;
|
||||||
```
|
```
|
||||||
|
|
@ -876,15 +876,15 @@ psql -h localhost -U lnbits -d lnbits
|
||||||
2. **Direct Configuration Update**:
|
2. **Direct Configuration Update**:
|
||||||
```sql
|
```sql
|
||||||
-- Update Lamassu config directly
|
-- Update Lamassu config directly
|
||||||
UPDATE satoshimachine.lamassu_config
|
UPDATE spirekeeper.lamassu_config
|
||||||
SET polling_enabled = false
|
SET polling_enabled = false
|
||||||
WHERE id = (SELECT MAX(id) FROM satoshimachine.lamassu_config);
|
WHERE id = (SELECT MAX(id) FROM spirekeeper.lamassu_config);
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Manual Client Balance Update**:
|
3. **Manual Client Balance Update**:
|
||||||
```sql
|
```sql
|
||||||
-- ONLY in emergency when dashboard unavailable
|
-- ONLY in emergency when dashboard unavailable
|
||||||
UPDATE satoshimachine.dca_clients
|
UPDATE spirekeeper.dca_clients
|
||||||
SET remaining_balance = <correct_amount>
|
SET remaining_balance = <correct_amount>
|
||||||
WHERE id = <client_id>;
|
WHERE id = <client_id>;
|
||||||
-- MUST document this action in incident log
|
-- MUST document this action in incident log
|
||||||
|
|
@ -910,19 +910,19 @@ uv run lnbits
|
||||||
```bash
|
```bash
|
||||||
# Export all DCA-related tables to CSV
|
# Export all DCA-related tables to CSV
|
||||||
sqlite3 -header -csv /path/to/lnbits/database.sqlite \
|
sqlite3 -header -csv /path/to/lnbits/database.sqlite \
|
||||||
"SELECT * FROM satoshimachine.lamassu_transactions;" \
|
"SELECT * FROM spirekeeper.lamassu_transactions;" \
|
||||||
> lamassu_transactions_export_$(date +%Y%m%d_%H%M%S).csv
|
> lamassu_transactions_export_$(date +%Y%m%d_%H%M%S).csv
|
||||||
|
|
||||||
sqlite3 -header -csv /path/to/lnbits/database.sqlite \
|
sqlite3 -header -csv /path/to/lnbits/database.sqlite \
|
||||||
"SELECT * FROM satoshimachine.dca_payments;" \
|
"SELECT * FROM spirekeeper.dca_payments;" \
|
||||||
> dca_payments_export_$(date +%Y%m%d_%H%M%S).csv
|
> dca_payments_export_$(date +%Y%m%d_%H%M%S).csv
|
||||||
|
|
||||||
sqlite3 -header -csv /path/to/lnbits/database.sqlite \
|
sqlite3 -header -csv /path/to/lnbits/database.sqlite \
|
||||||
"SELECT * FROM satoshimachine.dca_deposits;" \
|
"SELECT * FROM spirekeeper.dca_deposits;" \
|
||||||
> dca_deposits_export_$(date +%Y%m%d_%H%M%S).csv
|
> dca_deposits_export_$(date +%Y%m%d_%H%M%S).csv
|
||||||
|
|
||||||
sqlite3 -header -csv /path/to/lnbits/database.sqlite \
|
sqlite3 -header -csv /path/to/lnbits/database.sqlite \
|
||||||
"SELECT * FROM satoshimachine.dca_clients;" \
|
"SELECT * FROM spirekeeper.dca_clients;" \
|
||||||
> dca_clients_export_$(date +%Y%m%d_%H%M%S).csv
|
> dca_clients_export_$(date +%Y%m%d_%H%M%S).csv
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -946,9 +946,9 @@ SELECT
|
||||||
dp.amount_sats as client_received,
|
dp.amount_sats as client_received,
|
||||||
dp.status as payment_status,
|
dp.status as payment_status,
|
||||||
dp.payment_hash
|
dp.payment_hash
|
||||||
FROM satoshimachine.lamassu_transactions lt
|
FROM spirekeeper.lamassu_transactions lt
|
||||||
LEFT JOIN satoshimachine.dca_payments dp ON dp.lamassu_transaction_id = lt.id
|
LEFT JOIN spirekeeper.dca_payments dp ON dp.lamassu_transaction_id = lt.id
|
||||||
LEFT JOIN satoshimachine.dca_clients c ON dp.client_id = c.id
|
LEFT JOIN spirekeeper.dca_clients c ON dp.client_id = c.id
|
||||||
ORDER BY lt.created_at DESC, c.username;
|
ORDER BY lt.created_at DESC, c.username;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -981,18 +981,18 @@ async def process_lamassu_transaction(txn_data: dict) -> Optional[LamassuTransac
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- Add unique constraint on transaction_id
|
-- Add unique constraint on transaction_id
|
||||||
ALTER TABLE satoshimachine.lamassu_transactions
|
ALTER TABLE spirekeeper.lamassu_transactions
|
||||||
ADD CONSTRAINT unique_transaction_id UNIQUE (transaction_id);
|
ADD CONSTRAINT unique_transaction_id UNIQUE (transaction_id);
|
||||||
|
|
||||||
-- Prevent negative balances
|
-- Prevent negative balances
|
||||||
ALTER TABLE satoshimachine.dca_clients
|
ALTER TABLE spirekeeper.dca_clients
|
||||||
ADD CONSTRAINT positive_balance CHECK (remaining_balance >= 0);
|
ADD CONSTRAINT positive_balance CHECK (remaining_balance >= 0);
|
||||||
|
|
||||||
-- Ensure positive amounts
|
-- Ensure positive amounts
|
||||||
ALTER TABLE satoshimachine.dca_deposits
|
ALTER TABLE spirekeeper.dca_deposits
|
||||||
ADD CONSTRAINT positive_deposit CHECK (amount > 0);
|
ADD CONSTRAINT positive_deposit CHECK (amount > 0);
|
||||||
|
|
||||||
ALTER TABLE satoshimachine.dca_payments
|
ALTER TABLE spirekeeper.dca_payments
|
||||||
ADD CONSTRAINT positive_payment CHECK (amount_sats > 0);
|
ADD CONSTRAINT positive_payment CHECK (amount_sats > 0);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -1196,7 +1196,7 @@ grep "wallet.*api\|payment_hash" lnbits.log | tail -50
|
||||||
### System Access
|
### System Access
|
||||||
|
|
||||||
**LNBits Admin Dashboard**:
|
**LNBits Admin Dashboard**:
|
||||||
- URL: `https://<your-lnbits-host>/satoshimachine`
|
- URL: `https://<your-lnbits-host>/spirekeeper`
|
||||||
- Requires superuser authentication
|
- Requires superuser authentication
|
||||||
|
|
||||||
**Database Access**:
|
**Database Access**:
|
||||||
|
|
@ -1205,7 +1205,7 @@ grep "wallet.*api\|payment_hash" lnbits.log | tail -50
|
||||||
sqlite3 /home/padreug/AioLabs/Git/lnbits-extensions/lnbits/data/database.sqlite
|
sqlite3 /home/padreug/AioLabs/Git/lnbits-extensions/lnbits/data/database.sqlite
|
||||||
|
|
||||||
# Direct table access
|
# Direct table access
|
||||||
sqlite3 /path/to/db "SELECT * FROM satoshimachine.<table_name>;"
|
sqlite3 /path/to/db "SELECT * FROM spirekeeper.<table_name>;"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Log Files**:
|
**Log Files**:
|
||||||
|
|
@ -1274,7 +1274,7 @@ pkill -f lnbits
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- Disable automatic polling
|
-- Disable automatic polling
|
||||||
UPDATE satoshimachine.lamassu_config
|
UPDATE spirekeeper.lamassu_config
|
||||||
SET polling_enabled = false;
|
SET polling_enabled = false;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -1283,7 +1283,7 @@ SET polling_enabled = false;
|
||||||
```sql
|
```sql
|
||||||
-- All client balances summary
|
-- All client balances summary
|
||||||
SELECT id, username, remaining_balance, created_at
|
SELECT id, username, remaining_balance, created_at
|
||||||
FROM satoshimachine.dca_clients
|
FROM spirekeeper.dca_clients
|
||||||
ORDER BY remaining_balance DESC;
|
ORDER BY remaining_balance DESC;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -1291,7 +1291,7 @@ ORDER BY remaining_balance DESC;
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT id, transaction_id, created_at, crypto_atoms, base_amount, commission_amount
|
SELECT id, transaction_id, created_at, crypto_atoms, base_amount, commission_amount
|
||||||
FROM satoshimachine.lamassu_transactions
|
FROM spirekeeper.lamassu_transactions
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
LIMIT 10;
|
LIMIT 10;
|
||||||
```
|
```
|
||||||
|
|
@ -1299,7 +1299,7 @@ LIMIT 10;
|
||||||
### Failed Payments
|
### Failed Payments
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT * FROM satoshimachine.dca_payments
|
SELECT * FROM spirekeeper.dca_payments
|
||||||
WHERE status != 'completed'
|
WHERE status != 'completed'
|
||||||
ORDER BY created_at DESC;
|
ORDER BY created_at DESC;
|
||||||
```
|
```
|
||||||
|
|
@ -1314,19 +1314,19 @@ BACKUP_DIR="emergency_backup_${DATE}"
|
||||||
mkdir -p $BACKUP_DIR
|
mkdir -p $BACKUP_DIR
|
||||||
|
|
||||||
sqlite3 -header -csv /path/to/database.sqlite \
|
sqlite3 -header -csv /path/to/database.sqlite \
|
||||||
"SELECT * FROM satoshimachine.lamassu_transactions;" \
|
"SELECT * FROM spirekeeper.lamassu_transactions;" \
|
||||||
> ${BACKUP_DIR}/lamassu_transactions.csv
|
> ${BACKUP_DIR}/lamassu_transactions.csv
|
||||||
|
|
||||||
sqlite3 -header -csv /path/to/database.sqlite \
|
sqlite3 -header -csv /path/to/database.sqlite \
|
||||||
"SELECT * FROM satoshimachine.dca_payments;" \
|
"SELECT * FROM spirekeeper.dca_payments;" \
|
||||||
> ${BACKUP_DIR}/dca_payments.csv
|
> ${BACKUP_DIR}/dca_payments.csv
|
||||||
|
|
||||||
sqlite3 -header -csv /path/to/database.sqlite \
|
sqlite3 -header -csv /path/to/database.sqlite \
|
||||||
"SELECT * FROM satoshimachine.dca_deposits;" \
|
"SELECT * FROM spirekeeper.dca_deposits;" \
|
||||||
> ${BACKUP_DIR}/dca_deposits.csv
|
> ${BACKUP_DIR}/dca_deposits.csv
|
||||||
|
|
||||||
sqlite3 -header -csv /path/to/database.sqlite \
|
sqlite3 -header -csv /path/to/database.sqlite \
|
||||||
"SELECT * FROM satoshimachine.dca_clients;" \
|
"SELECT * FROM spirekeeper.dca_clients;" \
|
||||||
> ${BACKUP_DIR}/dca_clients.csv
|
> ${BACKUP_DIR}/dca_clients.csv
|
||||||
|
|
||||||
echo "Backup complete in ${BACKUP_DIR}/"
|
echo "Backup complete in ${BACKUP_DIR}/"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue