The node is an interface, so the code where money is actually at risk is tested against a fake that can be made to fail, stall, or lie. Plugging in Alby Hub is configuration, not new code. Crediting a deposit is idempotent by payment hash: a node reporting the same settlement twice must not mint money. Withdrawals debit before they pay, because a payment that succeeds while the ledger write fails loses money permanently, whereas the reverse is recoverable. Withdrawals above a threshold wait for a human, which bounds what a stolen session token can remove. 17 tests including concurrent settlement, concurrent double-spend, concurrent processors, failed payment refunds, fee caps, and solvency. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
40 lines
1.5 KiB
SQL
40 lines
1.5 KiB
SQL
-- Lightning deposits and withdrawals.
|
|
--
|
|
-- Invoices key on payment_hash, which is what makes crediting idempotent: a
|
|
-- node that reports the same settlement twice cannot produce two credits.
|
|
|
|
CREATE TABLE lightning_invoices (
|
|
payment_hash TEXT PRIMARY KEY,
|
|
account_id BIGINT NOT NULL REFERENCES accounts(id),
|
|
amount_msat BIGINT NOT NULL CHECK (amount_msat > 0),
|
|
bolt11 TEXT NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
expires_at TIMESTAMPTZ,
|
|
-- Set exactly once, when the payment is credited to the ledger.
|
|
credited_at TIMESTAMPTZ
|
|
);
|
|
|
|
CREATE INDEX lightning_invoices_account_idx ON lightning_invoices (account_id, created_at DESC);
|
|
CREATE INDEX lightning_invoices_pending_idx ON lightning_invoices (created_at)
|
|
WHERE credited_at IS NULL;
|
|
|
|
CREATE TYPE withdrawal_status AS ENUM
|
|
('queued', 'needs_approval', 'sending', 'paid', 'failed', 'rejected');
|
|
|
|
CREATE TABLE lightning_withdrawals (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
account_id BIGINT NOT NULL REFERENCES accounts(id),
|
|
bolt11 TEXT NOT NULL,
|
|
amount_msat BIGINT NOT NULL CHECK (amount_msat > 0),
|
|
status withdrawal_status NOT NULL,
|
|
payment_hash TEXT,
|
|
fee_msat BIGINT,
|
|
failure TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
resolved_at TIMESTAMPTZ
|
|
);
|
|
|
|
CREATE INDEX lightning_withdrawals_account_idx ON lightning_withdrawals (account_id, id DESC);
|
|
CREATE INDEX lightning_withdrawals_pending_idx ON lightning_withdrawals (id)
|
|
WHERE status IN ('queued', 'needs_approval', 'sending');
|