Includes: API (Express/TypeScript), Astro site, Python workers, document generators, FCC compliance tools, Canada CRTC formation, Ansible infrastructure, and deployment scripts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
95 lines
3.2 KiB
Python
95 lines
3.2 KiB
Python
"""Crypto offramp module.
|
|
|
|
Converts coins received by SHKeeper into USD at RelayFi bank via
|
|
Coinbase Prime — sole provider per plan. See
|
|
/home/justin/.claude/plans/swirling-napping-sonnet.md.
|
|
|
|
Exports:
|
|
Quote, ExecutionResult, ProviderStatus — dataclasses
|
|
Rail — literal type alias
|
|
OfframpError, InsufficientBalanceError, KycHoldError — exception hierarchy
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime
|
|
from decimal import Decimal
|
|
from typing import Literal, Optional
|
|
|
|
Rail = Literal["ach", "wire", "rtp"]
|
|
|
|
# Supported coins (XMR intentionally excluded — Coinbase Prime doesn't offramp it).
|
|
SUPPORTED_COINS: set[str] = {
|
|
"BTC", "ETH", "MATIC", "LTC", "TRX", "BNB", "DOGE", "USDC",
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class Quote:
|
|
"""Pre-execute price quote from the offramp provider."""
|
|
from_coin: str
|
|
to_currency: str = "USD"
|
|
amount_coin: Decimal = Decimal("0")
|
|
estimated_usd_cents: int = 0
|
|
fx_rate_usd: Decimal = Decimal("0") # 1 coin = N USD
|
|
fee_usd_cents: int = 0 # provider fee estimate
|
|
valid_until: Optional[datetime] = None
|
|
quote_id: Optional[str] = None # provider-side quote handle, if any
|
|
|
|
|
|
@dataclass
|
|
class ExecutionResult:
|
|
"""Outcome of calling `execute()` — the sell + wire request was accepted."""
|
|
provider_ref: str # Prime order id / transfer id
|
|
accepted_usd_cents: int # what the provider promised to settle
|
|
fill_price_usd: Decimal # actual coin→USD price achieved
|
|
state: Literal["submitted", "partial", "completed", "failed"]
|
|
rail: Rail
|
|
memo: Optional[str] = None # e.g., "PW-ORDER-FO-2026-0042"
|
|
raw: dict = field(default_factory=dict) # provider-native response for debugging
|
|
|
|
|
|
@dataclass
|
|
class ProviderStatus:
|
|
"""Current state of a previously-executed offramp."""
|
|
provider_ref: str
|
|
state: Literal[
|
|
"submitted", "selling", "selling_complete", "wiring", "completed",
|
|
"failed", "held",
|
|
]
|
|
settled_usd_cents: int = 0
|
|
fill_price_usd: Optional[Decimal] = None
|
|
wire_tx_id: Optional[str] = None
|
|
error_message: Optional[str] = None
|
|
raw: dict = field(default_factory=dict)
|
|
|
|
|
|
# ── Exception hierarchy ─────────────────────────────────────────────────
|
|
|
|
class OfframpError(Exception):
|
|
"""Base for all offramp errors."""
|
|
|
|
|
|
class InsufficientBalanceError(OfframpError):
|
|
"""Exchange side doesn't have enough of the coin credited yet."""
|
|
|
|
|
|
class KycHoldError(OfframpError):
|
|
"""Exchange paused our withdrawal for AML/KYC review — admin must resolve."""
|
|
|
|
|
|
class SlippageExceededError(OfframpError):
|
|
"""Realized fill price was outside MAX_SLIPPAGE_BPS of quote."""
|
|
|
|
|
|
class ProviderDegradedError(OfframpError):
|
|
"""Repeated 5xx/429 — circuit breaker tripped."""
|
|
|
|
|
|
__all__ = [
|
|
"Rail", "SUPPORTED_COINS",
|
|
"Quote", "ExecutionResult", "ProviderStatus",
|
|
"OfframpError", "InsufficientBalanceError", "KycHoldError",
|
|
"SlippageExceededError", "ProviderDegradedError",
|
|
]
|