With a variable upstream ($ui_upstream, added for docker-DNS re-resolution),
a trailing-slash URI in proxy_pass makes nginx forward '/' to the UI for every
sub-path, so /_next/static/*.css and /i/<invoice> all return the app index HTML
(200 text/html) -> the UI renders unstyled and client routing breaks. Omit the
URI to preserve the full request path, matching the /api/ block.
Exposes the WOW→fiat spot rate the invoice path already uses
internally (Nonlogs WOW-BTC × Kraken BTC-USD, 60s cache). No auth.
Allows downstream services (Medusa storefront) to display a live
USD reference price for WOW-denominated products without hard-coding
a third-party rate provider on the storefront side.
Follow-up to the previous commit — page.tsx now passes
default_qr_logo of type "wownero" | "monero" | "none" | "custom" to
this component, but the prop was still typed as the narrower
"monero" | "none" | "custom" set. Next build failed with TS2322.
Widened initialLogo prop + the internal useState + buildInitialState
helper to match.
ProfileResponse schema's default_qr_logo literal was inherited from
xmrcheckout: Literal["monero", "none", "custom"]. The DB column
(models.py) defaults to "wownero" and main.py's migration backfills
NULL → ... but the backfill also wrote "monero", contradicting the
column default.
End result: new users got default_qr_logo="wownero" (from the
column default), but GET /api/core/profile blew up with a Pydantic
ResponseValidationError on serialization because "wownero" isn't in
the literal. UI showed "Unavailable" for both Store ID and Primary
Address because the entire profile response 500'd, leaving
profileData null on the dashboard. API key + webhook secret rendered
fine because those come from cookies, not profileData.
Fix:
- schemas.py: QrLogoMode now Literal["wownero", "monero", "none",
"custom"] (keep "monero" for backward compat with any rows from
before the migration ran)
- schemas.py: ProfileResponse.default_qr_logo default → "wownero"
- main.py: backfill UPDATE → 'wownero' (was 'monero'); migration
column DEFAULT already correct as 'wownero'
- ui/dashboard/page.tsx: TS type for default_qr_logo widened to
include "wownero"; fallback default → "wownero"
- ui/dashboard/actions.ts: DefaultQrLogoState.logo type widened;
validation in updateDefaultQrLogo + initialMetadataPayload now
accepts "wownero"
Suchshop didn't hit this because their wowcheckout user row was
created back when this UI path wasn't being used (they were
provisioned via direct SQL/API). The new shop.wowne.ro merchant
created via the UI today triggered the column default → exposed
the schema mismatch.
isLikelyPrimaryAddress() required exactly 95 chars starting with "4"
— that's the Monero primary-address shape. Wownero primaries are 97
chars starting with "Wo". This was inherited from the xmrcheckout
fork and never updated.
Effect: pasting a valid Wownero address into the login form silently
failed isLikelyPrimaryAddress(), the validate API was never called,
no green VALID indicator rendered, canSubmit stayed false, the Sign
in button stayed greyed out. Suchshop sidestepped this because that
tenant was provisioned via direct SQL/API, not through this form.
Fix: 95 → 97, "4" → "Wo". One file, two lines.
The backend cryptographic validation in routes.py:
_validate_payment_address_and_view_key() was correct all along — it
checks the 2-byte Wownero varint prefix (byte 0 == 178). The UI just
wasn't asking it.
Commit 696b04e ported routes.py changes from upstream xmrcheckout
and silently reverted the Wownero-specific address validation
introduced in 20eff23. Result: every genuine Wownero address has
been rejected with "Primary address is invalid" since that port,
making /admin/merchants registration impossible for new merchants.
Re-applies the 2-byte varint prefix decoder + keccak checksum +
ed25519 view-key verification from 20eff23, and drops the now-unused
`from monero.address import Address, IntegratedAddress, SubAddress`
line so future upstream pulls do not silently re-introduce the
footgun (the import is the load-bearing tell that something has
re-pulled the upstream Monero-only code path).
Previously nginx resolved service names (api, ui) to IPs once at startup and cached them forever. When the api container is rebuilt (e.g. for a code fix) Docker assigns it a new IP and nginx keeps hitting the old dead one, returning 502 Bad Gateway on every request until nginx is manually restarted.
Actual impact tonight: after deploying the Wownero 10^11 decimals fix, wowcheckout-api-1 came up at 172.20.0.6 but nginx kept trying 172.20.0.9. Medusa's provider.authorizePayment call via the LAN URL 502'd. Medusa treated that as 'payment not authorized', refused to create the order, and the customer's WOW payment silently vanished even though the reconciler detected it correctly on the xmrcheckout side.
Use 'resolver 127.0.0.11 valid=10s' + 'set $api_upstream …' so nginx re-resolves via Docker's internal DNS every ~10s. Stale-IP window is now bounded to 10s max.
The codebase was forked from xmrcheckout and kept Monero's 10^12 atomic-unit constant everywhere. Wownero uses 11 decimal places, not 12.
Effect: _xmr_to_atomic(amount) produced a required_atomic value 10× larger than what wallet-rpc actually reports for the subaddress. Invoices flagged total_paid_atomic correctly (that comes from the daemon) but is_paid = total_paid >= required was always false, so the reconciler never flipped status pending → payment_detected, webhook never fired, Medusa never saw the payment.
Fix all four places: reconciler._xmr_to_atomic, btcpay_routes._xmr_to_atomic, btcpay_routes._atomic_to_xmr, btcpay_routes._format_xmr_fixed, routes.py invoice-status response. Extracted a WOW_ATOMIC_UNITS constant so this doesn't get lost again when we touch these files.
Mirror of xmrcheckout commit eb15c52. Same two bugs, same fix.
1. _build_payload now emits amount (fiat), currency, and amount_crypto so Medusa's capture workflow captures the right value instead of 0 (which leaves payments authorized forever).
2. get_status() no longer calls set_daemon without an open wallet (was flooding logs with RPC -13). _daemon_height() probe is sufficient for daemon reachability.
3. Silenced monero.backends.jsonrpc.wallet logger for expected transient close_wallet errors.
The checkout UI only redirected when status was 'confirmed' (after all
N confirmations). Now also redirects on 'payment_detected' (0-conf in
mempool). This matches BTCPay's behavior for BTC/LTC where the customer
is redirected to the store immediately on detection.
Ports xmrcheckout upstream features (7 commits since 655d037):
- SystemStatus model for reconciler heartbeat tracking
- Monero connectivity caching in system status
- Health status endpoints in routes
- Hardened reconciler status updates with SQLAlchemyError handling
- Status schema additions
Adapted for Wownero: 3-decimal precision (0.001 WOW) in amount rounding.
- Advisory lock: wrap per-invoice processing in try/finally so lock is
ALWAYS released, even on exception. Previously, any error between lock
acquisition and release would leak the lock, causing the invoice to be
silently skipped forever on subsequent cycles.
- Error logging: wallet RPC errors now logged at ERROR level (was DEBUG),
making silent reconciliation failures visible in production logs.
- Transfer protection: don't delete stored transfers when wallet-rpc
returns an empty response (transient RPC glitch). Only prune when we
actually got transfer data back.
- Wallet restore height: new wallets generated from keys now look back
~2 days (1440 blocks) instead of starting at current height. Prevents
missing payments that arrived before the wallet was first opened on a
given wallet-rpc instance.
- Decimal formatting: _format_xmr_fixed() now strips trailing zeros
instead of always showing 12 decimal places.
- Invoice amount rounding: fiat-converted amounts now use ROUND_UP with
3 decimal places (was ROUND_DOWN with 12), preventing undercharging.
- Webhook retries: BTCPay webhook delivery now retries up to 3 times
with 1s/2s backoff on 5xx or network errors. Timeout increased 5s→10s.
Failures logged at ERROR level (was WARNING/DEBUG).
- POST/GET/DELETE /api/admin/users endpoints
- Admin UI at /admin with login, user list, create/delete
- Auth via ADMIN_API_KEY env var (separate from tenant auth)
- Tenants remain scoped to their own store only