Python SDK for merchant-side Cardano payments + NFT cert minting (zero-custody)
Find a file
Kayos 68cb535c0f v0.2: README rewrite + docs/minting-workflow.md cold-signer runbook
README status table moves everything green except the TradeCraft compat
shim (still yellow, documented sunset path). Adds a migration guide
section mapping every old services/cardano_*.py import to its new
cardano_checkout.* equivalent so TradeCraft can adopt in one atomic
diff once the SQLAlchemyInvoiceStore adapter lands.

docs/minting-workflow.md: step-by-step runbook for the cold-signer
pattern — hot host builds UnsignedMint, operator ships three CBOR hex
files to Lucy, offline signer produces a signed tx, hot host submits
via submit_signed_tx. Covers the tx-id sanity check, skey hygiene
rules, time-locked-policy TTL clamp, and the preprod dry-run
requirement for every new policy.
2026-04-23 20:00:49 -07:00
cardano_checkout v0.2: wire mint + txbuild end-to-end against local Ogmios 2026-04-23 19:55:45 -07:00
docs v0.2: README rewrite + docs/minting-workflow.md cold-signer runbook 2026-04-23 20:00:49 -07:00
tests v0.2: add store, mint, and monitor integration tests 2026-04-23 19:58:46 -07:00
.gitignore v0.1.0-dev: initial extraction from TradeCraft + new abstractions 2026-04-23 18:04:00 -07:00
LICENSE v0.1.0-dev: initial extraction from TradeCraft + new abstractions 2026-04-23 18:04:00 -07:00
pyproject.toml v0.2: refactor monitor + scheduler around InvoiceStore Protocol 2026-04-23 19:55:28 -07:00
README.md v0.2: README rewrite + docs/minting-workflow.md cold-signer runbook 2026-04-23 20:00:49 -07:00

cardano-checkout

Python SDK for merchant-side Cardano payments + NFT certificate-of-authenticity minting.

Zero-custody by design: the merchant provides a wallet xpub. The SDK derives unique receive addresses per invoice, polls the chain for payment, and optionally mints a CIP-25 NFT cert on confirmation. The platform never holds or moves funds.

Extracted from TradeCraft's services/cardano_*.py modules (2,400+ lines of production code running on the Cardano mainnet) and packaged for reuse across the Sulkta Coop product family.

Status

v0.2.0-dev — Protocol-first core + live mint path. Monitor and scheduler have been refactored off SQLAlchemy and onto the InvoiceStore Protocol. Mint builds real transaction bodies against a local Ogmios endpoint and returns an UnsignedMint for cold-signing.

Module Status Notes
addresses stable CIP-1852 HD derivation via pycardano HDWallet soft derive
oracles stable ADA/USD price via CoinGecko + DexHunter, 5-min cache
invoice + store stable Framework-agnostic invoice + InMemoryStore reference impl
mint v0.2 CIP-25 v2 metadata + real tx body → UnsignedMint bundle
ipfs stable kubo HTTP API client w/ optional mirror-pin
monitor v0.2 Operates purely through InvoiceStore — no ORM coupling
scheduler v0.2 InvoiceScheduler drives check + reprice against the store
tradecraft_compat 🟡 compat shim Keeps TradeCraft's subscription + grace-period jobs alive during migration
txbuild v0.2 OgmiosChainContext wiring + submit_signed_tx + address UTxO queries

Migration status for TradeCraft: still imports the old module paths. See the v0.2 migration guide below.

Design

     ┌────────────────────────────────────────────────────────┐
     │                    Merchant App                        │
     │   (TradeCraft / chromaticcraft / your-product)         │
     └──────────────┬───────────────────────┬─────────────────┘
                    │                       │
         uses      │ implements             │ imports
                    ▼                       ▼
        ┌──────────────┐         ┌────────────────────────┐
        │ InvoiceStore │ ◄────── │  cardano_checkout SDK  │
        │  (your DB)   │         │                        │
        └──────────────┘         │  addresses    ← pure   │
                                 │  oracles      ← pure   │
                                 │  invoice      ← dataclass │
                                 │  store        ← Protocol + InMemoryStore │
                                 │  monitor      ← polls chain via store │
                                 │  scheduler    ← bg loop  │
                                 │  mint         ← NFT cert (cold-signer) │
                                 │  ipfs         ← upload   │
                                 │  txbuild      ← Ogmios wrappers │
                                 └────────────────────────┘
                                           │
                                  talks to │
                                           ▼
                                 ┌────────────────────────┐
                                 │ Koios + Ogmios + kubo  │
                                 └────────────────────────┘

The merchant app provides:

  1. A wallet xpub (account-level extended public key).
  2. An InvoiceStore implementation (SQLAlchemy, Postgres, SQLite, in-memory — whatever).

The SDK provides:

  1. Address derivation from the xpub.
  2. Per-invoice payment monitoring against Koios.
  3. ADA ↔ USD price conversion.
  4. CIP-25 v2 NFT cert minting with a cold-signer hand-off.
  5. IPFS upload + pinning for NFT image metadata.

Quick start

import asyncio
from cardano_checkout import addresses, oracles

