Route Classic payments through the runtime writer port - #120
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7096274bca
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| - Delivered on the next follow-up branch: route Classic unlock payments through | ||
| `RuntimeWritePort` instead of constructing a viem writer inside the catalog | ||
| hook. The tracked Product deployment still defaults to viem, but a | ||
| `product-cdm` build now changes the runtime write adapter at the same seam as | ||
| reads. |
There was a problem hiding this comment.
Keep the public project page aligned with the roadmap
This change marks Product CDM payments as delivered and substantially revises the architecture and production roadmap, but docs/index.html is unchanged. That leaves the public-facing project page describing an older product state; update it in the same change so the published roadmap and architecture narrative remain consistent.
AGENTS.md reference: AGENTS.md:L50-L55
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Agreed. Fixed in 9a6ef15 by updating docs/index.html to reflect the opt-in Product CDM runtime read/write path while keeping the tracked deployment explicitly on viem until account mapping, native value forwarding, and host-signed transaction evidence are verified.
| } | ||
|
|
||
| const { createProductCdmContracts } = await import('./productCdmContracts'); | ||
| const { resolver, verifyDeployment } = await createProductCdmContracts({ environment: config.productEnvironment }); |
There was a problem hiding this comment.
Pass the connected Product signer into the CDM contracts
In a product-cdm build, payForTrackAccess now accepts a product-host wallet, but this setup passes only the environment to createProductCdmContracts. Consequently both ContractManager and dynamically bound runtime contracts receive an undefined signerManager, and the connected Product account's signer is never attached to musicRoyPayAccess.tx; the opt-in payment path therefore cannot submit as the account whose derived H160 is checked for access. Construct and pass a Product SDK signer manager bound to the explicitly connected account before enabling this writer.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Agreed. Fixed in 9a6ef15: Product CDM writes now lazily create a Product SDK SignerManager/HostProvider for the configured Product account, pass that signerManager into createProductCdmContracts, and fail closed if the selected host public key does not match the Product wallet account already connected in Dotify. Added unit coverage for the signer handoff and mismatch guard.
Outcome
Classic unlock payments now go through Dotify's
RuntimeWritePortinstead 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:
useCatalogdirectly importedcreateViemRuntimeWriter, fetched an EVM wallet client, and submittedmusicRoyPayAccessitself.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-cdmRuntimeWritePortremains 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.tsselects 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.useCatalogmemoizes both runtime reader and writer, thenpayForTrackAccesssubmits payment throughruntimeWriter.payForAccess. Wallet gating now names the real signer requirement:Design decisions and tradeoffs
The PR deliberately does not add a new env var.
VITE_DOTIFY_RUNTIME_ADAPTERalready 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 explicitVITE_DOTIFY_RUNTIME_ADAPTER=product-cdmbuild 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_ADAPTERchanged 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
web/src/features/runtime/runtimeWriterProvider.ts- verify lazy Product setup, viem-per-write wallet resolution, and fail-closed adapter selection.web/src/hooks/useCatalog.ts- verify Classic unlock now callsruntimeWriter.payForAccessand no longer constructs viem directly.web/src/features/runtime/runtimeWriterProvider.test.ts- verify the default viem path, Product non-bundled error, and Product no-fallback routing.web/e2e/classic-unlock.spec.ts- verify the e2e assertion matches the current UI wording.Verify carefully
Validation
npm run test:unit -- runtimeWriterProvider runtimeReaderProvider productCdmRuntimeAdapter viemRuntimeAdapternpm run buildnpm run lintApp.tsxandArtistShell.tsx.npm run test:e2e -- classic-unlock.spec.tsnpm run test:unitnpx prettier --check ...Note: the global
npm run fmt:checkstill 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
docs/backlog/polkadot-product-readiness-and-killer-dapp-roadmap.mdDotify sprints)