fix: Wownero atomic unit is 10^11, not 10^12

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.
This commit is contained in:
jwinterm
2026-04-19 15:51:45 -04:00
parent 240c2b2b9f
commit f9159f5877
3 changed files with 26 additions and 7 deletions
+13 -4
View File
@@ -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
)
+9 -1
View File
@@ -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)
+4 -2
View File
@@ -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