Sulkta Coop's Python SDK for merchant-side Cardano payments + NFT certificate-of-authenticity minting. Zero-custody by design. Extracted from TradeCraft's services/cardano_*.py (2,400+ lines of production Cardano-mainnet code) and restructured as an installable Python package. Package layout (cardano_checkout/): - addresses.py — lifted verbatim: CIP-1852 HD derivation, pure pycardano - oracles.py — lifted from cardano_price.py: Koios ADA/USD feed w/ 5m cache - monitor.py — lifted verbatim (SQLAlchemy-coupled; v0.2 refactors to Store) - scheduler.py — lifted verbatim (same refactor note) - invoice.py — NEW: framework-agnostic Invoice dataclass + lifecycle enum - store.py — NEW: InvoiceStore Protocol for pluggable persistence - mint.py — NEW: CIP-25 v2 metadata builder (works); tx submission stub for v0.2 - ipfs.py — NEW: kubo HTTP client with primary-pin + mirror-pin pattern - txbuild.py — NEW: v0.2 stub for PyCardano / Ogmios tx construction Design: - Consumers provide xpub + InvoiceStore impl. SDK provides everything else. - IPFS: local kubo for upload + serve, optional mirror pins for archival. Chromaticcraft pattern: Rackham kubo primary, Lucy kubo mirror. - NFT: single native-script policy per merchant studio (CIP-25 v2, not CIP-68 — full wallet coverage, no mutability needed for static certs). Policy skey stays under Sulkta cold-custody (Lucy pattern); signing is an external hand-off like ADAMaps payouts. Tests: pure-module smoke tests pass for invoice, store-protocol, CIP-25 metadata envelope, IPFS client import, txbuild stub module. Address derivation tests ship but require pycardano + will exercise in CI. LICENSE: Apache-2.0 (matches upstream Cardano tooling). Next (v0.2 scope): - Refactor monitor + scheduler around InvoiceStore (drop SQLAlchemy coupling) - Wire mint.mint_nft_cert to PyCardano + local Ogmios on Rackham - txbuild: Ogmios chain-context + cold-signer hand-off shape - chromaticcraft Phase 2 imports the SDK as its first external consumer
56 lines
1.8 KiB
Python
56 lines
1.8 KiB
Python
"""CIP-25 v2 metadata envelope construction — pure unit tests, no network."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from cardano_checkout.mint import build_cip25_metadata
|
|
|
|
|
|
def test_basic_envelope_shape() -> None:
|
|
md = build_cip25_metadata(
|
|
policy_id="abc123",
|
|
asset_name="ChromaticCraft-Order-0001",
|
|
name="Chromatic Craft — Custom Order #0001",
|
|
image_cid="bafybeibgen",
|
|
description="Hand-stitched moth pendant",
|
|
properties={"order_id": "0001", "edition": "1 of 1"},
|
|
)
|
|
|
|
assert md["721"]["version"] == "2.0"
|
|
assert "abc123" in md["721"]
|
|
|
|
nft = md["721"]["abc123"]["ChromaticCraft-Order-0001"]
|
|
assert nft["name"] == "Chromatic Craft — Custom Order #0001"
|
|
assert nft["image"] == "ipfs://bafybeibgen"
|
|
assert nft["mediaType"] == "image/jpeg"
|
|
assert nft["description"] == "Hand-stitched moth pendant"
|
|
assert nft["order_id"] == "0001"
|
|
assert nft["edition"] == "1 of 1"
|
|
|
|
|
|
def test_description_under_64_chars_stays_a_string() -> None:
|
|
md = build_cip25_metadata(
|
|
policy_id="abc", asset_name="x", name="n",
|
|
image_cid="c", description="short",
|
|
)
|
|
assert md["721"]["abc"]["x"]["description"] == "short"
|
|
|
|
|
|
def test_description_over_64_chars_chunks_to_list() -> None:
|
|
long = "x" * 150
|
|
md = build_cip25_metadata(
|
|
policy_id="abc", asset_name="x", name="n",
|
|
image_cid="c", description=long,
|
|
)
|
|
desc = md["721"]["abc"]["x"]["description"]
|
|
assert isinstance(desc, list)
|
|
assert all(len(chunk) <= 64 for chunk in desc)
|
|
assert "".join(desc) == long
|
|
|
|
|
|
def test_image_uri_has_ipfs_prefix() -> None:
|
|
md = build_cip25_metadata(
|
|
policy_id="abc", asset_name="x", name="n",
|
|
image_cid="bafybeitestcid",
|
|
)
|
|
assert md["721"]["abc"]["x"]["image"].startswith("ipfs://")
|
|
assert "bafybeitestcid" in md["721"]["abc"]["x"]["image"]
|