Skip to content

feat(cardano): spec-exact CIP-30 adapter, conformance suite, and CI#9

Open
jinglescode wants to merge 6 commits into
mainfrom
feature/cip30-conformance
Open

feat(cardano): spec-exact CIP-30 adapter, conformance suite, and CI#9
jinglescode wants to merge 6 commits into
mainfrom
feature/cip30-conformance

Conversation

@jinglescode

Copy link
Copy Markdown
Member

Summary

Closes the CIP-30 conformance gap for the Cardano headless wallet (Catalyst milestone: Develop and deploy a CIP-30 headless wallet on Cardano). The existing CardanoHeadlessWallet already covered derivation, addresses, signing, and provider attachment — this PR adds the last mile: a spec-exact CIP-30 adapter layer, the full error taxonomy, a simulated initial API, and a conformance test suite, with zero breaking changes to existing APIs.

New: src/cardano/cip30/

  • createCip30Wallet({ wallet, name, ... }) → simulated window.cardano.{name}-shaped initial API: apiVersion "1", isEnabled(), enable(extensions?) with requested∩supported negotiation and a configurable approval hook (Refused(-3))
  • createCip30Api(wallet) → spec-exact endpoints: getUtxos(amount?, paginate?) with amount-coverage selection (coin + every multiasset) and null semantics, paginated getUsedAddresses, getCollateral({amount}) with null semantics, and every failure mapped to the CIP-30 error taxonomy (APIError −1…−4, PaginateError{maxSize}, TxSignError, DataSignError, TxSendError)
  • Use case: dApp integration tests and server-side agents against a real signing wallet — no browser, no extension

Surgical wallet improvements (non-breaking)

  • signData accepts bech32 or hex addresses (CIP-30 Address type)
  • getUsedAddresses derives used addresses from fetcher UTxOs when attached (resolves a TODO), with single-address fallback

Bug fixes

  • CIP-8 (pre-existing): COSE key/header lookups used serialized-JSON equality that never matches parsed CBOR nodes (subCborRef metadata) — our own COSE keys failed to re-parse. Replaced with semantic comparisons. Found by the new conformance suite.
  • blakejs moved to dependencies (was transitive-only)
  • package-lock.json regenerated — it had drifted and broke npm ci

Tests & CI

  • New conformance suite (test/cardano/cip30/): one describe per spec endpoint incl. initial API; CBOR round-trips; real Ed25519 signature verification against tx body hashes; COSE_Sign1/COSE_Key structural + cryptographic checks; pagination boundaries; per-failure-mode error codes; extension negotiation; provider-swap proof (two independent IFetcher impls + ISubmitter swap)
  • New .github/workflows/test.yml: build + jest on every PR (repo previously had publish-only CI)
  • 305 tests / 24 suites, all green (was 198 before this PR)

Docs

  • README: CIP-30 conformance matrix (initial + full API) with honestly documented deviations (stateless single-address semantics, auto-approve enable(), spec-deprecated getCollateral, unreachable decline codes), plus a "Simulated CIP-30 API (headless)" usage section

Test plan

  • pnpm test — 305/305 across 24 suites
  • pnpm build — ESM/CJS/DTS clean, no export collisions
  • npm ci --dry-run — lockfile in sync (new CI workflow will run green)
  • Multi-round engineer/QA review: every phase independently verified (diff scope, spec code values, crypto assertions, docs accuracy)

🤖 Generated with Claude Code

jinglescode and others added 6 commits July 6, 2026 16:54
- Add .github/workflows/test.yml running build + jest on every PR and
  push to main (repo previously had publish-only CI)
- Move blakejs to dependencies (used by src/cardano/signer/cip-8.ts,
  was only available transitively)
- Regenerate package-lock.json: it had drifted from package.json
  (jest-environment-jsdom tree missing), breaking npm ci
- Add a CBOR round-trip spike test pinning @cardano-sdk serialization
  against known-good CIP-30 vectors

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
getPublicKeyFromCoseKey, getPublicKey, getAddress and hashPayload
compared CBOR nodes via serialized-JSON equality, which never matches
nodes produced by Cbor.parse (they carry subCborRef/addInfos metadata
that constructed nodes lack). Parsing a COSE key produced by our own
getCoseKeyFromPublicKey threw 'Public key not found'.

Replace all six comparison sites with semantic helpers (cborKeyIsText,
cborKeyIsInt, cborIsSimpleTrue) that compare decoded values via
instanceof + value equality. Caught by the new CIP-30 conformance
suite; behavior otherwise unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New src/cardano/cip30/ module wrapping any ICardanoWallet as a
spec-exact CIP-30 surface, without changing existing wallet APIs:

- errors.ts: full CIP-30 error taxonomy — APIError(-1..-4),
  PaginateError{maxSize}, TxSignError(1,2), DataSignError(1,2,3),
  TxSendError(1,2) — with {code, info} wire shapes and type guards
- types.ts: ICip30Api, ICip30InitialApi, Paginate, Cip30Extension
- cip30-api.ts:
  - createCip30Api(wallet): getUtxos(amount?, paginate?) with
    amount-coverage selection (coin + every multiasset) and null
    semantics, paginated getUsedAddresses, getCollateral({amount})
    over pure-ADA utxos with null semantics, and every wallet error
    mapped to the spec taxonomy
  - createCip30Wallet({wallet, name, ...}): simulated initial API —
    apiVersion "1", isEnabled(), enable(extensions?) with
    requested-intersect-supported negotiation and a configurable
    approval hook for the Refused(-3) path

Surgical wallet improvements behind the adapter:
- signData now accepts bech32 or hex addresses (CIP-30 Address type)
- getUsedAddresses derives used addresses from fetcher utxos when a
  fetcher is attached, falling back to the single derived address

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- One describe per CIP-30 spec endpoint (initial API included):
  CBOR round-trips, pagination boundaries + PaginateError{maxSize},
  per-failure-mode error codes, null semantics, extension negotiation
- Real cryptography, not shape checks: Ed25519 witness signatures
  verified against the tx body hash; COSE_Sign1 verified via
  CoseSign1.verifySignature; COSE_Key structure (kty/alg/crv/x)
  asserted per CIP-8; hex and bech32 signData parity
- Provider attachment (A3): identical results from OfflineFetcher and
  a hand-rolled array-backed IFetcher, plus ISubmitter swap
- Shared fixtures extracted to test/cardano/cip30/fixtures.ts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Conformance matrix (initial + full API) with documented deviations:
  stateless single-address semantics, auto-approve enable(), spec-
  deprecated getCollateral, unreachable decline/refusal codes
- New 'Simulated CIP-30 API (headless)' section with usage examples
  and provider-attachment (A3) notes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
README documented an 'InMemoryBip32' export that doesn't exist — the
class was renamed CardanoInMemoryBip32 when the Bitcoin SDK landed its
BitcoinInMemoryBip32 counterpart. Import example would not compile.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant