From 165431f5b48999a1208386407c043abe6c1b2718 Mon Sep 17 00:00:00 2001
From: Kevin Nzeng Essimengane
Date: Mon, 24 Aug 2026 02:35:11 +0200
Subject: [PATCH 1/2] Use native payment asset labels in UI
---
.github/pull_request_template.md | 12 ++++++++++
docs/explanation/access-control-model.md | 5 ++--
.../product-devnet-architecture.md | 3 ++-
.../pull-requests-as-knowledge-sharing.md | 24 ++++++++++++-------
web/e2e/classic-unlock.spec.ts | 6 +++--
web/src/components/AccessGateOverlay.tsx | 12 ++++++----
web/src/components/AccountWalletModal.tsx | 1 +
web/src/components/StageRail.tsx | 7 ++++--
web/src/components/WalletModal.tsx | 4 +++-
.../features/payments/paymentModel.test.ts | 8 +++++++
web/src/features/payments/paymentModel.ts | 4 ++++
web/src/hooks/useCatalog.ts | 1 +
web/src/shared/utils/format.ts | 8 +++----
web/src/views/ArtistProfileView.tsx | 6 ++++-
web/src/views/ListenView.tsx | 6 +++--
web/src/views/ListenerShell.tsx | 4 ++++
web/src/views/PlayerView.tsx | 8 +++++--
web/src/views/YouView.tsx | 6 +++--
web/src/views/artist/ArtistConsole.tsx | 5 ++++
web/src/views/artist/NewReleaseTab.tsx | 8 ++++---
web/src/views/artist/OverviewTab.tsx | 12 ++++++----
web/src/views/artist/ReleasesTab.tsx | 10 ++++----
web/src/views/artist/RoyaltiesTab.tsx | 10 +++++---
23 files changed, 125 insertions(+), 45 deletions(-)
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index bb69812..0010862 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -59,6 +59,18 @@ modes, persistence, configuration, monitoring, migration, and rollback needs.
+## Pre-PR alignment check
+
+
+
+- Dependency / SDK currency:
+- Improvement flags:
+- Philosophical alignment:
+
## Metadata checklist
- [ ] Backlog issue linked with correct close/reference semantics
diff --git a/docs/explanation/access-control-model.md b/docs/explanation/access-control-model.md
index dc118e5..26cf6d4 100644
--- a/docs/explanation/access-control-model.md
+++ b/docs/explanation/access-control-model.md
@@ -68,8 +68,9 @@ level, it can access the track without paying.
In Classic mode, a listener pays a fixed amount of the configured chain's
native token to unlock full playback. On the current Product DevNet/Paseo Asset
-Hub runtime rail, that token is PAS. The payment goes directly and immediately
-to the artist's wallet - no intermediary, no payout schedule, no platform cut.
+Hub runtime rail, that token is PAS; a DOT-backed Polkadot Hub EVM chain would
+display DOT instead. The payment goes directly and immediately to the artist's
+wallet - no intermediary, no payout schedule, no platform cut.
Once a listener has paid for a track, their wallet is recorded on-chain. They
can return and play the track at any time without paying again, even if the
diff --git a/docs/explanation/product-devnet-architecture.md b/docs/explanation/product-devnet-architecture.md
index 5e0f82d..df6ecc4 100644
--- a/docs/explanation/product-devnet-architecture.md
+++ b/docs/explanation/product-devnet-architecture.md
@@ -196,7 +196,8 @@ Adapters:
Classic unlock no longer passes a loose `(runtimeAddress, contentHash, value)`
triple into runtime writers. The catalog hook creates a typed native runtime
payment intent first: asset symbol derived from the configured EVM chain
-(`PAS` on the current Product DevNet/Paseo Asset Hub chain), rail
+(`PAS` on the current Product DevNet/Paseo Asset Hub chain, `DOT` on a
+DOT-backed Polkadot Hub EVM chain), rail
`runtime-native`, runtime address, content hash, and 18-decimal native amount.
The amount comes from the runtime or catalog API `pricePlanck` value when
available; `priceDot` is only the rounded display string. The viem and Product
diff --git a/docs/explanation/pull-requests-as-knowledge-sharing.md b/docs/explanation/pull-requests-as-knowledge-sharing.md
index afc58d4..fbdaf5c 100644
--- a/docs/explanation/pull-requests-as-knowledge-sharing.md
+++ b/docs/explanation/pull-requests-as-knowledge-sharing.md
@@ -104,14 +104,22 @@ Before requesting review:
1. Confirm the branch contains one coherent backlog scope.
2. Re-read the PR description against the final diff.
-3. Verify the issue link and closure semantics.
-4. Add the PR to Project 5 and mirror the ticket fields.
-5. Check assignee, labels, milestone, draft state, and reviewers.
-6. Provide an ordered code map and high-risk review prompts.
-7. Report commands and outcomes, including warnings.
-8. Identify evidence that still requires deployment or manual validation.
-9. Confirm docs describe operational and security boundaries honestly.
-10. Make sure a reviewer can explain the design without private context.
+3. Check the relevant official dependency and SDK versions against the pinned
+ codebase. Do not update them opportunistically inside an unrelated PR, but
+ report drift and any compatibility work the reviewer should know about.
+4. Flag codebase improvements discovered while scoping the PR, even when they
+ are deliberately deferred to avoid scope creep.
+5. Review philosophical alignment: the change should strengthen shared
+ listening, artist sovereignty, low-friction trust, or honest access
+ boundaries. If it does not, explain why it still belongs.
+6. Verify the issue link and closure semantics.
+7. Add the PR to Project 5 and mirror the ticket fields.
+8. Check assignee, labels, milestone, draft state, and reviewers.
+9. Provide an ordered code map and high-risk review prompts.
+10. Report commands and outcomes, including warnings.
+11. Identify evidence that still requires deployment or manual validation.
+12. Confirm docs describe operational and security boundaries honestly.
+13. Make sure a reviewer can explain the design without private context.
## Definition of done
diff --git a/web/e2e/classic-unlock.spec.ts b/web/e2e/classic-unlock.spec.ts
index f135e91..6466edb 100644
--- a/web/e2e/classic-unlock.spec.ts
+++ b/web/e2e/classic-unlock.spec.ts
@@ -16,18 +16,20 @@ async function readClassicUnlockState(page: Page) {
return page.evaluate(() => window.__DOTIFY_E2E_CLASSIC_UNLOCK__ as ClassicUnlockE2eState | undefined);
}
+const E2E_NATIVE_PAYMENT_SYMBOL = 'PAS';
+
test('Classic track stays locked before payment and unlocks full playback after payment', async ({ page }) => {
await page.goto('/');
const trackCard = page.getByTestId('track-card');
await expect(trackCard).toContainText('Deterministic Classic Unlock');
- await expect(trackCard).toContainText('0.5 DOT');
+ await expect(trackCard).toContainText(`0.5 ${E2E_NATIVE_PAYMENT_SYMBOL}`);
await page.getByTestId('track-card-open').click();
await expect(page.getByTestId('locked-player-state')).toContainText('Listening closed');
await expect(page.getByTestId('access-warning')).toContainText('Support and open this track');
- await expect(page.getByTestId('access-warning')).toContainText('0.5 DOT');
+ await expect(page.getByTestId('access-warning')).toContainText(`0.5 ${E2E_NATIVE_PAYMENT_SYMBOL}`);
const beforePayment = await readClassicUnlockState(page);
expect(beforePayment?.fullKeyRequests ?? 0).toBe(0);
diff --git a/web/src/components/AccessGateOverlay.tsx b/web/src/components/AccessGateOverlay.tsx
index 724999d..7d5113c 100644
--- a/web/src/components/AccessGateOverlay.tsx
+++ b/web/src/components/AccessGateOverlay.tsx
@@ -1,20 +1,24 @@
import { LockKeyhole } from 'lucide-react';
import { Dialog } from './Dialog';
import type { AccessGate } from '../shared/types';
+import { nativeRuntimeAmountLabel, type DotifyNativeRuntimeAsset } from '../features/payments/paymentModel';
export function AccessGateOverlay({
gate,
+ nativePaymentAsset,
onDismiss,
onPay,
onSignIn
}: {
gate: AccessGate;
+ nativePaymentAsset: Pick;
onDismiss: () => void;
onPay?: () => void;
onSignIn?: () => void;
}) {
const configuredSplitBps = gate.track.royaltySplits.reduce((total, split) => total + split.bps, 0);
const artistRemainderBps = Math.max(0, 10_000 - configuredSplitBps);
+ const supportAmount = nativeRuntimeAmountLabel(gate.track.priceDot, nativePaymentAsset);
return (
You set the access
- Free for verified humans, or a price in DOT - per track, your call.
+ Free for verified humans, or a price in {nativePaymentSymbol} - per track, your call.
diff --git a/web/src/views/artist/ReleasesTab.tsx b/web/src/views/artist/ReleasesTab.tsx
index 8db6240..949d8da 100644
--- a/web/src/views/artist/ReleasesTab.tsx
+++ b/web/src/views/artist/ReleasesTab.tsx
@@ -17,6 +17,7 @@ type ReleasesTabProps = {
onUpdateReleaseAccessMode: (track: CatalogTrack, accessMode: AccessMode, priceDot: string, personhoodLevel: PersonhoodLevel) => void;
onSetReleaseActive: (track: CatalogTrack, active: boolean) => void;
releaseActionId: string | null;
+ nativePaymentSymbol: string;
/** Into orbit (Constellation phase C): id of a release that just landed on
* chain while the console was open; its card plays a one-shot arrival. */
arrivedReleaseId?: string | null;
@@ -34,6 +35,7 @@ export function ReleasesTab({
onUpdateReleaseAccessMode,
onSetReleaseActive,
releaseActionId,
+ nativePaymentSymbol,
arrivedReleaseId = null
}: ReleasesTabProps) {
const selectedRelease = artistTracks.find(track => track.id === selectedReleaseId) ?? artistTracks[0] ?? null;
@@ -103,7 +105,7 @@ export function ReleasesTab({
{track.active === false
? 'Inactive'
: track.accessMode === 'classic'
- ? `${track.priceDot} DOT`
+ ? `${track.priceDot} ${nativePaymentSymbol}`
: track.accessMode === 'free'
? 'Free'
: track.personhoodLevel}
@@ -137,7 +139,7 @@ export function ReleasesTab({
{accessModeLabel(selectedRelease)}
{selectedRelease.accessMode === 'classic'
- ? `${selectedRelease.priceDot} DOT`
+ ? `${selectedRelease.priceDot} ${nativePaymentSymbol}`
: selectedRelease.accessMode === 'free'
? 'Free'
: 'Free for verified humans'}
@@ -194,7 +196,7 @@ export function ReleasesTab({
From 0d699d51e5da588378f813287677bdbf08620b88 Mon Sep 17 00:00:00 2001
From: Kevin Nzeng Essimengane
Date: Mon, 24 Aug 2026 17:27:35 +0200
Subject: [PATCH 2/2] Address native payment label review
---
.github/pull_request_template.md | 12 ----------
.../pull-requests-as-knowledge-sharing.md | 24 +++++++------------
web/e2e/artist-publish.spec.ts | 8 ++++---
3 files changed, 13 insertions(+), 31 deletions(-)
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 0010862..bb69812 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -59,18 +59,6 @@ modes, persistence, configuration, monitoring, migration, and rollback needs.
-## Pre-PR alignment check
-
-
-
-- Dependency / SDK currency:
-- Improvement flags:
-- Philosophical alignment:
-
## Metadata checklist
- [ ] Backlog issue linked with correct close/reference semantics
diff --git a/docs/explanation/pull-requests-as-knowledge-sharing.md b/docs/explanation/pull-requests-as-knowledge-sharing.md
index fbdaf5c..afc58d4 100644
--- a/docs/explanation/pull-requests-as-knowledge-sharing.md
+++ b/docs/explanation/pull-requests-as-knowledge-sharing.md
@@ -104,22 +104,14 @@ Before requesting review:
1. Confirm the branch contains one coherent backlog scope.
2. Re-read the PR description against the final diff.
-3. Check the relevant official dependency and SDK versions against the pinned
- codebase. Do not update them opportunistically inside an unrelated PR, but
- report drift and any compatibility work the reviewer should know about.
-4. Flag codebase improvements discovered while scoping the PR, even when they
- are deliberately deferred to avoid scope creep.
-5. Review philosophical alignment: the change should strengthen shared
- listening, artist sovereignty, low-friction trust, or honest access
- boundaries. If it does not, explain why it still belongs.
-6. Verify the issue link and closure semantics.
-7. Add the PR to Project 5 and mirror the ticket fields.
-8. Check assignee, labels, milestone, draft state, and reviewers.
-9. Provide an ordered code map and high-risk review prompts.
-10. Report commands and outcomes, including warnings.
-11. Identify evidence that still requires deployment or manual validation.
-12. Confirm docs describe operational and security boundaries honestly.
-13. Make sure a reviewer can explain the design without private context.
+3. Verify the issue link and closure semantics.
+4. Add the PR to Project 5 and mirror the ticket fields.
+5. Check assignee, labels, milestone, draft state, and reviewers.
+6. Provide an ordered code map and high-risk review prompts.
+7. Report commands and outcomes, including warnings.
+8. Identify evidence that still requires deployment or manual validation.
+9. Confirm docs describe operational and security boundaries honestly.
+10. Make sure a reviewer can explain the design without private context.
## Definition of done
diff --git a/web/e2e/artist-publish.spec.ts b/web/e2e/artist-publish.spec.ts
index c3fb558..f06d3f1 100644
--- a/web/e2e/artist-publish.spec.ts
+++ b/web/e2e/artist-publish.spec.ts
@@ -25,6 +25,8 @@ declare global {
const fixtureDir = path.join(path.dirname(fileURLToPath(import.meta.url)), 'fixtures');
const audioFixture = path.join(fixtureDir, 'artist-release.wav');
const coverFixture = path.join(fixtureDir, 'artist-cover.svg');
+const E2E_NATIVE_PAYMENT_SYMBOL = 'PAS';
+const E2E_ARTIST_SHARE_PERCENT = '72.5';
async function readArtistPublishState(page: Page) {
return page.evaluate(() => window.__DOTIFY_E2E_ARTIST_PUBLISH__ as ArtistPublishE2eState | undefined);
@@ -76,12 +78,12 @@ async function completeReleaseDraft(page: Page) {
await page.getByRole('button', { name: 'Continue' }).click();
await page.getByTestId('release-access-select').selectOption('classic');
await page.getByTestId('release-price-input').fill('0.75');
- await page.getByTestId('release-royalty-input').fill('7250');
+ await page.getByTestId('release-royalty-input').fill(E2E_ARTIST_SHARE_PERCENT);
await page.getByRole('button', { name: 'Continue' }).click();
const reviewPanel = page.locator('.release-review');
await expect(reviewPanel.getByText('E2E Published Signal')).toBeVisible();
- await expect(reviewPanel.getByText('0.75 DOT')).toBeVisible();
+ await expect(reviewPanel.getByText(`0.75 ${E2E_NATIVE_PAYMENT_SYMBOL}`)).toBeVisible();
}
test('artist can create a runtime, publish a release, and see it in the listener catalog', async ({ page }) => {
@@ -102,7 +104,7 @@ test('artist can create a runtime, publish a release, and see it in the listener
const publishedCard = page.getByTestId('track-card').filter({ hasText: 'E2E Published Signal' });
await expect(publishedCard).toContainText('E2E Artist');
await expect(publishedCard).toContainText('A deterministic artist publish e2e release.');
- await expect(publishedCard).toContainText('0.75 DOT');
+ await expect(publishedCard).toContainText(`0.75 ${E2E_NATIVE_PAYMENT_SYMBOL}`);
});
test('artist onboarding handles a missing wallet without enabling profile creation', async ({ page }) => {