Skip to content

Commit a224c7f

Browse files
Merge pull request #386 from paritytech/feat/remove-username
feat(login): remove username step from playground login
2 parents b91c7f9 + 3a4b9e2 commit a224c7f

17 files changed

Lines changed: 32 additions & 1232 deletions

.changeset/remove-username.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"playground-cli": patch
3+
---
4+
5+
Remove the username step from `playground login`. The flow no longer prompts you to claim a registry handle and no longer displays a username in the header.

CLAUDE.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ These aren't self-evident from reading the code and have bitten us before. Treat
3636
- **All chain URLs / contract addresses live in `src/config.ts` — EXCEPT the CDM meta-registry address, which lives in `@parity/cdm-env` and resolves per-env via `getRegistryAddress(cfg.cdmEnvName)`.** Never inline a websocket URL or `0x…` address anywhere else. `config.ts` carries a `cdmEnvName` per env (the name cdm-env keys on — our `summit` is cdm-env's `w3s`, paseo passes through as `paseo-next-v2`); `src/utils/registry.ts` and `src/commands/contract.ts` resolve the meta-registry root from it and inject it over `cdm.json::registry` (which is just whatever `cdm i` baked — do NOT hand-edit `cdm.json`). `getRegistryAddress` returns `""` for an unknown/undeployed env, and `registry.ts` throws a clear "bump @parity/cdm-env" error on empty.
3737
- **Adding a network / Summit — the deployment-doc checklist.** A divergence guard (`src/config.test.ts`) reads polkadot-app-deploy's bundled `environments.json` via its public `loadEnvironments()` and asserts every wired `CONFIGS` env's endpoints/network/gateway match upstream, AND that the default env's `getRegistryAddress(cdmEnvName)` is non-empty — so the one-line switch can't merge until the target is genuinely ready. To point a release at Summit: (1) confirm `@parity/cdm-env` ships a non-empty `w3s` registry address (it's `""` through 2.0.5 — `node -e "console.log(require('@parity/cdm-env').getRegistryAddress('w3s'))"`; bump the dep if empty); (2) confirm Summit endpoints still match polkadot-app-deploy's `assets/environments.json` `summit`/`chains[*].endpoints.summit` (the guard enforces this — bump `@parity/polkadot-app-deploy` and update the `SUMMIT` block together if they drift); (3) flip `ACTIVE_TESTNET_ENV` to `"summit"`; (4) `pnpm typecheck && pnpm test` (the guard is the gate). Prerequisites that land upstream BEFORE the CLI switch (not our PR): the Summit CDM meta-registry contract + the playground-registry published to CDM (resolves by name `@w3s/playground-registry`), the Summit DotNS contracts (owned by polkadot-app-deploy's env catalog, keyed by env id), and the `w3s` registry address in `@parity/cdm-env`. Heads-up: cdm-env's `w3s.ipfsGatewayUrl` is `""` and disagrees with environments.json's `summit.ipfs` — we source the gateway from environments.json on purpose, so the guard checks against that.
3838
- **Upstream SDK status for Summit (audited June 2026 at the pinned versions — no bumps needed).** `@parity/product-sdk` already ships Summit at the versions we use: `descriptors@0.6.0` exports `summit-asset-hub`/`summit-bulletin`/`summit-individuality`, and `cloud-storage@0.6.0` carries the `summit` network preset (genesis + bulletin). product-sdk calls the network **`summit`**; only `@parity/cdm-env` and this CLI use the key **`w3s`** (that two-name split is why `cdmEnvName` exists — don't "fix" it). The CLI does NOT depend on `@parity/product-sdk-chain-client`, so its preset table is irrelevant here. `@novasamatech/*` (host-papp/statement-store/host-api/sdk-statement, via `@parity/product-sdk-terminal@0.5.0` → host-papp 0.8.7) is fully **env-agnostic**: SSO handshake, statement-store topic/SessionId derivation, and allowance slot-key derivation embed no network constant (genesisHash is a runtime SCALE field, not a baked value), and host-papp's hardcoded `SS_*_ENDPOINTS` People defaults are dead fallbacks because the terminal always forwards `getChainConfig().peopleEndpoints`. Net: the ONLY thing still pending for a Summit switch is `@parity/cdm-env` shipping the non-empty `w3s` registry address — nothing in product-sdk or triangle-js-sdks.
39-
- **Username lookup hits `Resources.Consumers` on the People parachain** (`src/utils/username.ts`). Mirrors `@novasamatech/host-papp`'s `createIdentityRpcAdapter`. Pass the SS58 string directly to `getValues([[ss58]])` — do NOT round-trip through `AccountId().dec(ss58)`. The upstream code only does that because its callers pass `0x…` hex; on an SS58 string `Bytes(32).dec` silently corrupts it into a different 32-byte sequence and the lookup fails opaquely as `(lookup failed)`.
4039

4140
### Deploy / Bulletin
4241

@@ -58,7 +57,6 @@ These aren't self-evident from reading the code and have bitten us before. Treat
5857
- **`session.rootAccountId` is whatever the mobile app published as `rootUserAccountId` in the SSO handshake.** On current mobile builds (`polkadot-app-android-v2`, see `feature/sso/impl/.../RealSsoHandshakeUseCase.kt:34``deriveRootAccount() = derivationPath = null`) it's the bare-mnemonic sr25519 root with no junction. The host-papp SDK does not derive it — it just decodes the 32 bytes from `HandshakeResponseSensitiveData.rootUserAccountId` (`triangle-js-sdks/packages/host-papp/src/sso/auth/scale/handshake.ts:23-27`) and forwards them. If a future mobile release changes the path, our display will silently change with it — the source of truth is the phone, not the CLI.
5958
- **The mobile's "Wallet account address" and "Candidate account address" debug rows are NOT reachable from the host.** They're sr25519 of mnemonic + `//wallet` and mnemonic + `//candidate` respectively (`feature/account/impl/.../RealAccountRepository.kt:166-173`, hard junctions). Hard derivations can't be reproduced from a public key, so the CLI never sees those SS58s. Don't try to surface a "wallet address that matches mobile" — it isn't possible without the mnemonic.
6059
- **The playground product account is derived by exactly one function** (`src/utils/sessionSigner.ts::derivePlaygroundProductPublicKey`), called by both `createPlaygroundSessionSigner` (signer construction) and `auth.ts::deriveSessionAddresses` (display triple). The math is `deriveProductAccountPublicKey(rootAccountId, "playground.dot", 0)` from `@parity/product-sdk-keys`. Do NOT call `deriveProductAccountPublicKey` (or any helper that wraps it) on an already-product-derived SS58 — that yields a doubly-derived ghost account. The `productAccountDisplay` / `productAccountAddresses` helpers that used to live in `src/commands/login/identityLine.ts` had exactly this bug and were deleted; resist re-introducing them. A frozen-vector regression test in `src/utils/auth.test.ts` (`deriveSessionAddresses` block) locks the pubkey/H160 the playground-app expects.
61-
- **Username storage is keyed on `session.rootAccountId`, not on the product account.** `Resources.Consumers[<rootAccountId>]` on the People parachain is populated by mobile's `Resources.register_person` call (signed by `//wallet`-derived key, but the storage key is the root). `lookupUsername` MUST be called with `addresses.rootAddress`, not the product SS58. Polkadot-desktop's `useSessionIdentity(session)` does the same — both read off the SSO `rootAccountId`.
6260
- **`SessionAddresses` triples are computed once in `auth.ts` and threaded through.** `ConnectResult`, `LoginStatus.success`, and `SessionHandle` all carry the `{ rootAddress, productAddress, productH160 }` bundle. `SessionHandle.address` is kept as a back-compat alias for `addresses.productAddress` because `signer.ts::resolveSigner` spreads the handle into `ResolvedSigner` and downstream deploy code (`signerMode.ts`, `playground.ts`, `registry.ts`, `DeployScreen.tsx`) reads `.address` for the signing key. UI code should prefer `addresses` so the root vs product distinction stays explicit.
6361

6462
### Allowances / session

src/commands/login/IdentityLines.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,6 @@ import { PLAYGROUND_PRODUCT_ID } from "../../config.js";
3636
* separately — same key, two encodings — which read as "two accounts"
3737
* to users. Collapsed here.
3838
*
39-
* The user's registry username is intentionally NOT rendered here — it
40-
* lives in the top breadcrumb (see `Header`'s `username` prop) so it
41-
* stays visible across every screen in the command. `UsernamePrompt`
42-
* owns the read + write path; this component is purely the address
43-
* pair now.
44-
*
4539
* The SS58 + H160 are taken straight off the auth-derived pair so
4640
* they never drift — the bug we had previously was running
4741
* `deriveProductAccountPublicKey` again on the already-derived SS58

src/commands/login/LoginScreen.tsx

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import { DependencyList } from "./DependencyList.js";
2020
import { IdentityLines } from "./IdentityLines.js";
2121
import { QrLogin } from "./QrLogin.js";
2222
import { AccountSetup } from "./AccountSetup.js";
23-
import { UsernamePrompt } from "./UsernamePrompt.js";
2423
import { NextSteps } from "./NextStepsCallout.js";
2524
import { computeAllDone } from "./completion.js";
2625
import { VERSION_LABEL } from "../../utils/version.js";
@@ -42,18 +41,13 @@ export function LoginScreen({
4241
const [depsComplete, setDepsComplete] = useState(false);
4342
const [accountComplete, setAccountComplete] = useState(false);
4443
const [accountOk, setAccountOk] = useState(true);
45-
// `null` ≡ "no username on chain and user declined to set one";
46-
// `string` ≡ "username known (existing or just-claimed)".
47-
// `undefined` ≡ "prompt has not resolved yet".
48-
const [username, setUsername] = useState<string | null | undefined>(undefined);
4944

5045
const allDone = computeAllDone({
5146
needsQr,
5247
authResolved,
5348
loggedInAddress: addresses?.productAddress ?? null,
5449
depsComplete,
5550
accountComplete,
56-
usernameComplete: username !== undefined,
5751
});
5852

5953
const handleDepsDone = () => {
@@ -68,16 +62,6 @@ export function LoginScreen({
6862
const handleAccountDone = (success: boolean) => {
6963
setAccountOk(success);
7064
setAccountComplete(true);
71-
// Account setup is a prerequisite for setUsername (the tx needs the
72-
// smart-contract allowance + a funded product account). When account
73-
// setup fails we skip the prompt entirely and treat the step as
74-
// resolved-with-no-username so the login flow can land on
75-
// "setup complete (with errors)" instead of hanging.
76-
if (!success) setUsername(null);
77-
};
78-
79-
const handleUsernameDone = (next: string | null) => {
80-
setUsername(next);
8165
};
8266

8367
useEffect(() => {
@@ -86,12 +70,7 @@ export function LoginScreen({
8670

8771
return (
8872
<Box flexDirection="column">
89-
<Header
90-
cmd="playground login"
91-
network={getNetworkLabel()}
92-
username={username ?? undefined}
93-
right={VERSION_LABEL}
94-
/>
73+
<Header cmd="playground login" network={getNetworkLabel()} right={VERSION_LABEL} />
9574

9675
{needsQr && <QrLogin login={login} onDone={handleAuthDone} />}
9776

@@ -107,10 +86,6 @@ export function LoginScreen({
10786
/>
10887
)}
10988

110-
{addresses && accountComplete && accountOk && (
111-
<UsernamePrompt addresses={addresses} onDone={handleUsernameDone} />
112-
)}
113-
11489
{allDone && (
11590
<Section gapBelow={false}>
11691
<Row

src/commands/login/QrLogin.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ export function QrLogin({
5050
}, []);
5151

5252
if (status.step === "success") {
53-
// The "logged in" row is rendered by IdentityLines (which also
54-
// shows username + product account); QrLogin's success path no
55-
// longer prints its own row to avoid duplicating the address.
53+
// The "logged in" row is rendered by IdentityLines (which shows the
54+
// wallet root + product account); QrLogin's success path no longer
55+
// prints its own row to avoid duplicating the address.
5656
return null;
5757
}
5858
if (status.step === "error") {

0 commit comments

Comments
 (0)