Commit graph

2 commits

Author SHA1 Message Date
Local Dev
8b76f16c1e prometheus: size-tiered pricing, 2 GiB cap, new hero copy, SEO LD
Three threads, one commit:

1. Size-tiered listing fee instead of a flat $1

   priceUsdForSize(bytes) in service/payments.js:
     0 - 100 MiB         → free
     100.001 - 200 MiB   → $1
     200.001 - 300 MiB   → $2
     ...
     1900.001 - 2000 MiB → $19
     2000.001 - 2048 MiB → $20 (ceiling at 2 GiB)

   listingNeedsPayment(bytes) replaces paymentsRequired() — a free-tier
   upload always publishes immediately even when PAYMENT_REQUIRED=1; the
   operator switch only governs the paid tiers. createInvoice derives the
   fee from the listing's own size at invoice time.

2. Upload cap back to 2 GiB

   The 200-MiB cap from the previous commit (which fixed the Caddy-413
   HTML bug) is reverted. The paywall is now the real size gate; Caddy
   is just the ceiling. p2pexchange Caddyfile already updated + reloaded.

3. Hero + SEO

   Hero leads with the plain-English product category:
     The decentralized app store.
     A home for apps, programs, dApps, and libraries — listed and settled
     on the Bitcoin Cash chain, hosted on the decentralized web, endorsed
     by signature. Anyone can publish. Nobody can gatekeep.

     Named for the one who gave fire to humans and did not ask the gods first.

   "hosted on Sia" → "hosted on the decentralized web". Sia is the
   mechanism, not the pitch — general readers aren't tripped on the
   acronym on the hero.

   Meta description rewritten to lead with "decentralized app store" and
   name the long-tail terms the audience searches for ("web3 browsers",
   "BCH wallets", "mobile and desktop apps").

   Two JSON-LD blocks (WebSite + Organization) inlined on home via
   layout's headExtra channel — knowledge panel + WebSite result card.

4. Submit wizard

   Step 1 result shows the computed listing fee under the scan verdict,
   so publishers see the price BEFORE filling out the manifest.

   Step 4 publish branches on the server's payment_required flag. On a
   paid listing it POSTs /api/invoice right after the listing POST and
   renders a payment card with the exact sat amount, the operator receive
   address, a `bitcoincash:` URI the wallet opens, and the invoice status
   URL for polling.
2026-10-22 23:31:43 +03:00
Local Dev
34efb319cc prometheus: BCH payments — invoices, price oracle, admin reconcile
Payment flow for the paid wave (the free wave is still the default):

  1. Publisher POSTs /api/listing. If PAYMENT_REQUIRED=1 is set, the
     listing lands with payment_status='pending' and is hidden from
     browse. If not set, the listing goes straight to 'free' and is
     visible immediately (today's behaviour, unchanged).
  2. Anyone (publisher, their script, their wallet) POSTs
     /api/invoice {listing_id} and gets back a one-shot invoice:
     operator receive address, exact sat amount (USD-pegged + random
     1..256 sat jitter so no two in-flight invoices collide),
     bitcoincash: URI for a wallet, 20-min expiry.
  3. Reader pays. The amount matches exactly one pending invoice, and
     when the mempool watcher reports (address, amount, txid) to
     tryMatchPayment, both the invoice and the listing flip to paid.
     Browse starts showing the listing.

The mempool watcher that would do (3) automatically is deliberately NOT
in this commit — it needs an electrum-cash subscription or a block
explorer webhook, and that's a next milestone. In the meantime, admins
reconcile by hand from /admin/payments, which shows every invoice with
a Mark paid form (optional txid field). Same effect on the DB, same
audit log entry, different `paid_by` string.

Design decisions worth recording:
- One receive address + amount matching, NOT an HD wallet per invoice.
  BitPay used this for years; it is simple, auditable, and doesn't
  require a seed on the server to spend from later.
- Random few-sat jitter per invoice guarantees uniqueness of the
  (address, amount) pair across all in-flight invoices.
- Price oracle is CoinGecko (public, no key, 60s cache, 15-min stale
  fallback). If both the fresh fetch and the stale fallback fail,
  /api/invoice refuses — issuing invoices on a stale price is worse
  than refusing to quote until the oracle recovers.
- Quote is locked at issue time; the 20-min deadline is the lock.
- Lazy expiry: GET /api/invoice/<id> flips an expired pending invoice
  to 'expired' as a side effect, so there's no sweeper to run.

Admin UI /admin/payments shows every invoice (pending/paid/expired),
plus a dashboard-ish header with the configured mode + receive address.
The gating config appears in the UI even when payments are disabled, so
an operator spinning up a new instance can see at a glance what's set.

Config:
  PAYMENT_REQUIRED=1              — turn paid mode on
  PROMETHEUS_PAYMENT_ADDRESS=…    — the operator's receive address
  PROMETHEUS_LISTING_FEE_USD=1    — the fee (default 1 USD)
  PROMETHEUS_PRICE_SOURCE=…       — override the CoinGecko URL

End-to-end verified:
  PAYMENT_REQUIRED=1 → new listing hidden from browse → /api/invoice
  returns correct sats at live BCH price → admin login + mark-paid →
  listing becomes visible → /api/invoice/<id> returns status 'paid'
  with paid_by='manual:admin.x'.
2026-10-08 21:52:01 +02:00