Cleanup: remove internal references and scaffolding
This commit is contained in:
parent
b4a81e0ab8
commit
812393d030
10 changed files with 72 additions and 141 deletions
108
README.md
108
README.md
|
|
@ -1,35 +1,16 @@
|
|||
# cardano-checkout
|
||||
|
||||
Merchant-side Cardano payment lifecycle in Python. Zero-custody by design.
|
||||
Merchant-side Cardano payment lifecycle in Python. Zero-custody.
|
||||
|
||||
**What we ship:** the invoice state machine + UTxO watcher + reprice
|
||||
loop. Per-invoice HD-derived receive addresses, Koios polling, confirm
|
||||
/ underpay / overpay classification, time-windowed repricing against
|
||||
Ships the invoice state machine + UTxO watcher + reprice loop.
|
||||
Per-invoice HD-derived receive addresses, Koios polling, confirm /
|
||||
underpay / overpay classification, time-windowed repricing against
|
||||
your own oracle.
|
||||
|
||||
**What we don't ship:** Cardano primitives. Address derivation, chain
|
||||
context, transaction building, native-script minting, signing — those
|
||||
are all [pycardano](https://github.com/Python-Cardano/pycardano)'s job.
|
||||
pycardano is mature, actively maintained (0.19.x as of 2026), and
|
||||
covers every primitive cleanly. This library slots next to it — no
|
||||
wrapping, no leaky abstraction, no second API to learn.
|
||||
|
||||
## Why this exists
|
||||
|
||||
Nothing else in the Python ecosystem (or any ecosystem — we checked)
|
||||
packages zero-custody merchant Cardano payments as a reusable library.
|
||||
Closest adjacents are all one of:
|
||||
|
||||
- CIP-30 browser-wallet plugins (customer-signs, not server-watches)
|
||||
- cardano-cli vending machines that watch a single static address (no
|
||||
xpub, no per-invoice derivation)
|
||||
- SaaS APIs (NMKR) — not libraries
|
||||
- Dormant / pre-1.0 grabs from the 2021-2023 era
|
||||
|
||||
The merchant state machine — "derive an address, watch for payment,
|
||||
confirm within tolerance, reprice if the quote lapses, emit a confirmed
|
||||
callback" — is what we package. You keep full control of everything
|
||||
else by using pycardano directly.
|
||||
Does NOT ship Cardano primitives. Address derivation, chain context,
|
||||
transaction building, native-script minting, signing — use
|
||||
[pycardano](https://github.com/Python-Cardano/pycardano) directly.
|
||||
This library slots next to it.
|
||||
|
||||
## Quick start
|
||||
|
||||
|
|
@ -42,7 +23,7 @@ from cardano_checkout import (
|
|||
)
|
||||
|
||||
|
||||
# Your oracle — we don't ship one. Anything async returning int lovelace works.
|
||||
# Your oracle. Anything async returning int lovelace works.
|
||||
async def my_price_fn(usd: float) -> int:
|
||||
rate = await fetch_ada_usd_somewhere() # CoinGecko, Koios, fixed rate, etc.
|
||||
return int(round(usd / rate * 1_000_000))
|
||||
|
|
@ -51,20 +32,17 @@ async def my_price_fn(usd: float) -> int:
|
|||
async def main() -> None:
|
||||
store = InMemoryStore() # swap for your SQLAlchemy / asyncpg / sqlite adapter
|
||||
|
||||
# Create an invoice. In production you'd derive the receive address from
|
||||
# your wallet xpub via pycardano — see the "Deriving addresses" section.
|
||||
invoice = Invoice(
|
||||
id="ord-0042",
|
||||
merchant_id="example-studio",
|
||||
merchant_id="my-shop",
|
||||
derivation_index=42,
|
||||
receive_address="addr1q...", # derived via pycardano — your code
|
||||
receive_address="addr1q...", # derive via pycardano
|
||||
expected_lovelace=5_000_000,
|
||||
usd_amount=2.50,
|
||||
expires_at=datetime.now(timezone.utc) + timedelta(minutes=15),
|
||||
)
|
||||
await store.create(invoice)
|
||||
|
||||
# Run the background scheduler — Koios poll every 15s + reprice every 60s.
|
||||
scheduler = InvoiceScheduler(store=store, price_fn=my_price_fn)
|
||||
await scheduler.start()
|
||||
|
||||
|
|
@ -77,12 +55,10 @@ asyncio.run(main())
|
|||
|
||||
## Deriving addresses with pycardano
|
||||
|
||||
We used to wrap this. You don't need the wrapper.
|
||||
|
||||
```python
|
||||
from pycardano import HDWallet, Address, Network
|
||||
|
||||
# Your merchant's account-level xpub — the xpub is public, not a secret.
|
||||
# Account-level xpub — public, not a secret.
|
||||
xpub_hex = "..."
|
||||
|
||||
account = HDWallet.from_xpub(bytes.fromhex(xpub_hex))
|
||||
|
|
@ -100,16 +76,9 @@ def derive_address(account: HDWallet, index: int, network=Network.MAINNET) -> st
|
|||
addr = derive_address(account, index=42)
|
||||
```
|
||||
|
||||
Six lines of pycardano that read cleanly against
|
||||
[their docs](https://pycardano.readthedocs.io/). Our old wrapper would
|
||||
have added one function call but made you learn our API instead of
|
||||
pycardano's. Skip it.
|
||||
## NFT cert: CIP-25 v2 metadata
|
||||
|
||||
## NFT cert-of-authenticity: CIP-25 v2 metadata
|
||||
|
||||
If you want each paid order to ship with an on-chain NFT cert, here's
|
||||
the CIP-25 v2 metadata builder as a copy-paste. It fits in your own
|
||||
code — no dep, no wrapper.
|
||||
Copy-paste builder for an on-chain cert per paid order. No dep.
|
||||
|
||||
```python
|
||||
def build_cip25_metadata(
|
||||
|
|
@ -150,20 +119,18 @@ def build_cip25_metadata(
|
|||
}
|
||||
```
|
||||
|
||||
Hand that dict to pycardano's `AuxiliaryData(Metadata({...}))` when you
|
||||
build the mint tx. Straight pycardano from there on.
|
||||
Hand the dict to pycardano's `AuxiliaryData(Metadata({...}))` when
|
||||
building the mint tx.
|
||||
|
||||
## Implementing your own InvoiceStore
|
||||
|
||||
The SDK's `InvoiceStore` is a Protocol — implement the six methods
|
||||
against whatever backend you want (SQLAlchemy, asyncpg, SQLite,
|
||||
in-memory for tests).
|
||||
`InvoiceStore` is a Protocol — implement six methods against whatever
|
||||
backend you want (SQLAlchemy, asyncpg, SQLite, in-memory).
|
||||
|
||||
```python
|
||||
from cardano_checkout import Invoice, InvoiceStatus, InvoiceStore
|
||||
|
||||
class MySqliteStore:
|
||||
# Implement these six methods and you're a valid InvoiceStore.
|
||||
async def create(self, invoice: Invoice) -> None: ...
|
||||
async def get(self, invoice_id: str) -> Invoice | None: ...
|
||||
async def list_by_status(self, status: InvoiceStatus, limit: int = 100) -> list[Invoice]: ...
|
||||
|
|
@ -172,38 +139,29 @@ class MySqliteStore:
|
|||
async def record_tx(self, invoice_id: str, tx_hash: str, lovelace_delta: int) -> None: ...
|
||||
```
|
||||
|
||||
See `InMemoryStore` in `cardano_checkout/store.py` for a 90-line
|
||||
reference implementation.
|
||||
See `InMemoryStore` in `cardano_checkout/store.py` for a reference impl.
|
||||
|
||||
## Status (1.0.0-dev)
|
||||
## Modules
|
||||
|
||||
| Module | Purpose |
|
||||
|---|---|
|
||||
| `invoice.py` | `Invoice` dataclass + `InvoiceStatus` enum — payment lifecycle states |
|
||||
| `invoice.py` | `Invoice` dataclass + `InvoiceStatus` enum |
|
||||
| `store.py` | `InvoiceStore` Protocol + `InMemoryStore` reference impl |
|
||||
| `monitor.py` | `check_address_utxos` (Koios), `evaluate_utxos` (ADA matching + tolerance), `check_pending_invoices`, `reprice_expired_invoices` (takes your `price_fn`) |
|
||||
| `scheduler.py` | `InvoiceScheduler` — APScheduler wrapper, runs check/reprice on the same 15s/60s cadence the host app's used in production since 2025 |
|
||||
| `monitor.py` | `check_address_utxos` (Koios), `evaluate_utxos`, `check_pending_invoices`, `reprice_expired_invoices` |
|
||||
| `scheduler.py` | `InvoiceScheduler` — APScheduler wrapper, 15s check + 60s reprice |
|
||||
|
||||
All tests offline, 26/26 green. Two direct deps: `httpx` (Koios calls),
|
||||
`apscheduler` (background scheduling). No pycardano dep — that's the
|
||||
consumer's pairing.
|
||||
Two direct deps: `httpx`, `apscheduler`. No pycardano dep.
|
||||
|
||||
## Design principles
|
||||
## Design
|
||||
|
||||
1. **Protocol-first.** Persistence, pricing, and any other side-effect
|
||||
concern goes through a consumer-supplied interface. The SDK has no
|
||||
opinion about your database, your oracle, or your ORM.
|
||||
2. **Use pycardano directly.** We don't wrap primitives. If you need
|
||||
address derivation, chain context, or transaction building, import
|
||||
pycardano. Our package sits next to it, not on top.
|
||||
3. **Zero-custody.** The merchant's keys never touch this code. We
|
||||
handle xpub-derived addresses (public), UTxO observation (chain),
|
||||
and state transitions (the store). Funds flow directly between
|
||||
customer and merchant wallets. We are not a custodian.
|
||||
4. **Offline-first tests.** Koios HTTP and price oracles are stubbed
|
||||
or swapped via fixture. No network in CI. Live tests (preprod mint
|
||||
round-trips, real Koios) are a consumer-side concern.
|
||||
1. **Protocol-first.** Persistence, pricing, side-effects through
|
||||
consumer-supplied interfaces.
|
||||
2. **Use pycardano directly.** No wrapping of primitives.
|
||||
3. **Zero-custody.** Merchant keys never touch this code. xpub-derived
|
||||
addresses, UTxO observation, state transitions. Funds flow directly
|
||||
between customer and merchant wallets.
|
||||
4. **Offline-first tests.** Koios + price oracles stubbed via fixture.
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0 — matches the broader Cardano tooling ecosystem.
|
||||
Apache-2.0.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue