From d07018b90de77fc21c90e6616a6da035a26cdb2d Mon Sep 17 00:00:00 2001 From: Valentin Fernandez Date: Fri, 7 Aug 2026 12:49:23 -0300 Subject: [PATCH 1/2] feat(ui): answer chain.getChainInfo from the host's configured chain set --- packages/ui/src/debug-wire-describe.ts | 1 + .../ui/src/host-callbacks/SupportedChains.ts | 47 +++++++++++++++++++ packages/ui/src/host-callbacks/handlers.ts | 6 ++- packages/ui/tests/debug-wire-describe.test.ts | 3 +- 4 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 packages/ui/src/host-callbacks/SupportedChains.ts diff --git a/packages/ui/src/debug-wire-describe.ts b/packages/ui/src/debug-wire-describe.ts index 60f7c53b..38bef2a0 100644 --- a/packages/ui/src/debug-wire-describe.ts +++ b/packages/ui/src/debug-wire-describe.ts @@ -68,6 +68,7 @@ const CHAIN_LINKAGE: readonly ChainLinkage[] = [ { wireTableKey: "CHAIN_GET_SPEC_GENESIS_HASH", stem: "SpecGenesisHash" }, { wireTableKey: "CHAIN_GET_SPEC_CHAIN_NAME", stem: "SpecChainName" }, { wireTableKey: "CHAIN_GET_SPEC_PROPERTIES", stem: "SpecProperties" }, + { wireTableKey: "CHAIN_GET_CHAIN_INFO", stem: "Info" }, { wireTableKey: "CHAIN_BROADCAST_TRANSACTION", stem: "TransactionBroadcast", diff --git a/packages/ui/src/host-callbacks/SupportedChains.ts b/packages/ui/src/host-callbacks/SupportedChains.ts new file mode 100644 index 00000000..36497add --- /dev/null +++ b/packages/ui/src/host-callbacks/SupportedChains.ts @@ -0,0 +1,47 @@ +// dot.li — TrUAPI supported-chains callback (RFC 0026) +// +// Maps the protocol's closed chain-role enum onto the active environment's +// config slots. The core answers `chain.getChainInfo` from this one set, +// resolving a requested identifier and mapping a miss to `NotSupported`. +// +// The set is filtered through the same per-backend predicate as +// `featureSupported`, so the two advertisements can never disagree: in +// RPC-gateway mode Bulletin is intentionally absent (its content is served +// via IPFS gateways; the core-owned Bulletin connection seam in `Chain.ts` +// is not a product-facing advertisement). + +import type { Features } from "@parity/truapi-host"; +import type { ChainIdentifier } from "@parity/truapi"; +import { hexToBytes } from "@parity/truapi/scale"; +import { getBackend } from "@dotli/config/mode"; +import { getActiveServicesConfig, getNetwork } from "@dotli/config/network"; +import { isChainSupported as isSmoldotChainSupported } from "@dotli/resolver/chains"; +import { isRpcChainSupported } from "@dotli/resolver/rpc-chain"; + +export function createSupportedChains(): Features["supportedChains"] { + return () => { + const cfg = getActiveServicesConfig(); + const slots: { identifier: ChainIdentifier; genesis: string }[] = [ + { identifier: "Relay", genesis: cfg.relay.genesis }, + { identifier: "AssetHub", genesis: cfg.assethub.genesis }, + { identifier: "People", genesis: cfg.people.genesis }, + { identifier: "Bulletin", genesis: cfg.bulletin.genesis }, + ]; + const isSupported = + getBackend() === "rpc-gateway" + ? isRpcChainSupported + : isSmoldotChainSupported; + // The environment id ("paseo-next-v2"), not a bare ecosystem ("paseo"): + // the field is informational and two environments of one ecosystem must + // stay distinguishable in product logs. + return Promise.resolve({ + network: getNetwork(), + chains: slots + .filter(({ genesis }) => isSupported(genesis)) + .map(({ identifier, genesis }) => ({ + identifier, + genesisHash: hexToBytes(genesis), + })), + }); + }; +} diff --git a/packages/ui/src/host-callbacks/handlers.ts b/packages/ui/src/host-callbacks/handlers.ts index 1a25ab20..1cd81f04 100644 --- a/packages/ui/src/host-callbacks/handlers.ts +++ b/packages/ui/src/host-callbacks/handlers.ts @@ -19,6 +19,7 @@ import { import { createPreimageAdapters } from "./Preimage"; import { createChainConnect } from "./Chain"; import { createFeatureSupported } from "./FeatureSupported"; +import { createSupportedChains } from "./SupportedChains"; import { createThemeSubscribe } from "./Theme"; import { createAuthStateChanged } from "./AuthState"; import { createSessionStoreAdapters } from "./SessionStore"; @@ -62,7 +63,10 @@ export function createHostCallbacks( blockingModalScope, promptLimiter, ), - features: { featureSupported: createFeatureSupported() }, + features: { + featureSupported: createFeatureSupported(), + supportedChains: createSupportedChains(), + }, productStorage: { read: createLocalStorageRead(), write: createLocalStorageWrite(), diff --git a/packages/ui/tests/debug-wire-describe.test.ts b/packages/ui/tests/debug-wire-describe.test.ts index 222fd14e..828dbedd 100644 --- a/packages/ui/tests/debug-wire-describe.test.ts +++ b/packages/ui/tests/debug-wire-describe.test.ts @@ -330,7 +330,7 @@ describe("chain-family drift guard", () => { // When const derived = [...derivedTagRoots, ...controlOnlyTags]; - // Then: pins the exact current 15-entry legacy vocabulary as a literal + // Then: pins the exact current 16-entry legacy vocabulary as a literal // snapshot, so it can't drift silently. expect(derived).toEqual([ "remote_chain_head_follow", @@ -344,6 +344,7 @@ describe("chain-family drift guard", () => { "remote_chain_spec_genesis_hash", "remote_chain_spec_chain_name", "remote_chain_spec_properties", + "remote_chain_info", "remote_chain_transaction_broadcast", "remote_chain_transaction_stop", "remote_chain_head_follow_stop", From acdf095c53670535729aabd2f8338b01c91a2d07 Mon Sep 17 00:00:00 2001 From: Valentin Fernandez Date: Tue, 11 Aug 2026 12:34:48 -0300 Subject: [PATCH 2/2] bump truapi to 0.8.0 and migrate to hex-encoded bytes --- bun.lock | 10 +++++----- package.json | 2 +- packages/ui/package.json | 4 ++-- .../ui/src/host-callbacks/SessionStore.ts | 14 +++++++++++--- .../ui/src/host-callbacks/SupportedChains.ts | 4 ++-- .../ui/src/host-callbacks/UserConfirmation.ts | 2 +- packages/ui/tests/legacy-host-bridge.test.ts | 4 ++-- packages/ui/tests/session-store.test.ts | 19 +++++++++++-------- packages/ui/tests/user-confirmation.test.ts | 10 +++++----- 9 files changed, 40 insertions(+), 29 deletions(-) diff --git a/bun.lock b/bun.lock index 1a938eca..57b0c84d 100644 --- a/bun.lock +++ b/bun.lock @@ -293,8 +293,8 @@ "@dotli/storage": "workspace:*", "@dotli/truapi-debug": "workspace:*", "@noble/hashes": "^2.2.0", - "@parity/truapi": "0.7.0", - "@parity/truapi-host": "0.4.0", + "@parity/truapi": "0.8.0", + "@parity/truapi-host": "0.5.0", "@polkadot-api/json-rpc-provider": "^0.2.0", "@scure/base": "^2.2.0", "neverthrow": "^8.2.0", @@ -321,7 +321,7 @@ }, }, "overrides": { - "@parity/truapi": "0.7.0", + "@parity/truapi": "0.8.0", "brace-expansion": "^5.0.9", "esbuild": "^0.28.1", "fast-uri": "3.1.5", @@ -661,9 +661,9 @@ "@oxc-project/types": ["@oxc-project/types@0.133.0", "", {}, "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA=="], - "@parity/truapi": ["@parity/truapi@0.7.0", "", { "dependencies": { "@noble/hashes": "^2.2.0", "neverthrow": "^8.2.0", "scale-ts": "^1.6.1" } }, "sha512-xBGZP7/l74I0WGUnHgoFNYah+20I3JgykJ5pNUVsw3mQzNoCHuZSVJW6sUq7fzLvRF4wM2glPTPxUOiFKgfE9A=="], + "@parity/truapi": ["@parity/truapi@0.8.0", "", { "dependencies": { "@noble/hashes": "^2.2.0", "neverthrow": "^8.2.0", "scale-ts": "^1.6.1" } }, "sha512-xeZAgnTcFZeTTTAH/CGpJdISqC/gpSxxBWQQEzQtKs8ndg2eTo1oeZh/ujhfrc9CrJmbK/2+oYpI9TYLH/Z8Gg=="], - "@parity/truapi-host": ["@parity/truapi-host@0.4.0", "", { "dependencies": { "@parity/truapi": "^0.7.0" } }, "sha512-C4FToZ+KCaSpG3nKpB2qAulp2UTmKg2PzJkjKS8Vuf1v1IbK4GJ58VZ/Qnmo2pgkOFfUeRlf2l0i5LUacorwEQ=="], + "@parity/truapi-host": ["@parity/truapi-host@0.5.0", "", { "dependencies": { "@parity/truapi": "^0.8.0" } }, "sha512-JI5X8Vk4r8SvklSLd54xBnAb79bKRibnPMiLrYAxUSZi9j/52KYQJ47r74wr7vwNE/mdRiAePcXzoL5/5UaQEw=="], "@playwright/test": ["@playwright/test@1.60.0", "", { "dependencies": { "playwright": "1.60.0" }, "bin": { "playwright": "cli.js" } }, "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag=="], diff --git a/package.json b/package.json index 18407a21..5371999d 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "smoldot": "3.3.2" }, "overrides": { - "@parity/truapi": "0.7.0", + "@parity/truapi": "0.8.0", "fast-uri": "3.1.5", "smoldot": "3.3.2", "esbuild": "^0.28.1", diff --git a/packages/ui/package.json b/packages/ui/package.json index 426ed39b..8b26d6c7 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -33,8 +33,8 @@ "@dotli/storage": "workspace:*", "@dotli/truapi-debug": "workspace:*", "@noble/hashes": "^2.2.0", - "@parity/truapi": "0.7.0", - "@parity/truapi-host": "0.4.0", + "@parity/truapi": "0.8.0", + "@parity/truapi-host": "0.5.0", "@polkadot-api/json-rpc-provider": "^0.2.0", "@scure/base": "^2.2.0", "neverthrow": "^8.2.0", diff --git a/packages/ui/src/host-callbacks/SessionStore.ts b/packages/ui/src/host-callbacks/SessionStore.ts index 4277a450..33c26814 100644 --- a/packages/ui/src/host-callbacks/SessionStore.ts +++ b/packages/ui/src/host-callbacks/SessionStore.ts @@ -44,9 +44,9 @@ export function toSessionUiState(info: SessionUiInfo): TruapiSessionUiState { const primaryUsername = info.fullUsername ?? info.liteUsername; return { connected: true, - publicKey: bytesToHex(info.publicKey), + publicKey: info.publicKey, ...(info.identityAccountId !== undefined - ? { identityAccountId: bytesToHex(info.identityAccountId) } + ? { identityAccountId: info.identityAccountId } : {}), ...(info.liteUsername !== undefined ? { liteUsername: info.liteUsername } @@ -244,6 +244,10 @@ function coreLocalStorageKey(key: CoreStorageKey): string { return `${CORE_LOCAL_STORAGE_PREFIX}auto-signing:${hexNoPrefix( encodeCoreStorageKey(key), )}`; + // Wallet-bound capabilities for the active pairing: one slot, unlike the + // legacy per-product `AutoSigningKey` above. + case "AutoSigningKeys": + return `${CORE_LOCAL_STORAGE_PREFIX}auto-signing-keys`; case "LastProcessedPairingStatement": return `${CORE_LOCAL_STORAGE_PREFIX}last-processed-pairing-statement`; case "AuthSession": @@ -252,7 +256,11 @@ function coreLocalStorageKey(key: CoreStorageKey): string { } function storesSecretMaterial(key: CoreStorageKey): boolean { - return key.tag === "AllowanceKeys" || key.tag === "AutoSigningKey"; + return ( + key.tag === "AllowanceKeys" || + key.tag === "AutoSigningKey" || + key.tag === "AutoSigningKeys" + ); } async function encodeCoreStorageValue( diff --git a/packages/ui/src/host-callbacks/SupportedChains.ts b/packages/ui/src/host-callbacks/SupportedChains.ts index 36497add..ec80c245 100644 --- a/packages/ui/src/host-callbacks/SupportedChains.ts +++ b/packages/ui/src/host-callbacks/SupportedChains.ts @@ -12,7 +12,7 @@ import type { Features } from "@parity/truapi-host"; import type { ChainIdentifier } from "@parity/truapi"; -import { hexToBytes } from "@parity/truapi/scale"; +import { toHexString } from "@parity/truapi/scale"; import { getBackend } from "@dotli/config/mode"; import { getActiveServicesConfig, getNetwork } from "@dotli/config/network"; import { isChainSupported as isSmoldotChainSupported } from "@dotli/resolver/chains"; @@ -40,7 +40,7 @@ export function createSupportedChains(): Features["supportedChains"] { .filter(({ genesis }) => isSupported(genesis)) .map(({ identifier, genesis }) => ({ identifier, - genesisHash: hexToBytes(genesis), + genesisHash: toHexString(genesis), })), }); }; diff --git a/packages/ui/src/host-callbacks/UserConfirmation.ts b/packages/ui/src/host-callbacks/UserConfirmation.ts index 82c4eca7..1dd7d46e 100644 --- a/packages/ui/src/host-callbacks/UserConfirmation.ts +++ b/packages/ui/src/host-callbacks/UserConfirmation.ts @@ -194,7 +194,7 @@ function formatRawPayload(payload: RawPayload): string { } function formatDerivationIndex(index: DerivationIndex): string { - return index.tag === "Left" ? String(index.value) : index.value; + return index.tag === "Index" ? String(index.value) : index.value; } function formatProductAccount(account: ProductAccountId): string { diff --git a/packages/ui/tests/legacy-host-bridge.test.ts b/packages/ui/tests/legacy-host-bridge.test.ts index 142f8e87..687642d8 100644 --- a/packages/ui/tests/legacy-host-bridge.test.ts +++ b/packages/ui/tests/legacy-host-bridge.test.ts @@ -185,7 +185,7 @@ describe("createLegacyNovaChainHeadProvider", () => { value: { productAccountId: { dotNsIdentifier: "localhost:3000.dot", - derivationIndex: { tag: "Left", value: 0 }, + derivationIndex: { tag: "Index", value: 0 }, }, }, }), @@ -214,7 +214,7 @@ describe("createLegacyNovaChainHeadProvider", () => { value: { productAccountId: { dotNsIdentifier: "other-product.dot", - derivationIndex: { tag: "Left", value: 0 }, + derivationIndex: { tag: "Index", value: 0 }, }, }, }), diff --git a/packages/ui/tests/session-store.test.ts b/packages/ui/tests/session-store.test.ts index eb08a4d2..58c21799 100644 --- a/packages/ui/tests/session-store.test.ts +++ b/packages/ui/tests/session-store.test.ts @@ -54,22 +54,25 @@ async function flushMicrotasks(): Promise { await Promise.resolve(); } +// The core reports these as `Bytes32` (hex), so the UI state carries them +// through unchanged rather than encoding them. +const SESSION_PUBLIC_KEY = + "0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; +const SESSION_IDENTITY_ACCOUNT_ID = + "0xa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"; + function connectedSessionUiInfo() { return { - publicKey: new Uint8Array(Array.from({ length: 32 }, (_, i) => i)), - identityAccountId: new Uint8Array( - Array.from({ length: 32 }, (_, i) => 0xa0 + i), - ), + publicKey: SESSION_PUBLIC_KEY, + identityAccountId: SESSION_IDENTITY_ACCOUNT_ID, liteUsername: "pgherveou.04", }; } const CONNECTED_DETAIL = { connected: true, - publicKey: - "0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", - identityAccountId: - "0xa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf", + publicKey: SESSION_PUBLIC_KEY, + identityAccountId: SESSION_IDENTITY_ACCOUNT_ID, liteUsername: "pgherveou.04", primaryUsername: "pgherveou.04", }; diff --git a/packages/ui/tests/user-confirmation.test.ts b/packages/ui/tests/user-confirmation.test.ts index 4b9f8a6a..44926f00 100644 --- a/packages/ui/tests/user-confirmation.test.ts +++ b/packages/ui/tests/user-confirmation.test.ts @@ -135,7 +135,7 @@ describe("user confirmation modal", () => { value: { account: { dotNsIdentifier: "truapi-playground.dot", - derivationIndex: { tag: "Left", value: 2 }, + derivationIndex: { tag: "Index", value: 2 }, }, payload: { blockHash: @@ -228,7 +228,7 @@ describe("user confirmation modal", () => { request: { account: { dotNsIdentifier: "other-product.dot", - derivationIndex: { tag: "Left", value: 4 }, + derivationIndex: { tag: "Index", value: 4 }, }, transcriptLabel: "0x706f703a61697264726f70", items: [ @@ -271,7 +271,7 @@ describe("user confirmation modal", () => { value: { signer: { dotNsIdentifier: "truapi-playground.dot", - derivationIndex: { tag: "Left", value: 3 }, + derivationIndex: { tag: "Index", value: 3 }, }, genesisHash: "0xbf0488dbe9daa1de1c08c5f743e26fdc2a4ecd74cf87dd1b4b1eeb99ae4ef19f", @@ -347,7 +347,7 @@ describe("user confirmation modal", () => { callingProductId: "truapi-playground.dot", context: { productId: "truapix-playground.dot", - suffix: { tag: "Left", value: 0 }, + suffix: { tag: "Index", value: 0 }, }, ringLocation: { chainId: @@ -397,7 +397,7 @@ describe("user confirmation modal", () => { callingProductId: "truapi-playground.dot", context: { productId: "truapix-playground.dot", - suffix: { tag: "Left", value: 0 }, + suffix: { tag: "Index", value: 0 }, }, ringLocation: { chainId: