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

proposal-milestone Catalyst Proposal Milestone

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants