Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/remove-username.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"playground-cli": patch
---

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.
2 changes: 0 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ These aren't self-evident from reading the code and have bitten us before. Treat
- **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.
- **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.
- **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.
- **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)`.

### Deploy / Bulletin

Expand All @@ -58,7 +57,6 @@ These aren't self-evident from reading the code and have bitten us before. Treat
- **`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.
- **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.
- **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.
- **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`.
- **`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.

### Allowances / session
Expand Down
6 changes: 0 additions & 6 deletions src/commands/login/IdentityLines.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,6 @@ import { PLAYGROUND_PRODUCT_ID } from "../../config.js";
* separately — same key, two encodings — which read as "two accounts"
* to users. Collapsed here.
*
* The user's registry username is intentionally NOT rendered here — it
* lives in the top breadcrumb (see `Header`'s `username` prop) so it
* stays visible across every screen in the command. `UsernamePrompt`
* owns the read + write path; this component is purely the address
* pair now.
*
* The SS58 + H160 are taken straight off the auth-derived pair so
* they never drift — the bug we had previously was running
* `deriveProductAccountPublicKey` again on the already-derived SS58
Expand Down
27 changes: 1 addition & 26 deletions src/commands/login/LoginScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { DependencyList } from "./DependencyList.js";
import { IdentityLines } from "./IdentityLines.js";
import { QrLogin } from "./QrLogin.js";
import { AccountSetup } from "./AccountSetup.js";
import { UsernamePrompt } from "./UsernamePrompt.js";
import { NextSteps } from "./NextStepsCallout.js";
import { computeAllDone } from "./completion.js";
import { VERSION_LABEL } from "../../utils/version.js";
Expand All @@ -42,18 +41,13 @@ export function LoginScreen({
const [depsComplete, setDepsComplete] = useState(false);
const [accountComplete, setAccountComplete] = useState(false);
const [accountOk, setAccountOk] = useState(true);
// `null` ≡ "no username on chain and user declined to set one";
// `string` ≡ "username known (existing or just-claimed)".
// `undefined` ≡ "prompt has not resolved yet".
const [username, setUsername] = useState<string | null | undefined>(undefined);

const allDone = computeAllDone({
needsQr,
authResolved,
loggedInAddress: addresses?.productAddress ?? null,
depsComplete,
accountComplete,
usernameComplete: username !== undefined,
});

const handleDepsDone = () => {
Expand All @@ -68,16 +62,6 @@ export function LoginScreen({
const handleAccountDone = (success: boolean) => {
setAccountOk(success);
setAccountComplete(true);
// Account setup is a prerequisite for setUsername (the tx needs the
// smart-contract allowance + a funded product account). When account
// setup fails we skip the prompt entirely and treat the step as
// resolved-with-no-username so the login flow can land on
// "setup complete (with errors)" instead of hanging.
if (!success) setUsername(null);
};

const handleUsernameDone = (next: string | null) => {
setUsername(next);
};

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

return (
<Box flexDirection="column">
<Header
cmd="playground login"
network={getNetworkLabel()}
username={username ?? undefined}
right={VERSION_LABEL}
/>
<Header cmd="playground login" network={getNetworkLabel()} right={VERSION_LABEL} />

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

Expand All @@ -107,10 +86,6 @@ export function LoginScreen({
/>
)}

{addresses && accountComplete && accountOk && (
<UsernamePrompt addresses={addresses} onDone={handleUsernameDone} />
)}

{allDone && (
<Section gapBelow={false}>
<Row
Expand Down
6 changes: 3 additions & 3 deletions src/commands/login/QrLogin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ export function QrLogin({
}, []);

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