diff --git a/api/app/btcpay_routes.py b/api/app/btcpay_routes.py index 985644b..eefeaeb 100644 --- a/api/app/btcpay_routes.py +++ b/api/app/btcpay_routes.py @@ -198,8 +198,17 @@ def _after_expiration(invoice: Invoice) -> bool: return detected_at > expires_at +# Wownero uses 11 decimal places (atomic unit = 10^-11 WOW), NOT 12 like +# Monero. Forked from xmrcheckout and MUST be corrected wherever the constant +# appears. Using 10^12 here broke payment detection — required_atomic came +# out 10× larger than what wallet-rpc actually reports on the subaddress, so +# is_paid never flipped and invoices stayed pending forever. +WOW_ATOMIC_UNITS = Decimal("100000000000") # 10^11 +WOW_ATOMIC_QUANTIZE = Decimal("0.00000000001") # 11 decimal places + + def _xmr_to_atomic(amount: Decimal) -> int: - quantized = (Decimal(amount) * Decimal("1000000000000")).to_integral_value( + quantized = (Decimal(amount) * WOW_ATOMIC_UNITS).to_integral_value( rounding=ROUND_DOWN ) return int(quantized) @@ -218,15 +227,15 @@ def _btcpay_amount_currency(invoice: Invoice) -> tuple[str, str, dict[str, Any]] def _format_xmr_fixed(value: Decimal) -> str: """Format WOW amount with trailing zeros stripped.""" - formatted = f"{value.quantize(Decimal('0.000000000001')):.12f}" + formatted = f"{value.quantize(WOW_ATOMIC_QUANTIZE):.11f}" if "." in formatted: formatted = formatted.rstrip("0").rstrip(".") return formatted def _atomic_to_xmr(value: int) -> Decimal: - return (Decimal(value) / Decimal("1000000000000")).quantize( - Decimal("0.000000000001"), rounding=ROUND_DOWN + return (Decimal(value) / WOW_ATOMIC_UNITS).quantize( + WOW_ATOMIC_QUANTIZE, rounding=ROUND_DOWN ) diff --git a/api/app/reconciler.py b/api/app/reconciler.py index 0fea2c8..fd66c0b 100644 --- a/api/app/reconciler.py +++ b/api/app/reconciler.py @@ -417,8 +417,16 @@ def _safe_update_reconciler_status( +# Wownero uses 11 decimal places (atomic unit = 10^-11 WOW), NOT 12 like +# Monero. Using 10^12 here makes required_atomic 10× larger than actual paid +# amount, so is_paid never becomes true and invoices stay pending forever +# even after detection + confirmations. This entire codebase was forked from +# xmrcheckout and the constant must be corrected wherever it appears. +WOW_ATOMIC_UNITS = Decimal("100000000000") # 10^11 + + def _xmr_to_atomic(amount: Decimal) -> int: - quantized = (Decimal(amount) * Decimal("1000000000000")).to_integral_value( + quantized = (Decimal(amount) * WOW_ATOMIC_UNITS).to_integral_value( rounding=ROUND_DOWN ) return int(quantized) diff --git a/api/app/routes.py b/api/app/routes.py index 25c6b4a..4ee6633 100644 --- a/api/app/routes.py +++ b/api/app/routes.py @@ -302,8 +302,10 @@ def _public_invoice_status_response( metadata = invoice.metadata_json or {} update: dict[str, Any] = {} if invoice.total_paid_atomic is not None: - total_paid = (Decimal(invoice.total_paid_atomic) / Decimal("1000000000000")).quantize( - Decimal("0.000000000001"), rounding=ROUND_DOWN + # Wownero uses 11 decimal places (10^11), NOT 12 like Monero. Using + # 10^12 here under-reports amount_paid by a factor of 10. + total_paid = (Decimal(invoice.total_paid_atomic) / Decimal("100000000000")).quantize( + Decimal("0.00000000001"), rounding=ROUND_DOWN ) update["amount_paid_xmr"] = format_xmr_amount(total_paid) btcpay_data = metadata.get("btcpay") if isinstance(metadata, dict) else None