docs: rewrite README for public release
This commit is contained in:
parent
d0e74dd860
commit
df7caf8404
1 changed files with 75 additions and 23 deletions
98
README.md
98
README.md
|
|
@ -1,16 +1,44 @@
|
||||||
# cardano-checkout
|
# cardano-checkout
|
||||||
|
|
||||||
Merchant-side Cardano payment lifecycle in Python. Zero-custody.
|
Merchant-side Cardano payment lifecycle for Python. Zero-custody.
|
||||||
|
|
||||||
Ships the invoice state machine + UTxO watcher + reprice loop.
|
`cardano-checkout` ships the invoice state machine, an on-chain UTxO
|
||||||
Per-invoice HD-derived receive addresses, Koios polling, confirm /
|
watcher, and a quote-reprice loop for accepting ADA payments at
|
||||||
underpay / overpay classification, time-windowed repricing against
|
per-invoice, HD-derived receive addresses. It polls [Koios](https://koios.rest)
|
||||||
your own oracle.
|
for payment, classifies each invoice as confirmed / underpaid / overpaid
|
||||||
|
within a tolerance, and reprices expired quotes against a price oracle you
|
||||||
|
supply.
|
||||||
|
|
||||||
Does NOT ship Cardano primitives. Address derivation, chain context,
|
It does **not** reimplement Cardano primitives. Address derivation, chain
|
||||||
transaction building, native-script minting, signing — use
|
context, transaction building, native-script minting, and signing are all
|
||||||
[pycardano](https://github.com/Python-Cardano/pycardano) directly.
|
[pycardano](https://github.com/Python-Cardano/pycardano)'s job — this
|
||||||
This library slots next to it.
|
library slots in next to it and owns only the merchant payment lifecycle.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Accepting on-chain payments for a shop is mostly bookkeeping, not
|
||||||
|
cryptography:
|
||||||
|
|
||||||
|
- one fresh receive address per order (derived from your wallet xpub),
|
||||||
|
- watch the chain until the expected amount lands,
|
||||||
|
- decide confirmed / underpaid / overpaid within a tolerance,
|
||||||
|
- if the quote window lapses before payment, reprice and try again.
|
||||||
|
|
||||||
|
`cardano-checkout` is that bookkeeping, behind small consumer-supplied
|
||||||
|
interfaces — a persistence `Protocol` and a pricing callable — so it drops
|
||||||
|
into any stack: SQLAlchemy, asyncpg, SQLite, or plain in-memory.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```
|
||||||
|
pip install cardano-checkout # core
|
||||||
|
pip install 'cardano-checkout[sqlalchemy]' # + SQLAlchemy extra
|
||||||
|
```
|
||||||
|
|
||||||
|
Two runtime dependencies: `httpx` (Koios HTTP) and `apscheduler`
|
||||||
|
(background loop). There is **no** `pycardano` dependency — see
|
||||||
|
[Deriving addresses with pycardano](#deriving-addresses-with-pycardano)
|
||||||
|
for the pairing pattern.
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
|
|
@ -25,7 +53,7 @@ from cardano_checkout import (
|
||||||
|
|
||||||
# Your oracle. Anything async returning int lovelace works.
|
# Your oracle. Anything async returning int lovelace works.
|
||||||
async def my_price_fn(usd: float) -> int:
|
async def my_price_fn(usd: float) -> int:
|
||||||
rate = await fetch_ada_usd_somewhere() # CoinGecko, Koios, fixed rate, etc.
|
rate = await fetch_ada_usd_somewhere() # CoinGecko, a DEX, a fixed rate, etc.
|
||||||
return int(round(usd / rate * 1_000_000))
|
return int(round(usd / rate * 1_000_000))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -53,8 +81,15 @@ async def main() -> None:
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you price your invoices in fixed ADA you can omit `price_fn` — the
|
||||||
|
reprice job becomes a no-op and invoices simply expire at `expires_at`.
|
||||||
|
|
||||||
## Deriving addresses with pycardano
|
## Deriving addresses with pycardano
|
||||||
|
|
||||||
|
The receive address for each invoice is derived from your **account-level
|
||||||
|
xpub** (a public key — not a secret). Customer funds flow directly to your
|
||||||
|
wallet; this library never touches keys.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from pycardano import HDWallet, Address, Network
|
from pycardano import HDWallet, Address, Network
|
||||||
|
|
||||||
|
|
@ -78,7 +113,10 @@ addr = derive_address(account, index=42)
|
||||||
|
|
||||||
## NFT cert: CIP-25 v2 metadata
|
## NFT cert: CIP-25 v2 metadata
|
||||||
|
|
||||||
Copy-paste builder for an on-chain cert per paid order. No dep.
|
Need a certificate-of-authenticity NFT per paid order? Here is a
|
||||||
|
dependency-free builder for the CIP-25 v2 metadata envelope. Hand the
|
||||||
|
result to pycardano's `AuxiliaryData(Metadata({...}))` when you build the
|
||||||
|
mint transaction.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def build_cip25_metadata(
|
def build_cip25_metadata(
|
||||||
|
|
@ -119,12 +157,9 @@ def build_cip25_metadata(
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Hand the dict to pycardano's `AuxiliaryData(Metadata({...}))` when
|
|
||||||
building the mint tx.
|
|
||||||
|
|
||||||
## Implementing your own InvoiceStore
|
## Implementing your own InvoiceStore
|
||||||
|
|
||||||
`InvoiceStore` is a Protocol — implement six methods against whatever
|
`InvoiceStore` is a `Protocol` — implement six methods against whatever
|
||||||
backend you want (SQLAlchemy, asyncpg, SQLite, in-memory).
|
backend you want (SQLAlchemy, asyncpg, SQLite, in-memory).
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
@ -139,7 +174,8 @@ class MySqliteStore:
|
||||||
async def record_tx(self, invoice_id: str, tx_hash: str, lovelace_delta: int) -> None: ...
|
async def record_tx(self, invoice_id: str, tx_hash: str, lovelace_delta: int) -> None: ...
|
||||||
```
|
```
|
||||||
|
|
||||||
See `InMemoryStore` in `cardano_checkout/store.py` for a reference impl.
|
See `InMemoryStore` in `cardano_checkout/store.py` for a reference
|
||||||
|
implementation (it also backs the test suite).
|
||||||
|
|
||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
|
|
@ -150,18 +186,34 @@ See `InMemoryStore` in `cardano_checkout/store.py` for a reference impl.
|
||||||
| `monitor.py` | `check_address_utxos` (Koios), `evaluate_utxos`, `check_pending_invoices`, `reprice_expired_invoices` |
|
| `monitor.py` | `check_address_utxos` (Koios), `evaluate_utxos`, `check_pending_invoices`, `reprice_expired_invoices` |
|
||||||
| `scheduler.py` | `InvoiceScheduler` — APScheduler wrapper, 15s check + 60s reprice |
|
| `scheduler.py` | `InvoiceScheduler` — APScheduler wrapper, 15s check + 60s reprice |
|
||||||
|
|
||||||
Two direct deps: `httpx`, `apscheduler`. No pycardano dep.
|
|
||||||
|
|
||||||
## Design
|
## Design
|
||||||
|
|
||||||
1. **Protocol-first.** Persistence, pricing, side-effects through
|
1. **Protocol-first.** Persistence, pricing, and side-effects all go
|
||||||
consumer-supplied interfaces.
|
through consumer-supplied interfaces.
|
||||||
2. **Use pycardano directly.** No wrapping of primitives.
|
2. **Use pycardano directly.** No wrapping of Cardano primitives.
|
||||||
3. **Zero-custody.** Merchant keys never touch this code. xpub-derived
|
3. **Zero-custody.** Merchant keys never touch this code. xpub-derived
|
||||||
addresses, UTxO observation, state transitions. Funds flow directly
|
addresses, UTxO observation, state transitions. Funds flow directly
|
||||||
between customer and merchant wallets.
|
between customer and merchant wallets.
|
||||||
4. **Offline-first tests.** Koios + price oracles stubbed via fixture.
|
4. **Offline-first tests.** Koios and price oracles are stubbed via
|
||||||
|
fixtures — the suite never touches a live node.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Issues and pull requests are welcome. A couple of house rules keep the
|
||||||
|
library focused:
|
||||||
|
|
||||||
|
- **Keep Cardano primitives out.** Anything pycardano already does
|
||||||
|
belongs in the consumer, not here.
|
||||||
|
- **Tests stay offline.** Koios and any price oracle must be stubbed via
|
||||||
|
fixtures so CI never hits a live node or a real wallet.
|
||||||
|
|
||||||
|
Run the suite before opening a PR:
|
||||||
|
|
||||||
|
```
|
||||||
|
pip install -e '.[test]'
|
||||||
|
pytest
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Apache-2.0.
|
Apache-2.0. See [LICENSE](LICENSE).
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue