Model track access payment intents - #121
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2b2d2195c9
ℹ️ 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".
|
|
||
| export const DOTIFY_NATIVE_RUNTIME_ASSET = { | ||
| kind: 'native', | ||
| symbol: 'DOT', |
There was a problem hiding this comment.
Label the native Paseo asset as PAS rather than DOT
In the configured Paseo Asset Hub/Product DevNet environment, msg.value spends the native PAS fee token, not DOT; the repository's own stack description confirms this at docs/design/dotify-product-stack-alignment.md:166-168. Because useCatalog now displays paymentIntent.asset.symbol in the confirmation message, this constant tells listeners they are approving DOT while their wallet spends PAS and makes the new settlement model encode the wrong asset. Derive the symbol from the configured chain or identify this rail as PAS/generic native currency.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed in commit 4c5e5c5.
The runtime-native asset is no longer hard-coded as DOT. paymentModel now requires the native asset on the payment intent, useCatalog derives it from the configured EVM chain via resolveEvmChain, and the shared chain config labels chain 420420417 as PAS. The access gate and transaction feedback now use that derived symbol, with UNIT only as a generic fallback for local/unknown chains.
I also updated the payment docs to describe this as runtime-native/PAS on Product DevNet rather than DOT, and added tests for both the payment asset derivation and the chain currency mapping.
Validation:
npm run test:unit -- paymentModel runtimeWriterProvider viemRuntimeAdapter productCdmRuntimeAdapter contracts-> 33 passednpm run build-> passed, existing chunk warnings onlynpm run lint-> passed with the existing 3 React Hook warningsnpm run test:e2e -- e2e/classic-unlock.spec.ts-> passednpm run build:product-devnet-> passed; bootstrap refresh kept the existing snapshot because fetch failedgit diff --check-> clean
There was a problem hiding this comment.
Addressed in 5816286. The implementation now makes the boundary explicit: EVM JSON-RPC is used to fetch chainId, but native symbol metadata is not available through a standard on-chain/RPC method. Dotify maps known Polkadot Hub EVM chain ids to native currencies instead, including 420420419 -> DOT, 420420418 -> KSM, and the current Product DevNet/Paseo rail 420420417 -> PAS. Added unit coverage and docs so this does not silently drift back to a hard-coded DOT/PAS assumption.
|
Claude feedback assessment and follow-up commit: 96e2bba I reviewed the five points from Claude and treated them as review feedback, not automatic instructions. The valuable parts are now implemented:
Validation run:
|
knzeng-e
left a comment
There was a problem hiding this comment.
reviewed. A question asked to chain the confg/contracts.ts *nativeCurrncyForChain* function
Outcome
This PR makes Dotify's Classic unlock payment path explicit before the next Product payment work: listener unlocks now build a typed native runtime payment intent before they reach
RuntimeWritePort, and Product CASH is represented as a separate unsupported rail instead of being conflated with runtimemsg.value.Issue and context
Refs #85 and the Product roadmap in
docs/backlog/polkadot-product-readiness-and-killer-dapp-roadmap.md.PR #120 moved Classic unlock writes behind
RuntimeWritePort, which was the right seam for viem vs Product CDM contract submission. The remaining ambiguity was the value flowing through that seam:(runtimeAddress, contentHash, value)was mechanically correct, but it did not encode whether the value was native runtime settlement or future Product CASH settlement.That matters because Dotify wants DOT/CASH actions to run inside the Product environment, but CASH lives on People chain while Dotify runtime entitlement is granted by Asset Hub SmartRuntimes. Until Product confirms the settlement model, the app must not imply CASH can be silently converted into native runtime value or that access can be marked paid without runtime evidence.
Architecture and key concepts
The new payment model has two rails:
runtime-native: executable today. It carries assetDOT, Asset Hub runtime address, content hash, and 18-decimal native amount.RuntimeWritePort.payForAccess()accepts only this executable intent.product-cash: modeled but unsupported. It records the future CASH path and explains why it cannot execute until there is a Product-confirmed receipt or bridge design.The runtime write adapters stay behaviorally unchanged at the contract boundary:
The viem adapter still submits
writeContract({ functionName: 'musicRoyPayAccess', args: [contentHash], value }). The Product CDM adapter still submitsmusicRoyPayAccess.tx(contentHash, { value }). The difference is that both receive a named intent, not loose positional payment data.How it works
useCatalog.payForTrackAccess()converts the track price withdotToPlanck()and builds a native runtime intent.Design decisions and tradeoffs
The implementation keeps the contract call unchanged because the deployed runtime still grants access through
musicRoyPayAccess()and nativemsg.value. Changing the contract or adding a fake CASH conversion here would overstate Product readiness.The alternative was to make
RuntimeWritePortaccept a union of native and CASH intents. I rejected that for this seam: adapters that call SmartRuntimemsg.valueshould not even type-check with CASH. A later Product payment/receipt port can consumeproduct-cashonce the settlement model is confirmed.The other small behavior change is intentional: after a successful unlock, Dotify navigates back to the player before reselecting the track. That matches the unlock promise and stabilizes the Classic unlock e2e flow.
Security, failure, and operations
docs/operations/deployment-configuration.mdwas checked and does not need an update for this PR.VITE_DOTIFY_RUNTIME_ADAPTER=product-cdm; Product host transaction smoke evidence remains a follow-up.Review guide
Suggested order
web/src/features/payments/paymentModel.tsand.test.ts- verify the rail model and fail-closed intent creation.web/src/features/runtime/runtimePorts.ts- verify that executable runtime writes accept only native runtime intents.web/src/features/runtime/viemRuntimeAdapter.tsandproductCdmRuntimeAdapter.ts- verify the actual contract calls are unchanged except for reading from the intent.web/src/features/runtime/runtimeWriterProvider.ts- verify adapter selection still stays lazy and fail-closed.web/src/hooks/useCatalog.ts- verify Classic unlock creates the intent, handles setup errors clearly, and returns to the player after success.Verify carefully
RuntimeWritePort.payForAccess().musicRoyPayAccess(contentHash)with the same native value.musicRoyPayAccess.tx(contentHash, { value })and does not claim Product CASH support.Validation
npm run test:unit -- paymentModel runtimeWriterProvider viemRuntimeAdapter productCdmRuntimeAdapternpm run test:e2e -- e2e/classic-unlock.spec.tsnpm run buildnpm run build:product-devnetnpm run lintApp.tsxandArtistShell.tsx.git diff --checkKnown limitations and follow-ups
Metadata checklist
Dotify sprints)In Reviewknzeng-edotify-backlog,product-sdk,polkadot,contracts,frontend,documentation