Skip to content

Route Classic payments through the runtime writer port - #120

Draft
knzeng-e wants to merge 1 commit into
devfrom
agent/product-runtime-payments
Draft

Route Classic payments through the runtime writer port#120
knzeng-e wants to merge 1 commit into
devfrom
agent/product-runtime-payments

Conversation

@knzeng-e

@knzeng-e knzeng-e commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Outcome

Classic unlock payments now go through Dotify's RuntimeWritePort instead of constructing a viem writer directly inside the catalog hook. This gives the Product/CDM path one controlled write seam while keeping the tracked deployment on the proven viem/passkey/EVM route.

Refs #85.

Issue and context

Local scope: docs/backlog/polkadot-product-readiness-and-killer-dapp-roadmap.md.

The current Product payment planning needs Dotify to execute track payments, rights-sensitive runtime writes, and future DOT/CASH settlement through explicit adapters. Before this PR, Classic unlock was the exception: useCatalog directly imported createViemRuntimeWriter, fetched an EVM wallet client, and submitted musicRoyPayAccess itself.

That made the Product CDM writer technically present but unreachable from the main listener payment flow. It also kept payment logic tied to one signer implementation, which would make the future CASH settlement work harder to isolate.

Architecture and key concepts

The runtime boundary is now symmetric:

Classic unlock CTA
  -> useCatalog.payForTrackAccess
  -> RuntimeWritePort.payForAccess
  -> viem writer by default
     OR Product CDM writer when VITE_DOTIFY_RUNTIME_ADAPTER=product-cdm

RuntimeWritePort remains the authority boundary for contract writes. The viem adapter still writes through the active passkey/EVM wallet. The Product CDM adapter is lazy and fail-closed: it is only bundled when the build opts in, and it does not contact the Product host during React render.

How it works

runtimeWriterProvider.ts selects the write adapter from the same runtime adapter config used by reads. In viem mode, it resolves the current EVM wallet client per write so account/network changes are respected. In Product CDM mode, it initializes the Product contract resolver on first write, verifies the deployment, and routes the transaction through Product contract handles.

useCatalog memoizes both runtime reader and writer, then payForTrackAccess submits payment through runtimeWriter.payForAccess. Wallet gating now names the real signer requirement:

  • no connected wallet opens the wallet modal;
  • viem mode requires a passkey/EVM-capable wallet;
  • Product CDM mode requires the Product host account;
  • unsupported combinations fail before transaction submission.

Design decisions and tradeoffs

The PR deliberately does not add a new env var. VITE_DOTIFY_RUNTIME_ADAPTER already selects the runtime contract graph; using it for writes keeps reads and writes on one explicit deployment decision.

The PR does not implement CASH payment, DOT/CASH conversion, or Coinage settlement. Current Classic unlock still pays native runtime value into musicRoyPayAccess. CASH settlement remains a separate design because CASH lives on the Product money rail while the artist runtime lives on Asset Hub.

The PR also does not invent a Product signer manager. If Product CDM transaction submission lacks host evidence or signer support, it fails explicitly rather than falling back to viem or a hidden signer.

Security, failure, and operations

The production default remains viem. Product CDM writes require an explicit VITE_DOTIFY_RUNTIME_ADAPTER=product-cdm build and a Product host account. There is no demo signer, no silent fallback, and no wallet bypass.

Operational docs were updated because the meaning of VITE_DOTIFY_RUNTIME_ADAPTER changed from read-only selection to read/write runtime adapter selection. The tracked Product profile still leaves the flag unset, so this PR does not change the deployed Product payment behavior until an operator intentionally enables CDM mode.

Review guide

Suggested order

  1. web/src/features/runtime/runtimeWriterProvider.ts - verify lazy Product setup, viem-per-write wallet resolution, and fail-closed adapter selection.
  2. web/src/hooks/useCatalog.ts - verify Classic unlock now calls runtimeWriter.payForAccess and no longer constructs viem directly.
  3. web/src/features/runtime/runtimeWriterProvider.test.ts - verify the default viem path, Product non-bundled error, and Product no-fallback routing.
  4. web/e2e/classic-unlock.spec.ts - verify the e2e assertion matches the current UI wording.
  5. Product/runtime docs - verify they correctly distinguish native-value CDM writes from future CASH settlement.

Verify carefully

  • Product CDM mode never falls back to viem after Product setup failure.
  • A connected Product account cannot be used for viem writes by accident.
  • A connected EVM/passkey wallet cannot accidentally submit through Product CDM mode.
  • Classic unlock still refreshes paid access and full playback after transaction confirmation.
  • The docs do not imply CASH settlement or DOT/CASH conversion has shipped.

Validation

Evidence What it proves
npm run test:unit -- runtimeWriterProvider runtimeReaderProvider productCdmRuntimeAdapter viemRuntimeAdapter Runtime read/write adapters still route correctly; 20 targeted tests passed.
npm run build TypeScript and the production Vite bundle compile. Existing Vite chunk-size/dynamic-import warnings remain.
npm run lint ESLint passes with 0 errors. Existing React Hook dependency warnings remain in App.tsx and ArtistShell.tsx.
npm run test:e2e -- classic-unlock.spec.ts The Classic unlock user flow still gates, pays, and opens full playback.
npm run test:unit Full unit suite passes: 39 files, 311 tests.
Targeted npx prettier --check ... Touched TypeScript/e2e files and the Product/config docs changed in this PR are formatted.

Note: the global npm run fmt:check still fails on pre-existing files outside this PR (productHost, useSession, turn, signaling tests, README). I did not format those to avoid unrelated churn.

Known limitations and follow-ups

This is not Product-native CASH settlement. Next work should capture real Product host transaction evidence, then design the CASH receipt/indexer/precompile path before changing value flow.

The markdown docs picked up table alignment from Prettier in touched files. The semantic updates are the Product runtime-write boundary and CASH limitation notes.

Metadata checklist

  • Backlog issue linked with reference semantics: Refs [Product SDK][P1] Prove Product host compatibility before integration #85
  • Local backlog document linked: docs/backlog/polkadot-product-readiness-and-killer-dapp-roadmap.md
  • Added to Project 5 (Dotify sprints)
  • Project Priority, Track, Phase, Type, and Backlog doc mirror the issue
  • Workflow status matches draft/review state
  • Assignee set
  • Applicable labels set
  • Applicable milestone set, or confirmed none exists
  • Reviewers requested when ownership is known
  • Draft/ready state is intentional

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