From 82afc4d17804e7ea1d7589c72a7b4bd052120816 Mon Sep 17 00:00:00 2001 From: UtkarshBhardwaj007 Date: Tue, 26 May 2026 01:04:03 +0100 Subject: [PATCH 1/5] feat(init): show registry username + account-in-use row MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `dot init`'s identity block now displays the user's playground.dot registry username (the handle they set via the playground-app profile) when one is claimed, falling back to the People-parachain identity name, then to the H160 — same precedence the playground-app uses in `displayNameForAccount`. Also adds an "account in use" row showing the derivation path (`playground.dot/0`) plus the full H160 that signs on their behalf. The new lookup (`lookupRegistryUsername(productH160)`) re-uses the shared `getConnection()` client and the existing `getReadOnlyRegistryContract` helper — no new deps. The call has a runtime optional-chain guard against contracts that don't expose `get_username` (e.g. the @w3s v7 deploy that's still live until the v_new cutover ships): in that case the lookup silently returns null and the display falls through to the People-parachain name. Once `dot contract install` picks up the new @w3s ABI, the call starts returning real values automatically — no CLI release needed. Errors are caught and logged; this is a display-time enhancement and never a hard failure path. --- .changeset/registry-username-display.md | 14 +++++++ src/commands/init/IdentityLines.tsx | 40 ++++++++++++++++---- src/utils/username.ts | 50 +++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 .changeset/registry-username-display.md diff --git a/.changeset/registry-username-display.md b/.changeset/registry-username-display.md new file mode 100644 index 0000000..07ef25f --- /dev/null +++ b/.changeset/registry-username-display.md @@ -0,0 +1,14 @@ +--- +"playground-cli": patch +--- + +`dot init` now shows the user's registry username (the handle set on the +playground.dot profile) when one has been claimed, falling back to the +People-parachain identity name and then to the H160 — same precedence as +the playground-app. Also surfaces an "account in use" row with the +derivation path + H160 so the user can verify the exact account that +signs on their behalf. + +No new dependencies. Read-only — silent fallback if the resolved +registry contract doesn't expose `getUsername` (e.g. running against an +older @w3s deploy). diff --git a/src/commands/init/IdentityLines.tsx b/src/commands/init/IdentityLines.tsx index 7b767bc..b52e759 100644 --- a/src/commands/init/IdentityLines.tsx +++ b/src/commands/init/IdentityLines.tsx @@ -16,7 +16,13 @@ import { useEffect, useState } from "react"; import { Row, Section } from "../../utils/ui/theme/index.js"; import type { SessionAddresses } from "../../utils/auth.js"; -import { formatUsernameLine, lookupUsername, type UsernameLookup } from "../../utils/username.js"; +import { + formatUsernameLine, + lookupUsername, + lookupRegistryUsername, + type UsernameLookup, +} from "../../utils/username.js"; +import { PLAYGROUND_PRODUCT_ID } from "../../config.js"; /** * Three-line identity block shown after a successful login: @@ -44,19 +50,36 @@ import { formatUsernameLine, lookupUsername, type UsernameLookup } from "../../u * identities fall through to the strings from `formatUsernameLine`. */ export function IdentityLines({ addresses }: { addresses: SessionAddresses }) { - const [username, setUsername] = useState({ kind: "loading" }); + const [walletUsername, setWalletUsername] = useState({ kind: "loading" }); + // null means "lookup completed, no registry username set"; undefined means + // "still loading". Display rule: prefer registry > People > fall back to + // the H160 — same precedence the playground-app uses in `displayNameForAccount`. + const [registryUsername, setRegistryUsername] = useState(undefined); useEffect(() => { let cancelled = false; lookupUsername(addresses.rootAddress).then((result) => { - if (!cancelled) setUsername(result); + if (!cancelled) setWalletUsername(result); + }); + lookupRegistryUsername(addresses.productH160 as `0x${string}`).then((result) => { + if (!cancelled) setRegistryUsername(result); }); return () => { cancelled = true; }; - }, [addresses.rootAddress]); + }, [addresses.rootAddress, addresses.productH160]); - const usernameTone = username.kind === "found" ? "default" : "muted"; + const usernameLine = registryUsername + ? registryUsername + : registryUsername === undefined + ? "(looking up...)" + : formatUsernameLine(walletUsername); + const usernameTone = registryUsername || walletUsername.kind === "found" ? "default" : "muted"; + const usernameSource = registryUsername + ? "playground" + : walletUsername.kind === "found" + ? "polkadot" + : null; return (
@@ -64,15 +87,16 @@ export function IdentityLines({ addresses }: { addresses: SessionAddresses }) { +
); } diff --git a/src/utils/username.ts b/src/utils/username.ts index dfe7489..e1e9ec0 100644 --- a/src/utils/username.ts +++ b/src/utils/username.ts @@ -46,6 +46,8 @@ import { createClient } from "polkadot-api"; import { getWsProvider } from "polkadot-api/ws"; import { getChainConfig } from "../config.js"; +import { getReadOnlyRegistryContract } from "./registry.js"; +import { getConnection } from "./connection.js"; // Cold-start WS connects to paseo-people-next-system-rpc on a slow conference // network can take a few seconds before metadata + the first query are ready. @@ -137,3 +139,51 @@ export async function lookupUsername(rootAccountSs58: string): Promise { + try { + const client = await getConnection(); + const registry = await getReadOnlyRegistryContract(client.raw.assetHub); + // The .query property is undefined on older registries → optional chain. + const getUsername = ( + registry as unknown as { + getUsername?: { + query?: (h160: `0x${string}`) => Promise<{ success: boolean; value: unknown }>; + }; + } + ).getUsername; + if (!getUsername?.query) return null; + const res = await getUsername.query(productH160); + if (!res.success) return null; + const value = res.value; + if (typeof value !== "string" || value === "") return null; + return value; + } catch { + return null; + } +} From a62029189a3d36b91a0449324b38bdfc4cea2af9 Mon Sep 17 00:00:00 2001 From: UtkarshBhardwaj007 Date: Tue, 26 May 2026 02:20:53 +0100 Subject: [PATCH 2/5] fix(deploy): match v8 registry publish signature The v8 @w3s/playground-registry contract added `modded_from`, `is_moddable`, and `is_dev_signer` to `publish(...)`, so every `dot deploy --playground` against Paseo Asset Hub Next was reverting with `Revive.ContractReverted` at the registry step. Wire the three new args through `publishToPlayground` and refresh `cdm.json` to the v8 ABI so the call encodes correctly. The runtime keeps resolving the live address from the on-chain meta-registry. --- .changeset/registry-username-display.md | 10 +- cdm.json | 310 +++++++++++++++++++++++- src/utils/deploy/playground.test.ts | 69 +++++- src/utils/deploy/playground.ts | 27 +++ src/utils/deploy/run.test.ts | 40 +++ src/utils/deploy/run.ts | 2 + src/utils/username.ts | 14 +- 7 files changed, 448 insertions(+), 24 deletions(-) diff --git a/.changeset/registry-username-display.md b/.changeset/registry-username-display.md index 07ef25f..efab6ab 100644 --- a/.changeset/registry-username-display.md +++ b/.changeset/registry-username-display.md @@ -4,11 +4,13 @@ `dot init` now shows the user's registry username (the handle set on the playground.dot profile) when one has been claimed, falling back to the -People-parachain identity name and then to the H160 — same precedence as +People-parachain identity name and then to the H160, same precedence as the playground-app. Also surfaces an "account in use" row with the derivation path + H160 so the user can verify the exact account that signs on their behalf. -No new dependencies. Read-only — silent fallback if the resolved -registry contract doesn't expose `getUsername` (e.g. running against an -older @w3s deploy). +`dot deploy --playground` now matches the v8 registry contract's 7-arg +`publish()` signature (adds `modded_from`, `is_moddable`, `is_dev_signer`), +which unblocks publishes against the freshly deployed v8 on Paseo Asset +Hub Next. `cdm.json` is refreshed to the v8 ABI; the runtime keeps +resolving the live contract address from the on-chain meta-registry. diff --git a/cdm.json b/cdm.json index 566536d..fdcfa59 100644 --- a/cdm.json +++ b/cdm.json @@ -14,8 +14,8 @@ "contracts": { "929b5c63e2cb5202": { "@w3s/playground-registry": { - "version": 7, - "address": "0xea3fb6C5cDA79FEEf5b2d42a9122fC1BAFaF647F", + "version": 8, + "address": "0x252461a7b650e98b75c11632c6b7fb4439EF5e32", "abi": [ { "type": "constructor", @@ -51,6 +51,18 @@ "type": "address" } ] + }, + { + "name": "modded_from", + "type": "string" + }, + { + "name": "is_moddable", + "type": "bool" + }, + { + "name": "is_dev_signer", + "type": "bool" } ], "outputs": [], @@ -104,6 +116,30 @@ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "star", + "inputs": [ + { + "name": "domain", + "type": "string" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "unstar", + "inputs": [ + { + "name": "domain", + "type": "string" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, { "type": "function", "name": "getContextId", @@ -256,6 +292,39 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "setBlacklisted", + "inputs": [ + { + "name": "accounts", + "type": "address[]" + }, + { + "name": "value", + "type": "bool" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "isBlacklisted", + "inputs": [ + { + "name": "account", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "setVisibility", @@ -473,6 +542,144 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "getPoints", + "inputs": [ + { + "name": "account", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getTopBuilders", + "inputs": [ + { + "name": "start", + "type": "uint32" + }, + { + "name": "count", + "type": "uint32" + } + ], + "outputs": [ + { + "name": "", + "type": "tuple[]", + "components": [ + { + "name": "account", + "type": "address" + }, + { + "name": "score", + "type": "uint128" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getModCount", + "inputs": [ + { + "name": "domain", + "type": "string" + } + ], + "outputs": [ + { + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getStarCount", + "inputs": [ + { + "name": "domain", + "type": "string" + } + ], + "outputs": [ + { + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "hasStarred", + "inputs": [ + { + "name": "voter", + "type": "address" + }, + { + "name": "domain", + "type": "string" + } + ], + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getPointBreakdown", + "inputs": [ + { + "name": "account", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "tuple", + "components": [ + { + "name": "launch_points", + "type": "uint128" + }, + { + "name": "mod_points", + "type": "uint128" + }, + { + "name": "star_points", + "type": "uint128" + }, + { + "name": "total", + "type": "uint128" + } + ] + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "setFrozen", @@ -527,6 +734,10 @@ { "name": "metadata_uri", "type": "string" + }, + { + "name": "is_moddable", + "type": "bool" } ], "outputs": [], @@ -559,6 +770,10 @@ { "name": "metadata_uri", "type": "string" + }, + { + "name": "is_moddable", + "type": "bool" } ] } @@ -577,6 +792,97 @@ ], "outputs": [], "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setUsername", + "inputs": [ + { + "name": "name", + "type": "string" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "clearUsername", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "getUsername", + "inputs": [ + { + "name": "account", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getUsernames", + "inputs": [ + { + "name": "accounts", + "type": "address[]" + } + ], + "outputs": [ + { + "name": "", + "type": "string[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getUsernameOwner", + "inputs": [ + { + "name": "name", + "type": "string" + } + ], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "isUsernameAvailable", + "inputs": [ + { + "name": "name", + "type": "string" + }, + { + "name": "prospective_caller", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "stateMutability": "view" } ], "metadataCid": "bafk2bzacedinnjvwctmltwri2xkmzhe26gt3ou7dimujsnwbinst3t33n2qgy" diff --git a/src/utils/deploy/playground.test.ts b/src/utils/deploy/playground.test.ts index 37a54e8..87085ce 100644 --- a/src/utils/deploy/playground.test.ts +++ b/src/utils/deploy/playground.test.ts @@ -326,10 +326,18 @@ describe("publishToPlayground", () => { expect.objectContaining({ __kind: "store" }), bulletinStorageSigner, ); - expect(publishTx).toHaveBeenCalledWith("my-app.dot", "bafymeta", 1, { - isSome: false, - value: "0x0000000000000000000000000000000000000000", - }); + expect(publishTx).toHaveBeenCalledWith( + "my-app.dot", + "bafymeta", + 1, + { + isSome: false, + value: "0x0000000000000000000000000000000000000000", + }, + "", + false, + false, + ); } finally { rmSync(dir, { recursive: true, force: true }); } @@ -388,10 +396,18 @@ describe("publishToPlayground", () => { cwd: "/definitely/not/a/repo", claimedOwnerH160: "0x1234567890abcdef1234567890abcdef12345678", }); - expect(publishTx).toHaveBeenCalledWith("claimed-app.dot", "bafymeta", 1, { - isSome: true, - value: "0x1234567890abcdef1234567890abcdef12345678", - }); + expect(publishTx).toHaveBeenCalledWith( + "claimed-app.dot", + "bafymeta", + 1, + { + isSome: true, + value: "0x1234567890abcdef1234567890abcdef12345678", + }, + "", + false, + false, + ); }); it("passes visibility=0 when isPrivate is true", async () => { @@ -402,10 +418,41 @@ describe("publishToPlayground", () => { cwd: "/definitely/not/a/repo", isPrivate: true, }); - expect(publishTx).toHaveBeenCalledWith("secret.dot", "bafymeta", 0, { - isSome: false, - value: "0x0000000000000000000000000000000000000000", + expect(publishTx).toHaveBeenCalledWith( + "secret.dot", + "bafymeta", + 0, + { + isSome: false, + value: "0x0000000000000000000000000000000000000000", + }, + "", + false, + false, + ); + }); + + it("forwards isModdable and isDevSigner to registry.publish", async () => { + await publishToPlayground({ + domain: "modded-by-dev", + publishSigner: fakeSigner, + repositoryUrl: "https://github.com/foo/bar", + cwd: "/definitely/not/a/repo", + isModdable: true, + isDevSigner: true, }); + expect(publishTx).toHaveBeenCalledWith( + "modded-by-dev.dot", + "bafymeta", + 1, + { + isSome: false, + value: "0x0000000000000000000000000000000000000000", + }, + "", + true, + true, + ); }); it("retries up to 3 times on registry publish failure", async () => { diff --git a/src/utils/deploy/playground.ts b/src/utils/deploy/playground.ts index 7ba2916..59e8c5d 100644 --- a/src/utils/deploy/playground.ts +++ b/src/utils/deploy/playground.ts @@ -83,6 +83,27 @@ export interface PublishToPlaygroundOptions { * to its owner in the playground. Defaults to public (visibility=1). */ isPrivate?: boolean; + /** + * Whether the published source is moddable (a public GitHub origin is + * recorded in metadata and listed in the `dot mod` picker). The contract + * records this bit so the playground-app filter doesn't need to fetch + * each metadata JSON to know. + */ + isModdable?: boolean; + /** + * Domain (`