# Derive a receive address for invoice #42
addr = addresses.derive_address(
    xpub_hex="<your wallet xpub>",
    index=42,
    network="mainnet",
)

# Convert a USD price to lovelace at current market
async def main() -> None:
    lovelace = await oracles.convert_usd_to_lovelace(99.00)
    ada = lovelace / 1_000_000
    print(f"Customer owes {ada:.4f} ADA for $99")

asyncio.run(main())

Payment monitoring

import asyncio
from cardano_checkout import InMemoryStore, Invoice, InvoiceStatus, InvoiceScheduler

store = InMemoryStore()  # swap for your real SQLAlchemy / asyncpg / SQLite adapter

# Create an invoice (typically you'd derive the address here via addresses.derive_address)
invoice = Invoice(
    id="ord-0042",
    merchant_id="chromaticcraft",
    derivation_index=42,
    receive_address="addr1q...",
    expected_lovelace=5_000_000,
    usd_amount=2.50,
)
asyncio.run(store.create(invoice))

# Wire the background scheduler — same 15s check / 60s reprice cadence as TradeCraft.
scheduler = InvoiceScheduler(store=store)
asyncio.run(scheduler.start())
# ... your app runs ...
asyncio.run(scheduler.stop())

IPFS: bake-then-mirror pattern

The SDK's IPFSClient expects a local kubo daemon (typically in the same Docker image as the web app) for upload and primary pin, and takes an optional list of mirror endpoints to pin add the CID on a second node for archival redundancy.

Typical chromaticcraft deployment:

from cardano_checkout import ipfs

client = ipfs.IPFSClient(
    api_url="http://127.0.0.1:5001",           # local kubo in the same container
    mirror_api_urls=["http://192.168.254.5:5001"],  # Lucy's kubo over the LAN/VPN
)

cid = await client.add(photo_bytes, filename="order-0001.jpg")
# Image now served by Rackham (low latency) AND pinned on Lucy (durability)

NFT cert-of-authenticity design

One minting policy per merchant studio. Policy is a native script (no Plutus required), optionally time-locked to make "no more editions after X" a cryptographically verifiable claim.

CIP-25 v2 metadata. Single NFT per order. Policy skey never leaves the custody host (Lucy in Sulkta's pattern — 2-of-2 native script: Cobb + Kayos). The SDK builds the metadata envelope + tx body on the hot node and returns an UnsignedMint bundle; an external offline signer provides the vkey witnesses; the hot node submits the assembled CBOR.

The full operator runbook — including the exact byte-movement sequence, verification checklist, and preprod dry-run procedure — lives in docs/minting-workflow.md.

v0.2 migration guide for TradeCraft

The generic invoice jobs moved to a Protocol-based API. The subscription + grace-period jobs stayed TradeCraft-specific and live in cardano_checkout.tradecraft_compat.

Import changes when TradeCraft adopts the SDK:

Was Becomes
from services.cardano_monitor import check_pending_payments, reprice_expired_payments from cardano_checkout.monitor import check_pending_invoices, reprice_expired_invoices
from services.cardano_monitor import _check_address_utxos, _evaluate_payment from cardano_checkout.monitor import check_address_utxos, evaluate_utxos (or import from tradecraft_compat for the exact old names)
from services.cardano_scheduler import start_cardano_scheduler, stop_cardano_scheduler from cardano_checkout.scheduler import InvoiceScheduler (instantiate with your store)
from services.cardano_scheduler import _check_subscription_payments, _reprice_subscription_payments, _enforce_grace_period from cardano_checkout.tradecraft_compat import check_subscription_payments, reprice_subscription_payments, enforce_grace_period (verbatim jobs — TradeCraft still drives them directly)
from services.cardano_price import * from cardano_checkout.oracles import *
from services.cardano_addresses import derive_address from cardano_checkout.addresses import derive_address

What TradeCraft still needs to write:

A SQLAlchemy adapter implementing InvoiceStore against the existing CardanoPayment table. list_by_status maps to a SELECT ... WHERE status = :s, next_derivation_index to a SELECT MAX(derivation_index) + 1, etc. That's 80-ish lines of wrapper code — nothing exotic. Once that's landed, the generic jobs run through InvoiceScheduler(store=SQLAlchemyInvoiceStore(...)) and the subscription jobs keep running through tradecraft_compat unchanged.

TODO for future sprints:

  • Ship a cardano_checkout.adapters.sqlalchemy.SQLAlchemyInvoiceStore so TradeCraft doesn't have to write the adapter from scratch.
  • Once TradeCraft's subscription jobs are migrated to a subscription- specific Protocol, delete tradecraft_compat.
  • Refund-path build_payment_tx in txbuild.py (v0.3).
  • Batched mints (sell-sheet of 10 NFTs at once).

Testing

pip install -e '.[test]'
pytest                                        # 42 tests, all offline

The test suite mocks the chain context for mint-tx construction and monkey-patches Koios + the oracle for monitor tests — CI never touches a live node. The address-derivation tests use a deterministic test-vector xpub from the standard "test ... junk" mnemonic so they can't drift.

Installation

pip install 'cardano-checkout[sqlalchemy]'   # if you're using SQLAlchemy
pip install cardano-checkout                  # core only

License

Apache-2.0 — matches upstream Cardano tooling.