Fork xmrcheckout for Wownero (WOW)

Minimal changes from upstream xmrcheckout to support Wownero:
- Rate provider: Nonlogs WOW-BTC via Kraken BTC/USD
- Subaddress derivation: Wownero network byte overrides (53/63/54)
- Payment URI scheme: wownero:
- Dockerfile: Wownero binaries from Codeberg
- Display text: Monero→Wownero, XMR→WOW throughout UI
- Default daemon: 192.168.88.235:34568
- Added wownero-logo.svg

File and folder structure kept identical to upstream for easy sync.
This commit is contained in:
jwinterm
2026-03-09 11:08:55 -04:00
commit f8f9b4424c
111 changed files with 23058 additions and 0 deletions
+122
View File
@@ -0,0 +1,122 @@
from __future__ import annotations
import logging
import uuid
from datetime import datetime
import requests
from requests import RequestException
from sqlalchemy.orm import Session
from .formatting import format_xmr_amount
from .models import Invoice, User, Webhook, WebhookDelivery
from .security import decrypt_secret, encrypt_secret, generate_webhook_secret
logger = logging.getLogger(__name__)
def build_webhook_payload(event: str, invoice: Invoice) -> dict[str, object]:
return {
"event": event,
"invoice": {
"id": str(invoice.id),
"address": invoice.address,
"subaddress_index": invoice.subaddress_index,
"amount_xmr": format_xmr_amount(invoice.amount_xmr),
"status": invoice.status,
"confirmation_target": invoice.confirmation_target,
"confirmations": invoice.confirmations or 0,
"paid_after_expiry": bool(invoice.paid_after_expiry),
"paid_after_expiry_at": _isoformat(invoice.paid_after_expiry_at),
"metadata": invoice.metadata_json,
"created_at": _isoformat(invoice.created_at),
"expires_at": _isoformat(invoice.expires_at),
"detected_at": _isoformat(invoice.detected_at),
"confirmed_at": _isoformat(invoice.confirmed_at),
},
}
def dispatch_webhooks(
db: Session,
user_id: str,
event: str,
invoice: Invoice,
) -> None:
hooks = (
db.query(Webhook)
.filter(
Webhook.user_id == user_id,
Webhook.active.is_(True),
)
.all()
)
user = db.query(User).filter(User.id == user_id).first()
webhook_secret = None
if user is not None:
webhook_secret = _ensure_webhook_secret(db, user)
payload = build_webhook_payload(event, invoice)
deliveries: list[WebhookDelivery] = []
user_uuid = uuid.UUID(user_id)
for hook in hooks:
if event not in hook.events:
continue
hook_urls = hook.event_urls or {}
target_url = hook_urls.get(event, hook.url)
if not target_url:
logger.warning(
"Webhook URL missing for event",
extra={"webhook_id": str(hook.id), "event": event},
)
continue
status_code = None
error_message = None
try:
headers = {"X-Webhook-Secret": webhook_secret} if webhook_secret else None
response = requests.post(target_url, json=payload, headers=headers, timeout=5)
status_code = response.status_code
except RequestException as exc:
error_message = str(exc)
logger.warning(
"Webhook delivery failed",
extra={"webhook_id": str(hook.id), "event": event},
)
logger.debug("Webhook delivery error: %s", exc)
deliveries.append(
WebhookDelivery(
user_id=user_uuid,
webhook_id=hook.id,
event=event,
url=target_url,
invoice_id=invoice.id,
invoice_address=invoice.address,
invoice_subaddress_index=invoice.subaddress_index,
invoice_amount_xmr=invoice.amount_xmr,
invoice_status=invoice.status,
payload_json=payload,
http_status=status_code,
error_message=error_message,
)
)
if deliveries:
try:
db.add_all(deliveries)
db.commit()
except Exception as exc:
db.rollback()
logger.warning("Failed to save webhook delivery history", extra={"error": str(exc)})
def _isoformat(value: datetime | None) -> str | None:
return value.isoformat() if value is not None else None
def _ensure_webhook_secret(db: Session, user: User) -> str:
if user.webhook_secret_encrypted:
return decrypt_secret(user.webhook_secret_encrypted)
secret = generate_webhook_secret()
user.webhook_secret_encrypted = encrypt_secret(secret)
db.add(user)
db.commit()
db.refresh(user)
return secret