cardano-api: strip 'Fix #N:' audit-ticket prefixes from inline comments (was 50+ in main.py), drop hardening-pass changelog blocks from module docstring, rewrite README to drop deploy paths + marketing sections, keep tier/auth/TTL + policy IDs. cardano-checkout-py: drop TradeCraft lineage refs, swap chromaticcraft/tradecraft test fixtures for acme/globex, repository URL → git.sulkta.com. |
||
|---|---|---|
| cardano_checkout | ||
| tests | ||
| .gitignore | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
cardano-checkout
Merchant-side Cardano payment lifecycle in Python. Zero-custody.
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.
Does NOT ship Cardano primitives. Address derivation, chain context, transaction building, native-script minting, signing — use pycardano directly. This library slots next to it.
Quick start
import asyncio
from datetime import datetime, timedelta, timezone
from cardano_checkout import (
Invoice, InvoiceStatus, InMemoryStore, InvoiceScheduler,
)
# 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))
async def main() -> None:
store = InMemoryStore() # swap for your SQLAlchemy / asyncpg / sqlite adapter
invoice = Invoice(
id="ord-0042",
merchant_id="my-shop",
derivation_index=42,
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)
scheduler = InvoiceScheduler(store=store, price_fn=my_price_fn)
await scheduler.start()
# ... app runs ...
await scheduler.stop()
asyncio.run(main())
Deriving addresses with pycardano
from pycardano import HDWallet, Address, Network
# Account-level xpub — public, not a secret.
xpub_hex = "..."
account = HDWallet.from_xpub(bytes.fromhex(xpub_hex))
def derive_address(account: HDWallet, index: int, network=Network.MAINNET) -> str:
payment = account.derive(0).derive(index) # external chain, address index
staking = account.derive(2).derive(0) # staking chain, always index 0
addr = Address(
payment_part=payment.public_key.hash(),
staking_part=staking.public_key.hash(),
network=network,
)
return str(addr)
addr = derive_address(account, index=42)
NFT cert: CIP-25 v2 metadata
Copy-paste builder for an on-chain cert per paid order. No dep.
def build_cip25_metadata(
*,
policy_id: str,
asset_name: str,
name: str,
image_cid: str,
description: str = "",
media_type: str = "image/jpeg",
properties: dict | None = None,
) -> dict:
"""Build a CIP-25 v2 metadata envelope.
Returns a dict ready to submit as transaction metadatum label 721.
Handles the 64-char chunking rule for long descriptions.
"""
def chunk64(s: str) -> list[str]:
if len(s) <= 64:
return [s]
return [s[i:i + 64] for i in range(0, len(s), 64)]
body: dict = {
"name": name,
"image": f"ipfs://{image_cid}",
"mediaType": media_type,
}
if description:
body["description"] = description if len(description) <= 64 else chunk64(description)
if properties:
body.update(properties)
return {
"721": {
policy_id: {asset_name: body},
"version": "2.0",
}
}
Hand the dict to pycardano's AuxiliaryData(Metadata({...})) when
building the mint tx.
Implementing your own InvoiceStore
InvoiceStore is a Protocol — implement six methods against whatever
backend you want (SQLAlchemy, asyncpg, SQLite, in-memory).
from cardano_checkout import Invoice, InvoiceStatus, InvoiceStore
class MySqliteStore:
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]: ...
async def update(self, invoice: Invoice) -> None: ...
async def next_derivation_index(self, merchant_id: str) -> int: ...
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.
Modules
| Module | Purpose |
|---|---|
invoice.py |
Invoice dataclass + InvoiceStatus enum |
store.py |
InvoiceStore Protocol + InMemoryStore reference impl |
monitor.py |
check_address_utxos (Koios), evaluate_utxos, check_pending_invoices, reprice_expired_invoices |
scheduler.py |
InvoiceScheduler — APScheduler wrapper, 15s check + 60s reprice |
Two direct deps: httpx, apscheduler. No pycardano dep.
Design
- Protocol-first. Persistence, pricing, side-effects through consumer-supplied interfaces.
- Use pycardano directly. No wrapping of primitives.
- Zero-custody. Merchant keys never touch this code. xpub-derived addresses, UTxO observation, state transitions. Funds flow directly between customer and merchant wallets.
- Offline-first tests. Koios + price oracles stubbed via fixture.
License
Apache-2.0.