Skip to content

Spending limit: cover the manual lightning_pay_invoice path #572

@biwasxyz

Description

@biwasxyz

Summary

The default-on wallet spending limit (src/services/spend-limiter.ts, added in #571) meters transfer_stx, transfer_btc, and x402/L402 auto-payments, but not the manual lightning_pay_invoice tool (src/tools/lightning.tools.ts). That tool currently pays a BOLT-11 invoice at its full face value with no amount cap — only the routing fee (maxFeeSats) is bounded.

This was flagged in the security audit (finding F3) and deferred from #571.

What's needed

  1. Decode the BOLT-11 invoice to get the amount in sats, reusing the existing decoder:
    import { decode as decodeBolt11 } from "light-bolt11-decoder";
    (same pattern as the L402 path in src/services/x402.service.ts ~line 488 — find the amount section, convert msat → sats).
  2. Refuse amountless invoices (consistent with the L402 path, which already does this — an unmeterable spend defeats the cap).
  3. await getSpendLimiter().check("sats", amountSats, addr) before provider.payInvoice(...).
  4. await getSpendLimiter().record("sats", amountSats, addr) after a successful pay.

Open question — the ledger key

The sats ledger is keyed by the wallet's Stacks address so BTC L1 + sBTC + L402 spends share one budget. But the Lightning wallet has its own session (~/.aibtc/lightning/keystore.json, unlocked separately via lightning_unlock), so the main STX wallet (getActiveAccount()) may be locked during a Lightning pay.

The L402 path handles this by keying on getWalletManager().getActiveAccount()?.address and skipping the check when it's absent — acceptable for L402 but leaves a gap if we want lightning_pay_invoice reliably metered.

Options to resolve:

  • (a) Key by the active Stacks address; fall back to a constant Lightning ledger key (e.g. __lightning__) when the main wallet is locked, so it's always metered (separate bucket when STX is locked, shared bucket otherwise).
  • (b) Derive the Stacks address from the Lightning session mnemonic to always key consistently (more coupling).
  • (c) Require the main wallet to be unlocked to meter (UX regression — previously only the Lightning wallet needed unlocking).

(a) is the pragmatic default; pick one and document it.

Acceptance criteria

  • lightning_pay_invoice rejects a pay that would exceed the remaining sats budget, before paying.
  • Amountless invoices are refused.
  • Successful pays decrement the sats ledger.
  • Ledger-key behavior documented (when STX wallet locked vs unlocked).
  • Tests covering over-budget block + successful record.

Out of scope

Contract-call swaps (ALEX/Bitflow/Zest/Jing/Styx) — intentionally not metered (the amount isn't a direct chokepoint param).

Follow-up to #571.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions