@parity/product-sdk-signer 0.6.4 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +71 -6
- package/dist/index.js +72 -2
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/errors.ts +12 -1
- package/src/providers/host.ts +307 -16
- package/src/signer-manager.ts +86 -0
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/types.ts","../src/providers/dev.ts","../src/sleep.ts","../src/retry.ts","../src/providers/host.ts","../src/signer-manager.ts"],"names":["log","createLogger","accounts"],"mappings":"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","file":"index.js","sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport type { ProviderType } from \"./types.js\";\n\n/** Base class for all signer errors. Use `instanceof SignerError` to catch any signer-related error. */\nexport class SignerError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"SignerError\";\n }\n}\n\n/** The Host API is not available (product-sdk not installed or not inside a container). */\nexport class HostUnavailableError extends SignerError {\n constructor(message = \"Host API is not available\") {\n super(message);\n this.name = \"HostUnavailableError\";\n }\n}\n\n/** The host rejected the account or signing request. */\nexport class HostRejectedError extends SignerError {\n constructor(message = \"Host rejected the request\") {\n super(message);\n this.name = \"HostRejectedError\";\n }\n}\n\n/** The host connection was lost. */\nexport class HostDisconnectedError extends SignerError {\n constructor(message = \"Host connection lost\") {\n super(message);\n this.name = \"HostDisconnectedError\";\n }\n}\n\n/** A signing operation failed. */\nexport class SigningFailedError extends SignerError {\n constructor(cause: unknown, message?: string) {\n super(\n message ?? `Signing failed: ${cause instanceof Error ? cause.message : String(cause)}`,\n { cause },\n );\n this.name = \"SigningFailedError\";\n }\n}\n\n/** No accounts available from the provider. */\nexport class NoAccountsError extends SignerError {\n readonly provider: ProviderType;\n\n constructor(provider: ProviderType, message?: string) {\n super(message ?? `No accounts available from ${provider} provider`);\n this.name = \"NoAccountsError\";\n this.provider = provider;\n }\n}\n\n/** An operation timed out. */\nexport class TimeoutError extends SignerError {\n readonly operation: string;\n readonly ms: number;\n\n constructor(operation: string, ms: number) {\n super(`Operation \"${operation}\" timed out after ${ms}ms`);\n this.name = \"TimeoutError\";\n this.operation = operation;\n this.ms = ms;\n }\n}\n\n/** An account was not found by address. */\nexport class AccountNotFoundError extends SignerError {\n readonly address: string;\n\n constructor(address: string) {\n super(`Account not found: ${address}`);\n this.name = \"AccountNotFoundError\";\n this.address = address;\n }\n}\n\n/** The SignerManager has been destroyed and is no longer usable. */\nexport class DestroyedError extends SignerError {\n constructor() {\n super(\"SignerManager has been destroyed\");\n this.name = \"DestroyedError\";\n }\n}\n\n// ── Type guards ──────────────────────────────────────────────────────\n\n/** Check if a SignerError is a host-related error. */\nexport function isHostError(\n e: SignerError,\n): e is HostUnavailableError | HostRejectedError | HostDisconnectedError {\n return (\n e instanceof HostUnavailableError ||\n e instanceof HostRejectedError ||\n e instanceof HostDisconnectedError\n );\n}\n\nif (import.meta.vitest) {\n const { test, expect, describe } = import.meta.vitest;\n\n describe(\"error classes\", () => {\n test(\"SignerError is the base class\", () => {\n const e = new HostUnavailableError();\n expect(e).toBeInstanceOf(SignerError);\n expect(e).toBeInstanceOf(Error);\n });\n\n test(\"HostUnavailableError with default message\", () => {\n const e = new HostUnavailableError();\n expect(e.name).toBe(\"HostUnavailableError\");\n expect(e.message).toBe(\"Host API is not available\");\n });\n\n test(\"HostUnavailableError with custom message\", () => {\n const e = new HostUnavailableError(\"custom\");\n expect(e.message).toBe(\"custom\");\n });\n\n test(\"HostRejectedError\", () => {\n const e = new HostRejectedError();\n expect(e).toBeInstanceOf(SignerError);\n expect(e.message).toContain(\"rejected\");\n });\n\n test(\"HostDisconnectedError\", () => {\n const e = new HostDisconnectedError();\n expect(e).toBeInstanceOf(SignerError);\n expect(e.message).toContain(\"lost\");\n });\n\n test(\"SigningFailedError with Error cause\", () => {\n const cause = new Error(\"bad signature\");\n const e = new SigningFailedError(cause);\n expect(e).toBeInstanceOf(SignerError);\n expect(e.cause).toBe(cause);\n expect(e.message).toContain(\"bad signature\");\n });\n\n test(\"SigningFailedError with string cause\", () => {\n const e = new SigningFailedError(\"oops\");\n expect(e.message).toContain(\"oops\");\n });\n\n test(\"SigningFailedError with custom message\", () => {\n const e = new SigningFailedError(\"oops\", \"custom msg\");\n expect(e.message).toBe(\"custom msg\");\n });\n\n test(\"NoAccountsError\", () => {\n const e = new NoAccountsError(\"host\");\n expect(e).toBeInstanceOf(SignerError);\n expect(e.provider).toBe(\"host\");\n expect(e.message).toContain(\"host\");\n });\n\n test(\"NoAccountsError with custom message\", () => {\n const e = new NoAccountsError(\"dev\", \"none found\");\n expect(e.message).toBe(\"none found\");\n });\n\n test(\"TimeoutError\", () => {\n const e = new TimeoutError(\"connect\", 5000);\n expect(e).toBeInstanceOf(SignerError);\n expect(e.operation).toBe(\"connect\");\n expect(e.ms).toBe(5000);\n expect(e.message).toContain(\"5000\");\n });\n\n test(\"AccountNotFoundError\", () => {\n const e = new AccountNotFoundError(\"5GrwvaEF...\");\n expect(e).toBeInstanceOf(SignerError);\n expect(e.address).toBe(\"5GrwvaEF...\");\n });\n\n test(\"DestroyedError\", () => {\n const e = new DestroyedError();\n expect(e).toBeInstanceOf(SignerError);\n expect(e.message).toContain(\"destroyed\");\n });\n\n test(\"all errors have stack traces\", () => {\n const e = new HostUnavailableError();\n expect(e.stack).toBeDefined();\n expect(e.stack).toContain(\"HostUnavailableError\");\n });\n });\n\n describe(\"type guards\", () => {\n test(\"isHostError returns true for host errors\", () => {\n expect(isHostError(new HostUnavailableError())).toBe(true);\n expect(isHostError(new HostRejectedError())).toBe(true);\n expect(isHostError(new HostDisconnectedError())).toBe(true);\n });\n\n test(\"isHostError returns false for non-host errors\", () => {\n expect(isHostError(new SigningFailedError(\"x\"))).toBe(false);\n expect(isHostError(new NoAccountsError(\"dev\"))).toBe(false);\n expect(isHostError(new TimeoutError(\"op\", 100))).toBe(false);\n expect(isHostError(new AccountNotFoundError(\"x\"))).toBe(false);\n expect(isHostError(new DestroyedError())).toBe(false);\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport type { PolkadotSigner } from \"polkadot-api\";\n\nimport type { SS58String } from \"@parity/product-sdk-address\";\nimport type { AllocatableResource, AllocationOutcome } from \"@parity/product-sdk-host\";\n\nimport type { SignerError } from \"./errors.js\";\n\n/** Connection status for a signer provider. */\nexport type ConnectionStatus = \"disconnected\" | \"connecting\" | \"connected\";\n\n/**\n * Identifies the source of an account.\n *\n * - `\"host\"`: Host API provider (Polkadot Desktop / Mobile) — the primary provider\n * - `\"dev\"`: Development accounts (Alice, Bob, etc.) — for testing only\n */\nexport type ProviderType = \"host\" | \"dev\";\n\n/** A signing-capable account from any provider. */\nexport interface SignerAccount {\n /** SS58 address (generic prefix 42 by default). */\n address: SS58String;\n /**\n * H160 EVM address derived from the public key.\n *\n * For native Substrate accounts: keccak256(publicKey), last 20 bytes.\n * For EVM-derived accounts: strips the 0xEE padding.\n * Used for pallet-revive / Asset Hub EVM contract interactions.\n */\n h160Address: `0x${string}`;\n /** Raw public key (32 bytes). May be sr25519, ed25519, or ecdsa depending on the provider. */\n publicKey: Uint8Array;\n /** Human-readable name if available from the provider. */\n name: string | null;\n /** Which provider supplied this account. */\n source: ProviderType;\n /** Get the PolkadotSigner for this account. */\n getSigner(): PolkadotSigner;\n}\n\n/** Full state snapshot emitted to subscribers. */\nexport interface SignerState {\n /** Current connection status. */\n status: ConnectionStatus;\n /** All available accounts across all connected providers. */\n accounts: readonly SignerAccount[];\n /** Currently selected account (null if none selected). */\n selectedAccount: SignerAccount | null;\n /** Which provider is active (null if disconnected). */\n activeProvider: ProviderType | null;\n /** Last error (null if no error). */\n error: SignerError | null;\n}\n\n/** Lightweight Result type for operations that can fail expectedly. */\nexport type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };\n\n/** Create a successful Result. */\nexport function ok<T>(value: T): Result<T, never> {\n return { ok: true, value };\n}\n\n/** Create a failed Result. */\nexport function err<E>(error: E): Result<never, E> {\n return { ok: false, error };\n}\n\n/** Factory function that creates a SignerProvider for a given type. */\nexport type ProviderFactory = (type: ProviderType) => import(\"./providers/types.js\").SignerProvider;\n\n/**\n * Adapter for persisting the selected account address across sessions.\n *\n * `globalThis.localStorage` satisfies this interface. Pass a custom\n * implementation for container environments (e.g., hostLocalStorage)\n * or for testing.\n */\nexport interface AccountPersistence {\n getItem(key: string): string | null | Promise<string | null>;\n setItem(key: string, value: string): void | Promise<void>;\n removeItem(key: string): void | Promise<void>;\n}\n\n/** Options for SignerManager construction. */\nexport interface SignerManagerOptions {\n /** SS58 prefix for address encoding. Default: 42 */\n ss58Prefix?: number;\n /**\n * Maximum time in ms to wait for the Host API.\n * Applied as an AbortSignal timeout on the host provider connection.\n * Default: 10_000\n */\n hostTimeout?: number;\n /** Maximum retry attempts for provider connection. Default: 3 */\n maxRetries?: number;\n /** Custom provider factory. Override to inject test doubles or custom providers. */\n createProvider?: ProviderFactory;\n /**\n * App name used for storage key namespacing. Default: \"product-sdk\"\n * The selected account is persisted under `product-sdk:signer:{dappName}:selectedAccount`.\n */\n dappName?: string;\n /**\n * Storage adapter for persisting selected account.\n * Uses host localStorage when inside a container.\n * Set to `null` to disable persistence entirely.\n */\n persistence?: AccountPersistence | null;\n /**\n * Callback fired exactly when the manager transitions to `connected`\n * with a selected account — not on subsequent state mutations while\n * still connected. Fires again after auto-reconnect, so a fresh host\n * session re-runs the callback.\n *\n * Common use: request product resource allocations once per session.\n * The `ctx` exposes a pre-bound `requestResourceAllocation` helper\n * plus an `AbortSignal` that fires if the user disconnects or\n * destroys the manager mid-flight.\n *\n * `requestResourceAllocation` throws on failure (matches the\n * `@parity/product-sdk-host` export of the same name); errors thrown\n * from `onConnect` are logged but do not affect the connected state —\n * the next reconnect retries.\n *\n * @example\n * ```ts\n * new SignerManager({\n * onConnect: async (_account, { requestResourceAllocation, signal }) => {\n * try {\n * const outcomes = await requestResourceAllocation([\n * { tag: \"AutoSigning\", value: undefined },\n * ]);\n * if (signal.aborted) return;\n * if (outcomes.some((o) => o.tag !== \"Allocated\")) {\n * logWarning(\"partial permissions\", outcomes);\n * }\n * } catch (cause) {\n * logWarning(\"resource allocation failed\", cause);\n * }\n * },\n * });\n * ```\n */\n onConnect?: OnConnect;\n}\n\n/** Context passed to the `onConnect` callback. */\nexport interface ConnectContext {\n /**\n * Aborted when the manager disconnects or is destroyed while the\n * callback is still running. Pass through to `fetch` / cancellation\n * primitives so mid-flight work stops promptly.\n */\n signal: AbortSignal;\n /**\n * Request a batch of host resource allocations. Bound shorthand for\n * `requestResourceAllocation` from `@parity/product-sdk-host` —\n * throws on failure, returns the unwrapped outcomes on success.\n */\n requestResourceAllocation: (resources: AllocatableResource[]) => Promise<AllocationOutcome[]>;\n}\n\n/** Callback signature for {@link SignerManagerOptions.onConnect}. */\nexport type OnConnect = (account: SignerAccount, ctx: ConnectContext) => void | Promise<void>;\n\nif (import.meta.vitest) {\n const { test, expect, describe } = import.meta.vitest;\n\n describe(\"ok\", () => {\n test(\"produces ok result with value\", () => {\n const result = ok(42);\n expect(result.ok).toBe(true);\n expect(result).toEqual({ ok: true, value: 42 });\n });\n\n test(\"works with complex values\", () => {\n const result = ok({ name: \"Alice\", age: 30 });\n expect(result.ok).toBe(true);\n if (result.ok) {\n expect(result.value.name).toBe(\"Alice\");\n }\n });\n\n test(\"works with null value\", () => {\n const result = ok(null);\n expect(result).toEqual({ ok: true, value: null });\n });\n\n test(\"works with undefined value\", () => {\n const result = ok(undefined);\n expect(result).toEqual({ ok: true, value: undefined });\n });\n });\n\n describe(\"err\", () => {\n test(\"produces error result\", () => {\n const result = err(\"something went wrong\");\n expect(result.ok).toBe(false);\n expect(result).toEqual({ ok: false, error: \"something went wrong\" });\n });\n\n test(\"works with typed error objects\", () => {\n const error = { type: \"HOST_UNAVAILABLE\" as const, message: \"no host\" };\n const result = err(error);\n expect(result.ok).toBe(false);\n if (!result.ok) {\n expect(result.error.type).toBe(\"HOST_UNAVAILABLE\");\n }\n });\n });\n\n describe(\"Result type narrowing\", () => {\n test(\"ok narrows to value access\", () => {\n const result: Result<number, string> = ok(42);\n if (result.ok) {\n const value: number = result.value;\n expect(value).toBe(42);\n } else {\n expect.unreachable(\"should be ok\");\n }\n });\n\n test(\"err narrows to error access\", () => {\n const result: Result<number, string> = err(\"fail\");\n if (!result.ok) {\n const error: string = result.error;\n expect(error).toBe(\"fail\");\n } else {\n expect.unreachable(\"should be err\");\n }\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport { seedToAccount } from \"@parity/product-sdk-keys\";\nimport { createLogger } from \"@parity/product-sdk-logger\";\n\nimport type { SignerError } from \"../errors.js\";\nimport type { Result, SignerAccount } from \"../types.js\";\nimport { ok } from \"../types.js\";\nimport type { SignerProvider, Unsubscribe } from \"./types.js\";\n\nconst log = createLogger(\"signer:dev\");\n\n/** The well-known Substrate development mnemonic phrase. */\nconst DEV_PHRASE = \"bottom drive obey lake curtain smoke basket hold race lonely fit walk\";\n\n/** Standard Substrate dev account names. */\nconst DEFAULT_DEV_NAMES = [\"Alice\", \"Bob\", \"Charlie\", \"Dave\", \"Eve\", \"Ferdie\"] as const;\n\n/** A well-known Substrate development account name (Alice, Bob, …) used to derive deterministic dev accounts from the standard Substrate dev mnemonic. */\nexport type DevAccountName = (typeof DEFAULT_DEV_NAMES)[number];\n\n/** Supported key types for dev account derivation. */\nexport type DevKeyType = \"sr25519\" | \"ed25519\";\n\n/** Options for the dev account provider. */\nexport interface DevProviderOptions {\n /** Which dev accounts to create. Default: all 6 standard accounts. */\n names?: readonly string[];\n /** Custom mnemonic phrase instead of DEV_PHRASE. */\n mnemonic?: string;\n /** SS58 prefix for address encoding. Default: 42 */\n ss58Prefix?: number;\n /** Key type for account derivation. Default: \"sr25519\" */\n keyType?: DevKeyType;\n}\n\n/**\n * Provider for Substrate development accounts.\n *\n * Uses the well-known DEV_PHRASE with hard derivation paths (`//Alice`, `//Bob`, etc.)\n * to create deterministic accounts for local development and testing.\n */\nexport class DevProvider implements SignerProvider {\n readonly type = \"dev\" as const;\n private readonly names: readonly string[];\n private readonly mnemonic: string;\n private readonly ss58Prefix: number;\n private readonly keyType: DevKeyType;\n\n constructor(options?: DevProviderOptions) {\n this.names = options?.names ?? DEFAULT_DEV_NAMES;\n this.mnemonic = options?.mnemonic ?? DEV_PHRASE;\n this.ss58Prefix = options?.ss58Prefix ?? 42;\n this.keyType = options?.keyType ?? \"sr25519\";\n }\n\n async connect(): Promise<Result<SignerAccount[], SignerError>> {\n log.debug(\"creating dev accounts\", { names: this.names, keyType: this.keyType });\n\n const accounts: SignerAccount[] = this.names.map((name) => {\n const derived = seedToAccount(\n this.mnemonic,\n `//${name}`,\n this.ss58Prefix,\n this.keyType,\n );\n\n return {\n address: derived.ss58Address,\n h160Address: derived.h160Address,\n publicKey: derived.publicKey,\n name,\n source: \"dev\" as const,\n getSigner: () => derived.signer,\n };\n });\n\n log.info(\"dev accounts ready\", { count: accounts.length });\n return ok(accounts);\n }\n\n disconnect(): void {\n // Dev accounts are stateless — nothing to clean up.\n }\n\n onStatusChange(\n _callback: (status: \"disconnected\" | \"connecting\" | \"connected\") => void,\n ): Unsubscribe {\n // Dev accounts are always \"connected\" — no status changes to emit.\n return () => {};\n }\n\n onAccountsChange(_callback: (accounts: SignerAccount[]) => void): Unsubscribe {\n // Dev accounts are static — no account changes to emit.\n return () => {};\n }\n}\n\nif (import.meta.vitest) {\n const { test, expect, describe } = import.meta.vitest;\n\n // Well-known Alice address on generic substrate (prefix 42)\n const ALICE_ADDRESS = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\";\n\n describe(\"DevProvider\", () => {\n test(\"connect returns 6 accounts by default\", async () => {\n const provider = new DevProvider();\n const result = await provider.connect();\n expect(result.ok).toBe(true);\n if (result.ok) {\n expect(result.value).toHaveLength(6);\n expect(result.value.map((a) => a.name)).toEqual([\n \"Alice\",\n \"Bob\",\n \"Charlie\",\n \"Dave\",\n \"Eve\",\n \"Ferdie\",\n ]);\n }\n });\n\n test(\"all accounts have source 'dev'\", async () => {\n const provider = new DevProvider();\n const result = await provider.connect();\n if (result.ok) {\n for (const account of result.value) {\n expect(account.source).toBe(\"dev\");\n }\n }\n });\n\n test(\"Alice has well-known address\", async () => {\n const provider = new DevProvider();\n const result = await provider.connect();\n if (result.ok) {\n expect(result.value[0].address).toBe(ALICE_ADDRESS);\n }\n });\n\n test(\"addresses are deterministic\", async () => {\n const a = new DevProvider();\n const b = new DevProvider();\n const ra = await a.connect();\n const rb = await b.connect();\n if (ra.ok && rb.ok) {\n expect(ra.value.map((x) => x.address)).toEqual(rb.value.map((x) => x.address));\n }\n });\n\n test(\"each account has 32-byte publicKey\", async () => {\n const provider = new DevProvider();\n const result = await provider.connect();\n if (result.ok) {\n for (const account of result.value) {\n expect(account.publicKey).toBeInstanceOf(Uint8Array);\n expect(account.publicKey.length).toBe(32);\n }\n }\n });\n\n test(\"getSigner returns signer with matching publicKey\", async () => {\n const provider = new DevProvider();\n const result = await provider.connect();\n if (result.ok) {\n for (const account of result.value) {\n const signer = account.getSigner();\n expect(signer.publicKey).toEqual(account.publicKey);\n }\n }\n });\n\n test(\"custom names subset\", async () => {\n const provider = new DevProvider({ names: [\"Alice\", \"Bob\"] });\n const result = await provider.connect();\n if (result.ok) {\n expect(result.value).toHaveLength(2);\n expect(result.value.map((a) => a.name)).toEqual([\"Alice\", \"Bob\"]);\n }\n });\n\n test(\"custom mnemonic produces different addresses\", async () => {\n const customMnemonic =\n \"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about\";\n const defaultProvider = new DevProvider();\n const customProvider = new DevProvider({ mnemonic: customMnemonic });\n const defResult = await defaultProvider.connect();\n const cusResult = await customProvider.connect();\n if (defResult.ok && cusResult.ok) {\n expect(defResult.value[0].address).not.toBe(cusResult.value[0].address);\n }\n });\n\n test(\"custom ss58Prefix changes address encoding\", async () => {\n const generic = new DevProvider({ ss58Prefix: 42 });\n const polkadot = new DevProvider({ ss58Prefix: 0 });\n const rg = await generic.connect();\n const rp = await polkadot.connect();\n if (rg.ok && rp.ok) {\n // Different address strings\n expect(rg.value[0].address).not.toBe(rp.value[0].address);\n // Same underlying public key\n expect(rg.value[0].publicKey).toEqual(rp.value[0].publicKey);\n }\n });\n\n test(\"disconnect is idempotent\", () => {\n const provider = new DevProvider();\n // Should not throw\n provider.disconnect();\n provider.disconnect();\n });\n\n test(\"onStatusChange returns no-op unsubscribe\", () => {\n const provider = new DevProvider();\n const callback = () => {};\n const unsub = provider.onStatusChange(callback);\n expect(typeof unsub).toBe(\"function\");\n unsub(); // should not throw\n });\n\n test(\"onAccountsChange returns no-op unsubscribe\", () => {\n const provider = new DevProvider();\n const callback = () => {};\n const unsub = provider.onAccountsChange(callback);\n expect(typeof unsub).toBe(\"function\");\n unsub(); // should not throw\n });\n\n test(\"type is 'dev'\", () => {\n const provider = new DevProvider();\n expect(provider.type).toBe(\"dev\");\n });\n\n test(\"empty names array returns zero accounts\", async () => {\n const provider = new DevProvider({ names: [] });\n const result = await provider.connect();\n if (result.ok) {\n expect(result.value).toHaveLength(0);\n }\n });\n\n test(\"default keyType is sr25519 (backward compatible)\", async () => {\n const provider = new DevProvider();\n const result = await provider.connect();\n if (result.ok) {\n // Alice address matches well-known sr25519 address\n expect(result.value[0].address).toBe(ALICE_ADDRESS);\n }\n });\n });\n\n describe(\"DevProvider ed25519\", () => {\n test(\"ed25519 produces different addresses than sr25519\", async () => {\n const sr = new DevProvider({ keyType: \"sr25519\" });\n const ed = new DevProvider({ keyType: \"ed25519\" });\n const srResult = await sr.connect();\n const edResult = await ed.connect();\n if (srResult.ok && edResult.ok) {\n expect(srResult.value[0].address).not.toBe(edResult.value[0].address);\n }\n });\n\n test(\"ed25519 addresses are deterministic\", async () => {\n const a = new DevProvider({ keyType: \"ed25519\" });\n const b = new DevProvider({ keyType: \"ed25519\" });\n const ra = await a.connect();\n const rb = await b.connect();\n if (ra.ok && rb.ok) {\n expect(ra.value.map((x) => x.address)).toEqual(rb.value.map((x) => x.address));\n }\n });\n\n test(\"ed25519 getSigner has matching publicKey\", async () => {\n const provider = new DevProvider({ keyType: \"ed25519\" });\n const result = await provider.connect();\n if (result.ok) {\n for (const account of result.value) {\n const signer = account.getSigner();\n expect(signer.publicKey).toEqual(account.publicKey);\n }\n }\n });\n\n test(\"ed25519 accounts have 32-byte publicKey\", async () => {\n const provider = new DevProvider({ keyType: \"ed25519\" });\n const result = await provider.connect();\n if (result.ok) {\n for (const account of result.value) {\n expect(account.publicKey).toBeInstanceOf(Uint8Array);\n expect(account.publicKey.length).toBe(32);\n }\n }\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Sleep for a given duration, cancellable via AbortSignal.\n *\n * Resolves immediately if the signal is already aborted.\n * Cleans up the abort listener when the timer fires naturally\n * to prevent listener accumulation in retry loops.\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal?.aborted) {\n resolve();\n return;\n }\n\n const onDone = () => {\n clearTimeout(timer);\n signal?.removeEventListener(\"abort\", onDone);\n resolve();\n };\n\n const timer = setTimeout(onDone, ms);\n signal?.addEventListener(\"abort\", onDone, { once: true });\n });\n}\n\nif (import.meta.vitest) {\n const { test, expect, describe, vi, beforeEach, afterEach } = import.meta.vitest;\n\n beforeEach(() => {\n vi.useFakeTimers();\n });\n\n afterEach(() => {\n vi.useRealTimers();\n });\n\n describe(\"sleep\", () => {\n test(\"resolves after specified duration\", async () => {\n let resolved = false;\n sleep(100).then(() => {\n resolved = true;\n });\n\n expect(resolved).toBe(false);\n await vi.advanceTimersByTimeAsync(99);\n expect(resolved).toBe(false);\n await vi.advanceTimersByTimeAsync(1);\n expect(resolved).toBe(true);\n });\n\n test(\"resolves immediately when signal is already aborted\", async () => {\n const controller = new AbortController();\n controller.abort();\n\n let resolved = false;\n sleep(10_000, controller.signal).then(() => {\n resolved = true;\n });\n\n // Should resolve on next microtask, not after 10s\n await vi.advanceTimersByTimeAsync(0);\n expect(resolved).toBe(true);\n });\n\n test(\"resolves early when signal is aborted during sleep\", async () => {\n const controller = new AbortController();\n let resolved = false;\n sleep(10_000, controller.signal).then(() => {\n resolved = true;\n });\n\n await vi.advanceTimersByTimeAsync(50);\n expect(resolved).toBe(false);\n\n controller.abort();\n await vi.advanceTimersByTimeAsync(0);\n expect(resolved).toBe(true);\n });\n\n test(\"works without a signal\", async () => {\n let resolved = false;\n sleep(50).then(() => {\n resolved = true;\n });\n\n await vi.advanceTimersByTimeAsync(50);\n expect(resolved).toBe(true);\n });\n\n test(\"cleans up abort listener after natural timer expiry\", async () => {\n const controller = new AbortController();\n const addSpy = vi.spyOn(controller.signal, \"addEventListener\");\n const removeSpy = vi.spyOn(controller.signal, \"removeEventListener\");\n\n sleep(50, controller.signal);\n expect(addSpy).toHaveBeenCalledTimes(1);\n\n await vi.advanceTimersByTimeAsync(50);\n expect(removeSpy).toHaveBeenCalledTimes(1);\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Result-based retry with exponential backoff for signer connection attempts.\n *\n * This retry utility works with `Result<T, E>` return values (not exceptions).\n * It exists separately from the tx package's exception-based `withRetry` because:\n *\n * - **Signer retry**: retries connection attempts that return `Result<T, SignerError>`.\n * Errors are expected values, not exceptional conditions. Supports AbortSignal.\n * - **TX retry**: retries transaction submissions that throw exceptions.\n * Non-retryable errors (dispatch, rejection, timeout) are detected and rethrown.\n *\n * Both use exponential backoff but serve different abstraction layers.\n *\n * @module\n */\nimport { sleep } from \"./sleep.js\";\nimport type { Result } from \"./types.js\";\n\n/** Options for retry with exponential backoff. */\nexport interface RetryOptions {\n /** Maximum number of attempts. Default: 3 */\n maxAttempts?: number;\n /** Initial delay in ms before first retry. Default: 500 */\n initialDelay?: number;\n /** Multiplier applied to delay after each attempt. Default: 2 */\n backoffMultiplier?: number;\n /** Maximum delay cap in ms. Default: 10_000 */\n maxDelay?: number;\n /** AbortSignal to cancel retries early. */\n signal?: AbortSignal;\n}\n\nconst DEFAULT_MAX_ATTEMPTS = 3;\nconst DEFAULT_INITIAL_DELAY = 500;\nconst DEFAULT_BACKOFF_MULTIPLIER = 2;\nconst DEFAULT_MAX_DELAY = 10_000;\n\n/**\n * Retry an async operation with exponential backoff.\n *\n * Calls `fn` up to `maxAttempts` times. If `fn` returns an error result,\n * waits with exponential backoff before the next attempt. Returns the first\n * successful result or the last error.\n */\nexport async function withRetry<T, E>(\n fn: (attempt: number) => Promise<Result<T, E>>,\n options?: RetryOptions,\n): Promise<Result<T, E>> {\n const maxAttempts = Math.max(1, options?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS);\n const initialDelay = options?.initialDelay ?? DEFAULT_INITIAL_DELAY;\n const backoffMultiplier = options?.backoffMultiplier ?? DEFAULT_BACKOFF_MULTIPLIER;\n const maxDelay = options?.maxDelay ?? DEFAULT_MAX_DELAY;\n const signal = options?.signal;\n\n let lastResult: Result<T, E> | undefined;\n let delay = initialDelay;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n if (signal?.aborted && lastResult) {\n return lastResult;\n }\n\n lastResult = await fn(attempt);\n if (lastResult.ok) {\n return lastResult;\n }\n\n // Don't delay after the last attempt\n if (attempt < maxAttempts - 1) {\n await sleep(delay, signal);\n delay = Math.min(delay * backoffMultiplier, maxDelay);\n }\n }\n\n // lastResult is always defined here since maxAttempts >= 1\n return lastResult!;\n}\n\nif (import.meta.vitest) {\n const { test, expect, describe, vi, beforeEach, afterEach } = import.meta.vitest;\n const { ok, err } = await import(\"./types.js\");\n\n beforeEach(() => {\n vi.useFakeTimers();\n });\n\n afterEach(() => {\n vi.useRealTimers();\n });\n\n describe(\"withRetry\", () => {\n test(\"succeeds on first attempt with no delay\", async () => {\n const fn = vi.fn().mockResolvedValue(ok(\"done\"));\n const promise = withRetry(fn);\n const result = await promise;\n expect(result).toEqual(ok(\"done\"));\n expect(fn).toHaveBeenCalledTimes(1);\n expect(fn).toHaveBeenCalledWith(0);\n });\n\n test(\"retries on failure and succeeds on second attempt\", async () => {\n const fn = vi\n .fn()\n .mockResolvedValueOnce(err(\"fail1\"))\n .mockResolvedValueOnce(ok(\"success\"));\n\n const promise = withRetry(fn, { initialDelay: 100 });\n await vi.advanceTimersByTimeAsync(100);\n const result = await promise;\n\n expect(result).toEqual(ok(\"success\"));\n expect(fn).toHaveBeenCalledTimes(2);\n expect(fn).toHaveBeenNthCalledWith(1, 0);\n expect(fn).toHaveBeenNthCalledWith(2, 1);\n });\n\n test(\"exhausts maxAttempts and returns last error\", async () => {\n const fn = vi\n .fn()\n .mockResolvedValueOnce(err(\"fail1\"))\n .mockResolvedValueOnce(err(\"fail2\"))\n .mockResolvedValueOnce(err(\"fail3\"));\n\n const promise = withRetry(fn, {\n maxAttempts: 3,\n initialDelay: 100,\n backoffMultiplier: 2,\n });\n\n await vi.advanceTimersByTimeAsync(100); // delay after attempt 0\n await vi.advanceTimersByTimeAsync(200); // delay after attempt 1\n const result = await promise;\n\n expect(result).toEqual(err(\"fail3\"));\n expect(fn).toHaveBeenCalledTimes(3);\n });\n\n test(\"respects AbortSignal cancellation\", async () => {\n const controller = new AbortController();\n const fn = vi.fn().mockResolvedValue(err(\"fail\"));\n\n const promise = withRetry(fn, {\n maxAttempts: 5,\n initialDelay: 1000,\n signal: controller.signal,\n });\n\n // First attempt runs, then abort during delay\n await vi.advanceTimersByTimeAsync(0);\n controller.abort();\n await vi.advanceTimersByTimeAsync(0);\n const result = await promise;\n\n expect(result.ok).toBe(false);\n // Should not have retried all 5 times\n expect(fn.mock.calls.length).toBeLessThan(5);\n });\n\n test(\"backoff delay increases correctly\", async () => {\n const fn = vi\n .fn()\n .mockResolvedValueOnce(err(\"e1\"))\n .mockResolvedValueOnce(err(\"e2\"))\n .mockResolvedValueOnce(err(\"e3\"))\n .mockResolvedValueOnce(ok(\"done\"));\n\n const promise = withRetry(fn, {\n maxAttempts: 4,\n initialDelay: 100,\n backoffMultiplier: 2,\n });\n\n // After attempt 0: delay 100ms\n await vi.advanceTimersByTimeAsync(100);\n // After attempt 1: delay 200ms\n await vi.advanceTimersByTimeAsync(200);\n // After attempt 2: delay 400ms\n await vi.advanceTimersByTimeAsync(400);\n const result = await promise;\n\n expect(result).toEqual(ok(\"done\"));\n expect(fn).toHaveBeenCalledTimes(4);\n });\n\n test(\"caps delay at maxDelay\", async () => {\n const fn = vi.fn().mockImplementation(async () => {\n return err(\"fail\");\n });\n\n const promise = withRetry(fn, {\n maxAttempts: 4,\n initialDelay: 5000,\n backoffMultiplier: 3,\n maxDelay: 8000,\n });\n\n // Attempt 0, delay 5000ms\n await vi.advanceTimersByTimeAsync(5000);\n // Attempt 1, delay min(15000, 8000) = 8000ms\n await vi.advanceTimersByTimeAsync(8000);\n // Attempt 2, delay min(24000, 8000) = 8000ms\n await vi.advanceTimersByTimeAsync(8000);\n const result = await promise;\n\n expect(result.ok).toBe(false);\n expect(fn).toHaveBeenCalledTimes(4);\n });\n\n test(\"attempt number is passed correctly to fn\", async () => {\n const attempts: number[] = [];\n const fn = vi.fn().mockImplementation(async (attempt: number) => {\n attempts.push(attempt);\n return attempt < 2 ? err(\"retry\") : ok(\"done\");\n });\n\n const promise = withRetry(fn, {\n maxAttempts: 3,\n initialDelay: 50,\n });\n await vi.advanceTimersByTimeAsync(50);\n await vi.advanceTimersByTimeAsync(100);\n await promise;\n\n expect(attempts).toEqual([0, 1, 2]);\n });\n\n test(\"single attempt with maxAttempts=1\", async () => {\n const fn = vi.fn().mockResolvedValue(err(\"fail\"));\n\n const result = await withRetry(fn, { maxAttempts: 1 });\n expect(result).toEqual(err(\"fail\"));\n expect(fn).toHaveBeenCalledTimes(1);\n });\n\n test(\"signal already aborted before first attempt — fn still called once\", async () => {\n const controller = new AbortController();\n controller.abort();\n\n const fn = vi.fn().mockResolvedValue(err(\"fail\"));\n const result = await withRetry(fn, {\n maxAttempts: 3,\n signal: controller.signal,\n });\n\n expect(result.ok).toBe(false);\n // fn is called once so it can produce a properly-typed error\n expect(fn).toHaveBeenCalledTimes(1);\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport { deriveH160, ss58Encode } from \"@parity/product-sdk-address\";\nimport { createLogger } from \"@parity/product-sdk-logger\";\n\nimport {\n HostRejectedError,\n HostUnavailableError,\n NoAccountsError,\n type SignerError,\n} from \"../errors.js\";\nimport { withRetry } from \"../retry.js\";\nimport type { ConnectionStatus, ProviderType, Result, SignerAccount } from \"../types.js\";\nimport { err, ok } from \"../types.js\";\nimport type { SignerProvider, Unsubscribe } from \"./types.js\";\n\nconst log = createLogger(\"signer:host\");\n\n/** Options for the Host API provider. */\nexport interface HostProviderOptions {\n /** SS58 prefix for address encoding. Default: 42 */\n ss58Prefix?: number;\n /** Max retry attempts for initial connection. Default: 3 */\n maxRetries?: number;\n /** Initial retry delay in ms. Default: 500 */\n retryDelay?: number;\n /**\n * Custom SDK loader. Defaults to `import(\"@novasamatech/host-api-wrapper\")`.\n * Override this for testing or custom SDK setups.\n * @internal\n */\n loadSdk?: () => Promise<ProductSdkModule>;\n /**\n * Custom loader for `@novasamatech/host-api` (used to construct the\n * `ChainSubmit` permission request). Defaults to dynamic import.\n * @internal\n */\n loadHostApiEnum?: () => Promise<HostApiEnumHelper>;\n /**\n * Whether to request the host's `ChainSubmit` permission after a\n * successful `connect()`. Without this, subsequent signing requests are\n * rejected by the host with `PermissionDenied`. Default: `true`.\n *\n * Set to `false` if your app needs to defer the permission prompt or\n * drives it manually.\n *\n * (Previously named `requestTransactionSubmitPermission` — alias kept\n * for backwards compatibility but the new wire format uses `ChainSubmit`.)\n */\n requestChainSubmitPermission?: boolean;\n /** @deprecated Renamed to `requestChainSubmitPermission`. */\n requestTransactionSubmitPermission?: boolean;\n /**\n * If set, `connect()` returns a single product account for the given\n * `dotNsIdentifier`, skipping the legacy fetch entirely. For apps\n * that sign exclusively with a per-dapp derived account.\n *\n * `SignerAccount.name` is populated best-effort from\n * `accounts.getUserId().primaryUsername`; failures leave it null.\n * Signing is pinned to `createTransaction` (see PR #96).\n */\n productAccount?: {\n /** App identifier (e.g., `\"playground.dot\"`). */\n dotNsIdentifier: string;\n /** Derivation index within the app scope. Default: 0. */\n derivationIndex?: number;\n };\n}\n\n/**\n * A product account — an app-scoped derived account managed by the host wallet.\n *\n * The host derives a unique keypair for each app (identified by `dotNsIdentifier`)\n * so apps get their own account that the user controls but is scoped to the app.\n */\nexport interface ProductAccount {\n /** App identifier (e.g., \"mark3t.dot\"). */\n dotNsIdentifier: string;\n /** Derivation index within the app scope. Default: 0 */\n derivationIndex: number;\n /** Raw public key (32 bytes). */\n publicKey: Uint8Array;\n}\n\n/**\n * A contextual alias obtained from Ring VRF.\n *\n * Proves account membership in a ring without revealing which account.\n */\nexport interface ContextualAlias {\n /** Ring context (32 bytes). */\n context: Uint8Array;\n /** The Ring VRF alias bytes. */\n alias: Uint8Array;\n}\n\n/**\n * Location of a Ring VRF ring on-chain.\n *\n * Matches the product-sdk's `RingLocation` codec shape.\n */\nexport interface RingLocation {\n genesisHash: string;\n ringRootHash: string;\n hints?: { palletInstance?: number } | undefined;\n}\n\n// Minimal types matching product-sdk's actual API shape.\n// We define these locally so the SDK remains an optional peer dep.\ninterface RawAccount {\n publicKey: Uint8Array;\n name?: string | undefined;\n}\n\n// Minimal neverthrow ResultAsync shape (product-sdk uses neverthrow internally)\ninterface NeverthrowResultAsync<T, E> {\n match: <A, B = A>(ok: (t: T) => A, err: (e: E) => B) => Promise<A | B>;\n}\n\n/**\n * Pin product-account signing to Nova's `host_create_transaction` path.\n *\n * The `createTransaction` path forwards opaque signed-extension bytes to\n * the host for metadata-driven decoding, so unknown extensions (e.g.\n * `AsPgas` on Paseo Next) survive end-to-end. The alternate\n * `\"signPayload\"` path wraps via PJS and throws\n * `\"PJS does not support this signed-extension: AsPgas\"` on those chains.\n *\n * Nova's `host-api-wrapper@0.8.0` already defaults to `\"createTransaction\"`,\n * so this is a defensive pin rather than an opt-in — it guards against a\n * future upstream default flip and makes the routing legible at the call\n * site. The legacy-account signer doesn't expose this switch.\n */\nconst PRODUCT_SIGNER_TYPE = \"createTransaction\" as const;\n\n/** @internal */\nexport interface AccountsProvider {\n getLegacyAccounts: () => NeverthrowResultAsync<RawAccount[], unknown>;\n getLegacyAccountSigner: (account: ProductAccount) => import(\"polkadot-api\").PolkadotSigner;\n getProductAccount: (\n dotNsIdentifier: string,\n derivationIndex?: number,\n ) => NeverthrowResultAsync<RawAccount, unknown>;\n getProductAccountSigner: (\n account: ProductAccount,\n signerType?: \"signPayload\" | \"createTransaction\",\n ) => import(\"polkadot-api\").PolkadotSigner;\n getProductAccountAlias: (\n dotNsIdentifier: string,\n derivationIndex?: number,\n ) => NeverthrowResultAsync<ContextualAlias, unknown>;\n getUserId: () => NeverthrowResultAsync<{ primaryUsername: string }, unknown>;\n createRingVRFProof: (\n dotNsIdentifier: string,\n derivationIndex: number,\n location: unknown,\n message: Uint8Array,\n ) => NeverthrowResultAsync<Uint8Array, unknown>;\n subscribeAccountConnectionStatus: (\n callback: (status: string) => void,\n ) => { unsubscribe: () => void } | (() => void);\n}\n\n/** @internal */\nexport interface HostApiPermissionBridge {\n /**\n * Request a Host API permission. Product-sdk's `hostApi.permission(...)`\n * takes a tagged enum like `enumValue(\"v1\", { tag: \"TransactionSubmit\" })`\n * and returns a neverthrow ResultAsync.\n */\n permission: (request: unknown) => NeverthrowResultAsync<unknown, unknown>;\n}\n\n/** @internal */\nexport interface HostApiEnumHelper {\n enumValue: (version: string, value: { tag: string; value?: unknown }) => unknown;\n}\n\n/** @internal */\nexport interface ProductSdkModule {\n createAccountsProvider: () => AccountsProvider;\n /** Present from product-sdk ≥ 0.6; used to request TransactionSubmit. */\n hostApi?: HostApiPermissionBridge;\n}\n\n/* @integration */\nasync function defaultLoadSdk(): Promise<ProductSdkModule> {\n return (await import(\"@novasamatech/host-api-wrapper\")) as unknown as ProductSdkModule;\n}\n\n/* @integration */\nasync function defaultLoadHostApiEnum(): Promise<HostApiEnumHelper> {\n return (await import(\"@novasamatech/host-api\")) as unknown as HostApiEnumHelper;\n}\n\n/**\n * Provider for the Host API (Polkadot Desktop / Android).\n *\n * Dynamically imports `@novasamatech/host-api-wrapper` at runtime so it remains\n * an optional peer dependency. Apps running outside a host container will\n * gracefully get a `HOST_UNAVAILABLE` error.\n *\n * Supports both non-product accounts (user's external wallets) and product\n * accounts (app-scoped derived accounts managed by the host).\n */\nexport class HostProvider implements SignerProvider {\n readonly type: ProviderType = \"host\";\n private readonly ss58Prefix: number;\n private readonly maxRetries: number;\n private readonly retryDelay: number;\n private readonly loadSdk: () => Promise<ProductSdkModule>;\n private readonly loadHostApiEnum: () => Promise<HostApiEnumHelper>;\n private readonly requestChainSubmitPermission: boolean;\n private readonly productAccount: HostProviderOptions[\"productAccount\"];\n\n private accountsProvider: AccountsProvider | null = null;\n private statusCleanup: (() => void) | null = null;\n private statusListeners = new Set<(status: ConnectionStatus) => void>();\n private accountListeners = new Set<(accounts: SignerAccount[]) => void>();\n\n constructor(options?: HostProviderOptions) {\n this.ss58Prefix = options?.ss58Prefix ?? 42;\n this.maxRetries = options?.maxRetries ?? 3;\n this.retryDelay = options?.retryDelay ?? 500;\n this.loadSdk = options?.loadSdk ?? defaultLoadSdk;\n this.loadHostApiEnum = options?.loadHostApiEnum ?? defaultLoadHostApiEnum;\n // New name takes precedence; fall back to the deprecated alias.\n this.requestChainSubmitPermission =\n options?.requestChainSubmitPermission ??\n options?.requestTransactionSubmitPermission ??\n true;\n this.productAccount = options?.productAccount;\n }\n\n async connect(signal?: AbortSignal): Promise<Result<SignerAccount[], SignerError>> {\n log.debug(\"attempting Host API connection\");\n\n return withRetry(\n async () => {\n if (signal?.aborted) {\n return err(new HostUnavailableError(\"Connection aborted\"));\n }\n return this.tryConnect();\n },\n {\n maxAttempts: this.maxRetries,\n initialDelay: this.retryDelay,\n signal,\n },\n );\n }\n\n disconnect(): void {\n if (this.statusCleanup) {\n this.statusCleanup();\n this.statusCleanup = null;\n }\n this.accountsProvider = null;\n this.statusListeners.clear();\n this.accountListeners.clear();\n log.debug(\"host provider disconnected\");\n }\n\n onStatusChange(callback: (status: ConnectionStatus) => void): Unsubscribe {\n this.statusListeners.add(callback);\n return () => {\n this.statusListeners.delete(callback);\n };\n }\n\n onAccountsChange(callback: (accounts: SignerAccount[]) => void): Unsubscribe {\n this.accountListeners.add(callback);\n return () => {\n this.accountListeners.delete(callback);\n };\n }\n\n // ── Product Account API ──────────────────────────────────────────\n\n /**\n * Get an app-scoped product account from the host.\n *\n * Product accounts are derived by the host wallet for each app, identified\n * by `dotNsIdentifier` (e.g., \"mark3t.dot\"). The user controls these accounts\n * but they are scoped to the requesting app.\n *\n * Requires a prior successful `connect()` call.\n */\n async getProductAccount(\n dotNsIdentifier: string,\n derivationIndex = 0,\n ): Promise<Result<SignerAccount, SignerError>> {\n if (!this.accountsProvider) {\n return err(new HostUnavailableError(\"Host provider is not connected\"));\n }\n\n try {\n const raw = (await this.accountsProvider\n .getProductAccount(dotNsIdentifier, derivationIndex)\n .match(\n (account) => account,\n (error) => {\n throw new Error(\n `Host rejected product account request: ${formatError(error)}`,\n );\n },\n )) as RawAccount;\n\n const address = ss58Encode(raw.publicKey, this.ss58Prefix);\n const productAccount: ProductAccount = {\n dotNsIdentifier,\n derivationIndex,\n publicKey: raw.publicKey,\n };\n\n return ok({\n address,\n h160Address: deriveH160(raw.publicKey),\n publicKey: raw.publicKey,\n name: raw.name ?? null,\n source: \"host\" as const,\n getSigner: () => {\n if (!this.accountsProvider) {\n throw new Error(\"Host provider is disconnected\");\n }\n return this.accountsProvider.getProductAccountSigner(\n productAccount,\n PRODUCT_SIGNER_TYPE,\n );\n },\n });\n } catch (cause) {\n log.error(\"failed to get product account\", { cause });\n return err(\n new HostRejectedError(\n cause instanceof Error ? cause.message : \"Failed to get product account\",\n ),\n );\n }\n }\n\n /**\n * Get a PolkadotSigner for a product account.\n *\n * Convenience method for when you already have the product account details.\n * Requires a prior successful `connect()` call.\n *\n * Routing is pinned to `signerType: \"createTransaction\"` via\n * {@link PRODUCT_SIGNER_TYPE} so unknown signed extensions (e.g. `AsPgas`\n * on Paseo Next) are forwarded to the host as opaque bytes for\n * metadata-driven decoding, rather than going through the PJS bridge\n * that throws on unknown extensions.\n */\n getProductAccountSigner(account: ProductAccount): import(\"polkadot-api\").PolkadotSigner {\n if (!this.accountsProvider) {\n throw new Error(\"Host provider is not connected\");\n }\n return this.accountsProvider.getProductAccountSigner(account, PRODUCT_SIGNER_TYPE);\n }\n\n /**\n * Get a contextual alias for a product account via Ring VRF.\n *\n * Aliases prove account membership in a ring without revealing which\n * account produced the alias.\n *\n * Requires a prior successful `connect()` call.\n */\n async getProductAccountAlias(\n dotNsIdentifier: string,\n derivationIndex = 0,\n ): Promise<Result<ContextualAlias, SignerError>> {\n if (!this.accountsProvider) {\n return err(new HostUnavailableError(\"Host provider is not connected\"));\n }\n\n try {\n const alias = (await this.accountsProvider\n .getProductAccountAlias(dotNsIdentifier, derivationIndex)\n .match(\n (result) => result,\n (error) => {\n throw new Error(`Host rejected alias request: ${formatError(error)}`);\n },\n )) as ContextualAlias;\n\n return ok(alias);\n } catch (cause) {\n log.error(\"failed to get product account alias\", { cause });\n return err(\n new HostRejectedError(\n cause instanceof Error ? cause.message : \"Failed to get product account alias\",\n ),\n );\n }\n }\n\n /**\n * Create a Ring VRF proof for anonymous operations.\n *\n * Proves that the signer is a member of the ring at the given location\n * without revealing which member. Used for privacy-preserving protocols.\n *\n * Requires a prior successful `connect()` call.\n */\n async createRingVRFProof(\n dotNsIdentifier: string,\n derivationIndex: number,\n location: RingLocation,\n message: Uint8Array,\n ): Promise<Result<Uint8Array, SignerError>> {\n if (!this.accountsProvider) {\n return err(new HostUnavailableError(\"Host provider is not connected\"));\n }\n\n try {\n const proof = (await this.accountsProvider\n .createRingVRFProof(dotNsIdentifier, derivationIndex, location, message)\n .match(\n (result) => result,\n (error) => {\n throw new Error(\n `Host rejected Ring VRF proof request: ${formatError(error)}`,\n );\n },\n )) as Uint8Array;\n\n return ok(proof);\n } catch (cause) {\n log.error(\"failed to create Ring VRF proof\", { cause });\n return err(\n new HostRejectedError(\n cause instanceof Error ? cause.message : \"Failed to create Ring VRF proof\",\n ),\n );\n }\n }\n\n // ── Private ──────────────────────────────────────────────────────\n\n private async tryConnect(): Promise<Result<SignerAccount[], SignerError>> {\n // Step 1: Load product-sdk\n let sdk: ProductSdkModule;\n try {\n sdk = await this.loadSdk();\n } catch (cause) {\n log.warn(\"product-sdk not available\", { cause });\n return err(\n new HostUnavailableError(\n cause instanceof Error\n ? `product-sdk import failed: ${cause.message}`\n : \"product-sdk is not installed\",\n ),\n );\n }\n\n // Step 2: Create accounts provider\n const provider = sdk.createAccountsProvider();\n this.accountsProvider = provider;\n\n // Step 3: Fetch accounts.\n //\n // When `productAccount` is configured, skip the legacy fetch entirely\n // and return a single product account. Product-account-only apps\n // (no wallet picker) often run against hosts that have no legacy\n // accounts to surface — calling `getLegacyAccounts()` there returns\n // an empty list and the connect would fail with `NoAccountsError`.\n let signerAccounts: SignerAccount[];\n if (this.productAccount) {\n const accountResult = await this.fetchProductSignerAccount(\n provider,\n this.productAccount.dotNsIdentifier,\n this.productAccount.derivationIndex ?? 0,\n );\n if (!accountResult.ok) return accountResult;\n signerAccounts = [accountResult.value];\n } else {\n let rawAccounts: RawAccount[];\n try {\n rawAccounts = (await provider.getLegacyAccounts().match(\n (accounts) => accounts,\n (error) => {\n throw new Error(`Host rejected account request: ${formatError(error)}`);\n },\n )) as RawAccount[];\n } catch (cause) {\n log.error(\"failed to get accounts from host\", { cause });\n return err(\n new HostRejectedError(\n cause instanceof Error ? cause.message : \"Failed to get accounts from host\",\n ),\n );\n }\n\n if (rawAccounts.length === 0) {\n log.warn(\"host returned no accounts\");\n return err(new NoAccountsError(\"host\"));\n }\n\n signerAccounts = this.mapAccounts(rawAccounts);\n }\n\n // Step 4: Request ChainSubmit permission up-front.\n //\n // The host gates signing on this permission — without it, the\n // production host rejects every sign request with `PermissionDenied`\n // at both `handleSignPayload` (legacy account path) and\n // `host_create_transaction` (product-account path), which typically\n // manifests as a silently-hanging tx. Doing it once during connect()\n // matches what production apps need and spares consumers the\n // boilerplate.\n //\n // We don't fail `connect()` if this step fails: the consumer can still\n // use the signer for read-only code paths, and the actual sign call\n // will surface a clear error if permission is missing.\n //\n // The legal v1 RemotePermission variants per\n // `@novasamatech/host-api@0.8.0` are: Remote, WebRtc, ChainSubmit,\n // PreimageSubmit, StatementSubmit. ChainSubmit is the chain-tx\n // permission (was named TransactionSubmit in earlier host-api\n // revisions; renamed in 0.7). `WebRtc` was spelled `WebRTC` before\n // 0.8.\n if (this.requestChainSubmitPermission && sdk.hostApi) {\n try {\n const hostApiEnum = await this.loadHostApiEnum();\n const request = hostApiEnum.enumValue(\"v1\", {\n tag: \"ChainSubmit\",\n value: undefined,\n });\n await sdk.hostApi.permission(request).match(\n () => {\n log.debug(\"ChainSubmit permission granted\");\n },\n (error) => {\n log.warn(\"ChainSubmit permission rejected by host\", {\n error: formatError(error),\n });\n },\n );\n } catch (cause) {\n log.warn(\"failed to request ChainSubmit permission\", { cause });\n }\n }\n\n log.info(\"host connected\", { accounts: signerAccounts.length });\n\n // Step 6: Subscribe to connection status\n const sub = provider.subscribeAccountConnectionStatus((status) => {\n const mapped: ConnectionStatus = status === \"connected\" ? \"connected\" : \"disconnected\";\n log.debug(\"host status changed\", { status: mapped });\n for (const listener of this.statusListeners) {\n listener(mapped);\n }\n });\n this.statusCleanup = typeof sub === \"function\" ? sub : () => sub.unsubscribe();\n\n return ok(signerAccounts);\n }\n\n private async fetchProductSignerAccount(\n provider: AccountsProvider,\n dotNsIdentifier: string,\n derivationIndex: number,\n ): Promise<Result<SignerAccount, SignerError>> {\n // Run the account fetch and the best-effort username fetch in\n // parallel — they're independent host RPCs. `getUserId` failures\n // (NotConnected, PermissionDenied, codec drift) resolve to `null`\n // so they never abort connect; the account name falls back to\n // whatever `getProductAccount` returned (typically also null,\n // since product accounts are nameless on the host side).\n const fetchUsername = async (): Promise<string | null> => {\n try {\n return await provider.getUserId().match(\n (result) => result.primaryUsername,\n (error) => {\n log.debug(\"getUserId failed; product account name stays null\", {\n error: formatError(error),\n });\n return null as string | null;\n },\n );\n } catch (cause) {\n log.debug(\"getUserId threw; product account name stays null\", { cause });\n return null;\n }\n };\n const [accountResult, primaryUsername] = await Promise.all([\n this.getProductAccount(dotNsIdentifier, derivationIndex),\n fetchUsername(),\n ]);\n if (!accountResult.ok) return accountResult;\n const account = accountResult.value;\n return ok({ ...account, name: account.name ?? primaryUsername });\n }\n\n private mapAccounts(rawAccounts: ReadonlyArray<RawAccount>): SignerAccount[] {\n return rawAccounts.map((raw) => {\n const address = ss58Encode(raw.publicKey, this.ss58Prefix);\n const h160Address = deriveH160(raw.publicKey);\n return {\n address,\n h160Address,\n publicKey: raw.publicKey,\n name: raw.name ?? null,\n source: \"host\" as const,\n getSigner: () => {\n if (!this.accountsProvider) {\n throw new Error(\"Host provider is disconnected\");\n }\n return this.accountsProvider.getLegacyAccountSigner({\n dotNsIdentifier: \"\",\n derivationIndex: 0,\n publicKey: raw.publicKey,\n });\n },\n };\n });\n }\n}\n\n/**\n * Format a host-error for logging.\n *\n * host-api errors come back as `{ tag: \"v1\", value: <inner> }` where the\n * inner can be either another tagged enum (with its own tag/value) or a\n * plain `Error`-shaped object surfacing client-side codec failures\n * (e.g. `GenericError: inner[tag] is not a function` when the SDK\n * encodes a request the codec doesn't understand).\n *\n * Walking the value side as well as the tag means schema drift between\n * host-api versions and the SDK produces something more diagnostic than\n * just the outermost wrapper tag.\n */\nfunction formatError(error: unknown): string {\n if (!error || typeof error !== \"object\") return String(error);\n const e = error as Record<string, unknown>;\n if (!(\"tag\" in e)) return String(error);\n\n const outerTag = String(e.tag);\n const inner = e.value;\n\n // Inner is an Error-shaped object with name/message — surface those.\n if (inner && typeof inner === \"object\") {\n const innerObj = inner as Record<string, unknown>;\n if (typeof innerObj.message === \"string\") {\n const innerName =\n typeof innerObj.name === \"string\" && innerObj.name !== \"Error\"\n ? `${innerObj.name}: `\n : \"\";\n return `${outerTag} → ${innerName}${innerObj.message}`;\n }\n // Inner is a nested tagged-enum — recurse.\n if (\"tag\" in innerObj) {\n return `${outerTag} → ${formatError(inner)}`;\n }\n }\n\n // Inner is a primitive or absent — fall back to the outer tag alone.\n if (inner !== undefined) {\n return `${outerTag} (${String(inner)})`;\n }\n return outerTag;\n}\n\nif (import.meta.vitest) {\n const { test, expect, describe, vi, beforeEach } = import.meta.vitest;\n\n interface RawAccountTest {\n publicKey: Uint8Array;\n name?: string | undefined;\n }\n\n function createMockProvider(\n options: {\n accounts?: RawAccountTest[];\n shouldReject?: boolean;\n error?: unknown;\n primaryUsername?: string;\n } = {},\n ) {\n const accounts = options.accounts ?? [];\n const shouldReject = options.shouldReject ?? false;\n const mockSigner = {\n publicKey: new Uint8Array(32).fill(0xbb),\n } as unknown as import(\"polkadot-api\").PolkadotSigner;\n\n return {\n getLegacyAccounts: vi.fn().mockReturnValue({\n match: async (\n onOk: (v: RawAccountTest[]) => unknown,\n onErr: (e: unknown) => unknown,\n ) => {\n if (shouldReject) {\n return onErr(options.error ?? \"Unknown\");\n }\n return onOk(accounts);\n },\n }),\n getLegacyAccountSigner: vi.fn().mockReturnValue(mockSigner),\n getProductAccount: vi.fn().mockReturnValue({\n match: async (\n onOk: (v: RawAccountTest) => unknown,\n onErr: (e: unknown) => unknown,\n ) => {\n if (shouldReject) {\n return onErr(options.error ?? \"Unknown\");\n }\n return onOk(accounts[0] ?? { publicKey: new Uint8Array(32), name: undefined });\n },\n }),\n getProductAccountSigner: vi.fn().mockReturnValue(mockSigner),\n getProductAccountAlias: vi.fn().mockReturnValue({\n match: async (onOk: (v: unknown) => unknown, onErr: (e: unknown) => unknown) => {\n if (shouldReject) {\n return onErr(options.error ?? \"Unknown\");\n }\n return onOk({\n context: new Uint8Array(32).fill(0x01),\n alias: new Uint8Array(64).fill(0x02),\n });\n },\n }),\n createRingVRFProof: vi.fn().mockReturnValue({\n match: async (onOk: (v: unknown) => unknown, onErr: (e: unknown) => unknown) => {\n if (shouldReject) {\n return onErr(options.error ?? \"Unknown\");\n }\n return onOk(new Uint8Array(128).fill(0x03));\n },\n }),\n subscribeAccountConnectionStatus: vi.fn().mockReturnValue(() => {}),\n getUserId: vi.fn().mockReturnValue({\n match: async (\n onOk: (v: { primaryUsername: string }) => unknown,\n onErr: (e: unknown) => unknown,\n ) => {\n if (shouldReject) {\n return onErr(options.error ?? \"Unknown\");\n }\n return onOk({ primaryUsername: options.primaryUsername ?? \"\" });\n },\n }),\n };\n }\n\n function createMockSdk(\n mockProvider: ReturnType<typeof createMockProvider>,\n opts?: {\n hostApi?: HostApiPermissionBridge;\n },\n ): ProductSdkModule {\n return {\n createAccountsProvider: () => mockProvider as unknown as AccountsProvider,\n ...(opts?.hostApi ? { hostApi: opts.hostApi } : {}),\n };\n }\n\n /**\n * A fake neverthrow ResultAsync-like object. Resolves via `onOk` when\n * `error === undefined`, otherwise via `onErr`.\n */\n function fakeResult<T>(value: T, error?: unknown): NeverthrowResultAsync<T, unknown> {\n return {\n match: async (onOk, onErr) => {\n if (error !== undefined) return onErr(error);\n return onOk(value);\n },\n };\n }\n\n const fakeHostApiEnum: HostApiEnumHelper = {\n enumValue: (version, value) => ({ version, value }),\n };\n\n beforeEach(() => {\n vi.restoreAllMocks();\n });\n\n describe(\"HostProvider\", () => {\n test(\"returns HOST_UNAVAILABLE when SDK load fails\", async () => {\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.reject(new Error(\"Cannot find module\")),\n });\n const result = await provider.connect();\n\n expect(result.ok).toBe(false);\n if (!result.ok) {\n expect(result.error).toBeInstanceOf(HostUnavailableError);\n expect(result.error.message).toContain(\"Cannot find module\");\n }\n });\n\n test(\"returns HOST_REJECTED when getLegacyAccounts fails\", async () => {\n const mockProvider = createMockProvider({ shouldReject: true, error: \"Rejected\" });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider)),\n });\n const result = await provider.connect();\n\n expect(result.ok).toBe(false);\n if (!result.ok) {\n expect(result.error).toBeInstanceOf(HostRejectedError);\n }\n });\n\n test(\"returns NO_ACCOUNTS when host returns empty list\", async () => {\n const mockProvider = createMockProvider({ accounts: [] });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider)),\n });\n const result = await provider.connect();\n\n expect(result.ok).toBe(false);\n if (!result.ok) {\n expect(result.error).toBeInstanceOf(NoAccountsError);\n }\n });\n\n test(\"maps accounts correctly on success\", async () => {\n const rawAccounts: RawAccountTest[] = [\n { publicKey: new Uint8Array(32).fill(0xaa), name: \"Alice\" },\n { publicKey: new Uint8Array(32).fill(0xbb), name: undefined },\n ];\n const mockProvider = createMockProvider({ accounts: rawAccounts });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider)),\n });\n const result = await provider.connect();\n\n expect(result.ok).toBe(true);\n if (result.ok) {\n expect(result.value).toHaveLength(2);\n expect(result.value[0].name).toBe(\"Alice\");\n expect(result.value[0].source).toBe(\"host\");\n expect(result.value[0].publicKey).toEqual(rawAccounts[0].publicKey);\n expect(result.value[1].name).toBeNull();\n }\n });\n\n test(\"getProductAccountSigner pins signerType to 'createTransaction'\", async () => {\n // Regression guard: the alternate \"signPayload\" route goes through\n // PJS and throws on unknown signed extensions (e.g. AsPgas on\n // Paseo Next). If a future refactor drops the explicit pin and\n // upstream's default ever flips back to signPayload, this would\n // silently regress.\n const rawAccounts: RawAccountTest[] = [\n { publicKey: new Uint8Array(32).fill(0xaa), name: \"Alice\" },\n ];\n const mockProvider = createMockProvider({ accounts: rawAccounts });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider)),\n });\n await provider.connect();\n\n // Path 1: HostProvider.getProductAccountSigner(...)\n provider.getProductAccountSigner({\n dotNsIdentifier: \"test.dot\",\n derivationIndex: 0,\n publicKey: rawAccounts[0].publicKey,\n });\n expect(mockProvider.getProductAccountSigner).toHaveBeenLastCalledWith(\n expect.anything(),\n \"createTransaction\",\n );\n\n // Path 2: getSigner() returned from HostProvider.getProductAccount(...)\n const productAccountResult = await provider.getProductAccount(\"test.dot\", 0);\n expect(productAccountResult.ok).toBe(true);\n if (productAccountResult.ok) {\n productAccountResult.value.getSigner();\n expect(mockProvider.getProductAccountSigner).toHaveBeenLastCalledWith(\n expect.anything(),\n \"createTransaction\",\n );\n }\n });\n\n test(\"disconnect is idempotent\", () => {\n const provider = new HostProvider();\n provider.disconnect();\n provider.disconnect();\n });\n\n test(\"type is 'host'\", () => {\n const provider = new HostProvider();\n expect(provider.type).toBe(\"host\");\n });\n\n test(\"onAccountsChange adds and removes listener\", () => {\n const provider = new HostProvider();\n const cb = () => {};\n const unsub = provider.onAccountsChange(cb);\n expect(typeof unsub).toBe(\"function\");\n unsub();\n });\n\n test(\"productAccount option returns the derived account, populates name via getUserId, and skips the legacy fetch\", async () => {\n const productPubkey = new Uint8Array(32).fill(0xcc);\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: productPubkey, name: undefined }],\n primaryUsername: \"alice\",\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider)),\n productAccount: { dotNsIdentifier: \"myapp.dot\", derivationIndex: 0 },\n });\n const result = await provider.connect();\n\n expect(result.ok).toBe(true);\n if (result.ok) {\n expect(result.value).toHaveLength(1);\n expect(result.value[0].publicKey).toEqual(productPubkey);\n expect(result.value[0].source).toBe(\"host\");\n expect(result.value[0].name).toBe(\"alice\");\n result.value[0].getSigner();\n expect(mockProvider.getProductAccountSigner).toHaveBeenLastCalledWith(\n expect.objectContaining({\n dotNsIdentifier: \"myapp.dot\",\n derivationIndex: 0,\n }),\n \"createTransaction\",\n );\n }\n expect(mockProvider.getProductAccount).toHaveBeenCalledWith(\"myapp.dot\", 0);\n expect(mockProvider.getUserId).toHaveBeenCalled();\n expect(mockProvider.getLegacyAccounts).not.toHaveBeenCalled();\n });\n\n test(\"productAccount option survives getUserId failure (name stays null, connect still succeeds)\", async () => {\n const productPubkey = new Uint8Array(32).fill(0xee);\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: productPubkey, name: undefined }],\n });\n // Force getUserId to reject — connect must still succeed with name=null.\n mockProvider.getUserId.mockReturnValue({\n match: async (\n _onOk: (v: { primaryUsername: string }) => unknown,\n onErr: (e: unknown) => unknown,\n ) => onErr({ tag: \"v1\", value: { tag: \"GetUserIdErr::PermissionDenied\" } }),\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider)),\n productAccount: { dotNsIdentifier: \"myapp.dot\" },\n });\n const result = await provider.connect();\n\n expect(result.ok).toBe(true);\n if (result.ok) {\n expect(result.value[0].name).toBeNull();\n }\n });\n\n test(\"productAccount option succeeds when host has no legacy accounts (regression: signer 0.5.0 NoAccountsError)\", async () => {\n // Without the option, this scenario returned `err(NoAccountsError)`\n // before any product-account fetch could happen — breaking every\n // product-only app whose host doesn't surface legacy accounts.\n const productPubkey = new Uint8Array(32).fill(0xdd);\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: productPubkey, name: undefined }],\n });\n // Force the legacy path to look empty if it were ever consulted.\n mockProvider.getLegacyAccounts.mockReturnValue({\n match: async (\n onOk: (v: RawAccountTest[]) => unknown,\n _onErr: (e: unknown) => unknown,\n ) => onOk([]),\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider)),\n productAccount: { dotNsIdentifier: \"playground.dot\" },\n });\n const result = await provider.connect();\n\n expect(result.ok).toBe(true);\n if (result.ok) {\n expect(result.value).toHaveLength(1);\n expect(result.value[0].publicKey).toEqual(productPubkey);\n }\n });\n });\n\n describe(\"ChainSubmit permission request\", () => {\n test(\"sends a v1 ChainSubmit request (regression guard for the TransactionSubmit bug)\", async () => {\n const captured: unknown[] = [];\n const hostApi: HostApiPermissionBridge = {\n permission: (request) => {\n captured.push(request);\n return fakeResult(undefined);\n },\n };\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: new Uint8Array(32).fill(0x01) }],\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider, { hostApi })),\n loadHostApiEnum: () => Promise.resolve(fakeHostApiEnum),\n });\n\n await provider.connect();\n\n expect(captured).toHaveLength(1);\n // The fake hostApiEnum returns `{ version, value }` so we can\n // assert on the exact wire shape that would reach\n // host-api's RemotePermission codec.\n expect(captured[0]).toEqual({\n version: \"v1\",\n value: { tag: \"ChainSubmit\", value: undefined },\n });\n });\n\n test(\"does NOT send a TransactionSubmit tag (the bug)\", async () => {\n const captured: unknown[] = [];\n const hostApi: HostApiPermissionBridge = {\n permission: (request) => {\n captured.push(request);\n return fakeResult(undefined);\n },\n };\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: new Uint8Array(32).fill(0x01) }],\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider, { hostApi })),\n loadHostApiEnum: () => Promise.resolve(fakeHostApiEnum),\n });\n\n await provider.connect();\n\n const sent = JSON.stringify(captured[0]);\n expect(sent).not.toContain(\"TransactionSubmit\");\n });\n\n test(\"skipped when sdk.hostApi is unavailable (older product-sdk)\", async () => {\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: new Uint8Array(32).fill(0x01) }],\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider /* no hostApi */)),\n loadHostApiEnum: () => Promise.resolve(fakeHostApiEnum),\n });\n\n const result = await provider.connect();\n // Connect should succeed even without the hostApi bridge —\n // permission is best-effort.\n expect(result.ok).toBe(true);\n });\n\n test(\"skipped when requestChainSubmitPermission is false\", async () => {\n const captured: unknown[] = [];\n const hostApi: HostApiPermissionBridge = {\n permission: (request) => {\n captured.push(request);\n return fakeResult(undefined);\n },\n };\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: new Uint8Array(32).fill(0x01) }],\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider, { hostApi })),\n loadHostApiEnum: () => Promise.resolve(fakeHostApiEnum),\n requestChainSubmitPermission: false,\n });\n\n await provider.connect();\n expect(captured).toHaveLength(0);\n });\n\n test(\"deprecated requestTransactionSubmitPermission alias still controls the request\", async () => {\n const captured: unknown[] = [];\n const hostApi: HostApiPermissionBridge = {\n permission: (request) => {\n captured.push(request);\n return fakeResult(undefined);\n },\n };\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: new Uint8Array(32).fill(0x01) }],\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider, { hostApi })),\n loadHostApiEnum: () => Promise.resolve(fakeHostApiEnum),\n // Old name; new code path should still respect it as `false`.\n requestTransactionSubmitPermission: false,\n });\n\n await provider.connect();\n expect(captured).toHaveLength(0);\n });\n\n test(\"connect succeeds even when permission request rejects\", async () => {\n // Whatever the host says about permission, connect() should\n // still return ok — the consumer can sign later with whatever\n // permission they negotiate.\n const hostApi: HostApiPermissionBridge = {\n permission: () => fakeResult(undefined, { tag: \"PermissionDenied\" }),\n };\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: new Uint8Array(32).fill(0x01) }],\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider, { hostApi })),\n loadHostApiEnum: () => Promise.resolve(fakeHostApiEnum),\n });\n\n const result = await provider.connect();\n expect(result.ok).toBe(true);\n });\n\n test(\"connect succeeds even when the hostApiEnum loader throws (codec drift)\", async () => {\n // The original bug: the v1 RemotePermission codec didn't\n // recognize the TransactionSubmit tag and threw client-side.\n // Even when something like that happens, connect() must\n // remain ok — permission is best-effort.\n const hostApi: HostApiPermissionBridge = {\n permission: () => fakeResult(undefined),\n };\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: new Uint8Array(32).fill(0x01) }],\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider, { hostApi })),\n loadHostApiEnum: () => Promise.reject(new Error(\"codec drift\")),\n });\n\n const result = await provider.connect();\n expect(result.ok).toBe(true);\n });\n });\n\n describe(\"formatError\", () => {\n // Direct unit tests for the helper. The previous implementation\n // collapsed any tagged-enum error to its outer tag — losing the\n // inner reason. The fix surfaces the inner Error-shape (name +\n // message) and recurses through nested tagged enums.\n\n test(\"returns a string for a primitive error\", () => {\n expect(formatError(\"Rejected\")).toBe(\"Rejected\");\n expect(formatError(42)).toBe(\"42\");\n expect(formatError(null)).toBe(\"null\");\n expect(formatError(undefined)).toBe(\"undefined\");\n });\n\n test(\"surfaces inner Error name + message under the outer tag\", () => {\n // Simulates the exact shape the original bug produced:\n // `{ tag: \"v1\", value: { name: \"GenericError\", message: \"...\" } }`\n const wrapped = {\n tag: \"v1\",\n value: {\n name: \"GenericError\",\n message: \"Unknown error: inner[tag] is not a function\",\n },\n };\n const out = formatError(wrapped);\n expect(out).toContain(\"v1\");\n expect(out).toContain(\"GenericError\");\n expect(out).toContain(\"inner[tag] is not a function\");\n });\n\n test(\"strips the redundant 'Error' name when the inner is a plain Error\", () => {\n const wrapped = {\n tag: \"v1\",\n value: { name: \"Error\", message: \"boom\" },\n };\n expect(formatError(wrapped)).toBe(\"v1 → boom\");\n });\n\n test(\"recurses through nested tagged-enum errors\", () => {\n const wrapped = {\n tag: \"v1\",\n value: { tag: \"Inner\", value: { name: \"NestedErr\", message: \"deep\" } },\n };\n expect(formatError(wrapped)).toContain(\"v1\");\n expect(formatError(wrapped)).toContain(\"Inner\");\n expect(formatError(wrapped)).toContain(\"NestedErr\");\n expect(formatError(wrapped)).toContain(\"deep\");\n });\n\n test(\"returns just the outer tag when value is undefined\", () => {\n expect(formatError({ tag: \"PermissionDenied\" })).toBe(\"PermissionDenied\");\n });\n\n test(\"formats a primitive inner value alongside the tag\", () => {\n expect(formatError({ tag: \"v1\", value: \"code-42\" })).toBe(\"v1 (code-42)\");\n });\n });\n\n describe(\"RemotePermission codec interop\", () => {\n // Smoke test that the wire payload we build (`ChainSubmit`) round-trips\n // through the real host-api codec. The previous bug shipped\n // `TransactionSubmit`, which the codec rejects — locking this in here\n // catches a regression at the codec layer without needing the host.\n test(\"encodes ChainSubmit payload without throwing\", async () => {\n const { RemotePermission } = await import(\"@novasamatech/host-api\");\n const payload = { tag: \"ChainSubmit\" as const, value: undefined };\n const encoded = RemotePermission.enc(payload);\n expect(encoded).toBeInstanceOf(Uint8Array);\n const decoded = RemotePermission.dec(encoded);\n expect(decoded.tag).toBe(\"ChainSubmit\");\n });\n\n test(\"rejects the legacy TransactionSubmit tag\", async () => {\n const { RemotePermission } = await import(\"@novasamatech/host-api\");\n // `TransactionSubmit` is not a valid variant in v1 — the codec\n // should refuse to encode it. This proves the codec actually\n // validates tags (so test 1 isn't a tautology).\n expect(() =>\n RemotePermission.enc({\n tag: \"TransactionSubmit\",\n value: undefined,\n } as never),\n ).toThrow();\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport type { PolkadotSigner } from \"polkadot-api\";\n\nimport { createLogger } from \"@parity/product-sdk-logger\";\n\nimport {\n AccountNotFoundError,\n DestroyedError,\n HostDisconnectedError,\n HostUnavailableError,\n SigningFailedError,\n type SignerError,\n} from \"./errors.js\";\nimport { getHostLocalStorage, requestResourceAllocation } from \"@parity/product-sdk-host\";\nimport { DevProvider } from \"./providers/dev.js\";\nimport { HostProvider } from \"./providers/host.js\";\nimport type { ContextualAlias, ProductAccount, RingLocation } from \"./providers/host.js\";\nimport type { SignerProvider } from \"./providers/types.js\";\nimport { withRetry } from \"./retry.js\";\nimport type {\n AccountPersistence,\n ConnectContext,\n ConnectionStatus,\n OnConnect,\n ProviderType,\n Result,\n SignerAccount,\n SignerManagerOptions,\n SignerState,\n} from \"./types.js\";\nimport { err, ok } from \"./types.js\";\n\nconst log = createLogger(\"signer\");\n\nconst DEFAULT_HOST_TIMEOUT = 10_000;\nconst DEFAULT_MAX_RETRIES = 3;\nconst DEFAULT_SS58_PREFIX = 42;\nconst DEFAULT_DAPP_NAME = \"product-sdk\";\n\n// Auto-reconnect settings for host disconnect events\nconst RECONNECT_MAX_ATTEMPTS = 5;\nconst RECONNECT_INITIAL_DELAY = 1_000;\nconst RECONNECT_MAX_DELAY = 15_000;\n\nfunction persistenceStorageKey(dappName: string): string {\n return `product-sdk:signer:${dappName}:selectedAccount`;\n}\n\n/* @integration */\n/**\n * Auto-detect the best available persistence adapter.\n *\n * Uses hostLocalStorage from the host container. Returns null if unavailable.\n */\nasync function detectPersistence(): Promise<AccountPersistence | null> {\n try {\n const hostStorage = await getHostLocalStorage();\n if (hostStorage) {\n log.debug(\"using hostLocalStorage for persistence\");\n return {\n getItem: (key) => hostStorage.readString(key),\n setItem: (key, value) => hostStorage.writeString(key, value),\n removeItem: (key) => hostStorage.writeString(key, \"\"),\n };\n }\n } catch {\n // host storage not available\n }\n return null;\n}\n\nfunction initialState(): SignerState {\n return {\n status: \"disconnected\",\n accounts: [],\n selectedAccount: null,\n activeProvider: null,\n error: null,\n };\n}\n\nfunction resolveSelectedAccount(\n accounts: readonly SignerAccount[],\n preferredAddress: string | null | undefined,\n): SignerAccount | null {\n if (preferredAddress) {\n const found = accounts.find((a) => a.address === preferredAddress);\n if (found) return found;\n }\n return accounts[0] ?? null;\n}\n\n/**\n * Core orchestrator for signer management.\n *\n * Manages account discovery and signer creation via the Host API.\n * Framework-agnostic — use the `subscribe()` pattern to integrate with\n * React, Vue, or any framework.\n *\n * ## Lifecycle\n *\n * ```\n * disconnected → connecting → connected ──── selectAccount / signRaw / …\n * ▲ │ │\n * │ ▼ ▼\n * └── disconnect() provider drops → auto-reconnect → connected\n * (onConnect re-fires)\n *\n * ┌─ destroy() ──► (terminal — manager unusable)\n * ▼\n * ```\n *\n * ## Callbacks\n *\n * - **`subscribe(cb)`** fires synchronously on every state mutation, in\n * registration order, inside the call stack that mutated state. It does\n * **not** fire with the initial state — call `getState()` if you need a\n * priming read. Multiple mutations during the same operation produce\n * multiple notifications.\n *\n * - **`onConnect(account, ctx)`** (from `SignerManagerOptions`) fires\n * exactly when the manager transitions from non-connected to\n * `\"connected\"` with a selected account. It fires on a microtask\n * *after* the `subscribe` notification, so subscribers always observe\n * `state.status === \"connected\"` before `onConnect`'s side effects run.\n * It re-fires after auto-reconnect (the SDK reconnects automatically\n * when the provider drops), and re-fires after a fresh `connect()`.\n *\n * - **Internal `onAccountsChange` wiring** is worth a behavioral note:\n * when the provider reports an updated account list, the manager\n * preserves the current selection if its address is still present, or\n * sets `selectedAccount` to `null` if it isn't — it does **not** fall\n * back to `accounts[0]`. The fallback-to-first only applies on\n * connect-success, where there is no prior selection to preserve.\n *\n * ## `disconnect()` vs `destroy()`\n *\n * - `disconnect()` resets state to initial. Subsequent `connect()` calls\n * work normally. Reversible.\n * - `destroy()` is **terminal**: the instance is marked unusable, all\n * subscribers are cleared, and any further call returns `DestroyedError`.\n * Use in framework teardown (React `useEffect` cleanup, HMR dispose).\n *\n * Both methods cancel any in-flight `connect`, reconnect attempt, and\n * `onConnect` callback (the `ctx.signal` becomes aborted).\n *\n * @example\n * ```ts\n * const manager = new SignerManager({\n * onConnect: async (_account, { requestResourceAllocation }) => {\n * await requestResourceAllocation([{ tag: \"AutoSigning\", value: undefined }]);\n * },\n * });\n * manager.subscribe(state => console.log(state.status));\n *\n * await manager.connect(); // host provider (default)\n * await manager.connect(\"dev\"); // Alice / Bob / … for testing\n *\n * manager.selectAccount(\"5GrwvaEF…\");\n * const signer = manager.getSigner();\n * ```\n */\nexport class SignerManager {\n private state: SignerState;\n private provider: SignerProvider | null = null;\n private subscribers = new Set<(state: SignerState) => void>();\n private cleanups: (() => void)[] = [];\n private isDestroyed = false;\n private reconnectController: AbortController | null = null;\n private connectController: AbortController | null = null;\n\n private readonly ss58Prefix: number;\n private readonly hostTimeout: number;\n private readonly maxRetries: number;\n private readonly providerFactory: ((type: ProviderType) => SignerProvider) | undefined;\n private readonly dappName: string;\n private readonly persistenceOption: AccountPersistence | null | undefined;\n private resolvedPersistence: AccountPersistence | null | undefined;\n private readonly onConnectCallback: OnConnect | undefined;\n private onConnectController: AbortController | null = null;\n\n constructor(options?: SignerManagerOptions) {\n this.ss58Prefix = options?.ss58Prefix ?? DEFAULT_SS58_PREFIX;\n this.hostTimeout = options?.hostTimeout ?? DEFAULT_HOST_TIMEOUT;\n this.maxRetries = options?.maxRetries ?? DEFAULT_MAX_RETRIES;\n this.providerFactory = options?.createProvider;\n this.dappName = options?.dappName ?? DEFAULT_DAPP_NAME;\n // null = disabled, undefined = auto-detect, AccountPersistence = explicit\n this.persistenceOption = options?.persistence;\n this.resolvedPersistence = options?.persistence;\n this.onConnectCallback = options?.onConnect;\n this.state = initialState();\n }\n\n private async getPersistence(): Promise<AccountPersistence | null> {\n if (this.persistenceOption === null) return null;\n if (this.persistenceOption !== undefined) return this.persistenceOption;\n // Auto-detect (lazy, cached)\n if (this.resolvedPersistence === undefined) {\n this.resolvedPersistence = await detectPersistence();\n }\n return this.resolvedPersistence ?? null;\n }\n\n /** Get a snapshot of the current state. */\n getState(): SignerState {\n return this.state;\n }\n\n /**\n * Subscribe to state changes.\n *\n * The callback fires synchronously on every state mutation, in\n * registration order, inside the call stack that mutated state. It\n * does **not** fire with the current state at subscription time —\n * call {@link getState} if you need a priming read.\n *\n * For \"fired once when the user connects\" semantics, prefer the\n * {@link SignerManagerOptions.onConnect} option instead of gating on\n * `state.status` inside this callback — `subscribe` will fire many\n * times while connected (`selectAccount`, account swaps, etc.).\n *\n * @returns an unsubscribe function.\n */\n subscribe(callback: (state: SignerState) => void): () => void {\n this.subscribers.add(callback);\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n /**\n * Connect to a provider.\n *\n * If no provider type is specified, connects to the Host API.\n * The SDK is designed to run exclusively inside a host container.\n *\n * When connecting to a specific provider type:\n * - `\"host\"`: Connect to the Host API (default, recommended)\n * - `\"dev\"`: Connect using dev accounts (for testing)\n */\n async connect(providerType?: ProviderType): Promise<Result<SignerAccount[], SignerError>> {\n if (this.isDestroyed) {\n return err(new DestroyedError());\n }\n\n // Cancel any in-flight connection or reconnect attempt\n this.cancelConnect();\n this.cancelReconnect();\n this.cancelOnConnect();\n this.connectController = new AbortController();\n const signal = this.connectController.signal;\n\n // Clean up previous connection\n this.disconnectInternal();\n\n this.setState({ status: \"connecting\", error: null });\n\n // Default to host provider - the SDK is designed for container-only usage\n const targetProvider = providerType ?? \"host\";\n return this.connectToProvider(targetProvider, signal);\n }\n\n /**\n * Disconnect from the current provider and reset state to initial.\n *\n * Reversible — subsequent `connect()` calls work normally. Cancels\n * any in-flight `connect`, reconnect attempt, or `onConnect` callback\n * (`ctx.signal` becomes aborted).\n */\n disconnect(): void {\n this.cancelConnect();\n this.cancelReconnect();\n this.cancelOnConnect();\n this.disconnectInternal();\n this.setState(initialState());\n log.info(\"disconnected\");\n }\n\n /**\n * Select an account by address.\n * Returns the account on success, or ACCOUNT_NOT_FOUND error.\n */\n selectAccount(address: string): Result<SignerAccount, SignerError> {\n if (this.isDestroyed) {\n return err(new DestroyedError());\n }\n\n const account = this.state.accounts.find((a) => a.address === address);\n if (!account) {\n log.warn(\"account not found\", { address });\n return err(new AccountNotFoundError(address));\n }\n\n this.setState({ selectedAccount: account });\n this.persistAccount(address);\n log.debug(\"account selected\", { address });\n return ok(account);\n }\n\n /**\n * Get the PolkadotSigner for the currently selected account.\n * Returns null if no account is selected or manager is disconnected.\n */\n getSigner(): PolkadotSigner | null {\n return this.state.selectedAccount?.getSigner() ?? null;\n }\n\n /**\n * Sign arbitrary bytes with the currently selected account.\n *\n * Convenience wrapper around `PolkadotSigner.signBytes` — useful for\n * master key derivation, message signing, and proof generation without\n * constructing a full transaction.\n *\n * Returns a SIGNING_FAILED error if no account is selected or signing fails.\n */\n async signRaw(data: Uint8Array): Promise<Result<Uint8Array, SignerError>> {\n if (this.isDestroyed) {\n return err(new DestroyedError());\n }\n\n const signer = this.getSigner();\n if (!signer) {\n return err(new SigningFailedError(null, \"No account selected\"));\n }\n\n try {\n const signature = await signer.signBytes(data);\n return ok(signature);\n } catch (cause) {\n log.error(\"signRaw failed\", { cause });\n return err(new SigningFailedError(cause));\n }\n }\n\n // ── Host-only: Product Account API ─────────────────────────────\n\n /**\n * Get an app-scoped product account from the host.\n *\n * Product accounts are derived by the host wallet for each app, identified\n * by `dotNsIdentifier` (e.g., \"mark3t.dot\"). Only available when connected\n * via the host provider — returns HOST_UNAVAILABLE otherwise.\n *\n * @example\n * ```ts\n * const result = await manager.getProductAccount(\"myapp.dot\");\n * if (result.ok) {\n * const signer = result.value.getSigner();\n * }\n * ```\n */\n async getProductAccount(\n dotNsIdentifier: string,\n derivationIndex = 0,\n ): Promise<Result<SignerAccount, SignerError>> {\n if (this.isDestroyed) return err(new DestroyedError());\n\n const host = this.getHostProvider();\n if (!host) {\n return err(\n new HostUnavailableError(\"Product accounts require a host provider connection\"),\n );\n }\n return host.getProductAccount(dotNsIdentifier, derivationIndex);\n }\n\n /**\n * Get a contextual alias for a product account via Ring VRF.\n *\n * Aliases prove account membership in a ring without revealing which\n * account produced the alias. Only available when connected via the host\n * provider — returns HOST_UNAVAILABLE otherwise.\n */\n async getProductAccountAlias(\n dotNsIdentifier: string,\n derivationIndex = 0,\n ): Promise<Result<ContextualAlias, SignerError>> {\n if (this.isDestroyed) return err(new DestroyedError());\n\n const host = this.getHostProvider();\n if (!host) {\n return err(\n new HostUnavailableError(\n \"Product account aliases require a host provider connection\",\n ),\n );\n }\n return host.getProductAccountAlias(dotNsIdentifier, derivationIndex);\n }\n\n /**\n * Create a Ring VRF proof for anonymous operations.\n *\n * Proves that the signer is a member of the ring at the given location\n * without revealing which member. Only available when connected via the\n * host provider — returns HOST_UNAVAILABLE otherwise.\n */\n async createRingVRFProof(\n dotNsIdentifier: string,\n derivationIndex: number,\n location: RingLocation,\n message: Uint8Array,\n ): Promise<Result<Uint8Array, SignerError>> {\n if (this.isDestroyed) return err(new DestroyedError());\n\n const host = this.getHostProvider();\n if (!host) {\n return err(\n new HostUnavailableError(\"Ring VRF proofs require a host provider connection\"),\n );\n }\n return host.createRingVRFProof(dotNsIdentifier, derivationIndex, location, message);\n }\n\n /**\n * Destroy the manager and release all resources.\n *\n * **Terminal** — clears all subscribers, cancels in-flight work, and\n * marks the instance unusable. Any subsequent method returns\n * `DestroyedError`. Idempotent. Use in framework teardown (React\n * `useEffect` cleanup, HMR dispose). For a reversible reset, use\n * {@link disconnect} instead.\n */\n destroy(): void {\n if (this.isDestroyed) return;\n this.isDestroyed = true;\n this.cancelConnect();\n this.cancelReconnect();\n this.cancelOnConnect();\n this.disconnectInternal();\n this.subscribers.clear();\n this.state = initialState();\n log.info(\"manager destroyed\");\n }\n\n // ── Private ──────────────────────────────────────────────────────\n\n private async connectToProvider(\n type: ProviderType,\n signal?: AbortSignal,\n ): Promise<Result<SignerAccount[], SignerError>> {\n const provider = this.createProvider(type);\n\n const result = await provider.connect(signal);\n if (!result.ok) {\n provider.disconnect();\n this.setState({ status: \"disconnected\", error: result.error });\n return result;\n }\n\n // Success — set up provider\n this.provider = provider;\n\n // Wire status change listener\n const statusUnsub = provider.onStatusChange((status) => {\n this.handleProviderStatusChange(status);\n });\n this.cleanups.push(statusUnsub);\n\n // Wire account change listener\n const accountUnsub = provider.onAccountsChange((accounts) => {\n this.setState({\n accounts,\n // Clear selected if no longer in list\n selectedAccount:\n accounts.find((a) => a.address === this.state.selectedAccount?.address) ?? null,\n });\n });\n this.cleanups.push(accountUnsub);\n\n const accounts = result.value;\n\n const persisted = await this.loadPersistedAccount();\n const selectedAccount = resolveSelectedAccount(accounts, persisted);\n\n this.setState({\n status: \"connected\",\n accounts,\n activeProvider: type,\n selectedAccount,\n error: null,\n });\n\n if (selectedAccount) {\n this.persistAccount(selectedAccount.address);\n this.fireOnConnect(selectedAccount);\n }\n\n log.info(\"connected\", { provider: type, accounts: accounts.length });\n return result;\n }\n\n private createProvider(type: ProviderType): SignerProvider {\n if (this.providerFactory) {\n return this.providerFactory(type);\n }\n\n switch (type) {\n case \"host\":\n return new HostProvider({\n ss58Prefix: this.ss58Prefix,\n maxRetries: this.maxRetries,\n retryDelay: 500,\n });\n case \"dev\":\n return new DevProvider({\n ss58Prefix: this.ss58Prefix,\n });\n default:\n throw new Error(\n `Unsupported provider type: ${type}. The SDK only supports \"host\" and \"dev\" providers.`,\n );\n }\n }\n\n /* @integration */\n private handleProviderStatusChange(status: ConnectionStatus): void {\n if (status === \"disconnected\" && this.state.status === \"connected\") {\n log.warn(\"provider disconnected, attempting reconnect\");\n this.attemptReconnect();\n }\n }\n\n /* @integration */\n private attemptReconnect(): void {\n this.cancelReconnect();\n\n const providerType = this.state.activeProvider;\n if (!providerType) return;\n\n this.reconnectController = new AbortController();\n const signal = this.reconnectController.signal;\n\n this.setState({ status: \"connecting\" });\n\n withRetry(\n async () => {\n if (signal.aborted) {\n return err(new HostDisconnectedError(\"Reconnect cancelled\"));\n }\n\n this.disconnectInternal();\n const provider = this.createProvider(providerType);\n\n // Compose hostTimeout with reconnect signal for host providers\n const connectSignal =\n providerType === \"host\"\n ? AbortSignal.any([signal, AbortSignal.timeout(this.hostTimeout)])\n : signal;\n const result = await provider.connect(connectSignal);\n\n if (!result.ok) return result;\n\n // Re-wire provider\n this.provider = provider;\n const statusUnsub = provider.onStatusChange((s) =>\n this.handleProviderStatusChange(s),\n );\n this.cleanups.push(statusUnsub);\n\n const accountUnsub = provider.onAccountsChange((accounts) => {\n this.setState({\n accounts,\n selectedAccount:\n accounts.find(\n (a) => a.address === this.state.selectedAccount?.address,\n ) ?? null,\n });\n });\n this.cleanups.push(accountUnsub);\n\n const accounts = result.value;\n const selectedAccount = resolveSelectedAccount(\n accounts,\n this.state.selectedAccount?.address,\n );\n this.setState({\n status: \"connected\",\n accounts,\n activeProvider: providerType,\n selectedAccount,\n error: null,\n });\n\n if (selectedAccount) {\n this.fireOnConnect(selectedAccount);\n }\n\n log.info(\"reconnected\", { provider: providerType });\n return result;\n },\n {\n maxAttempts: RECONNECT_MAX_ATTEMPTS,\n initialDelay: RECONNECT_INITIAL_DELAY,\n maxDelay: RECONNECT_MAX_DELAY,\n signal,\n },\n )\n .then((result) => {\n if (!result.ok && !signal.aborted) {\n log.error(\"reconnect failed after all retries\", { error: result.error });\n this.setState({\n status: \"disconnected\",\n error: new HostDisconnectedError(\"Reconnect failed after all retries\"),\n });\n }\n })\n .catch((cause) => {\n log.error(\"unexpected reconnect error\", { cause });\n this.setState({\n status: \"disconnected\",\n error: new HostDisconnectedError(\"Reconnect failed unexpectedly\"),\n });\n });\n }\n\n /** Returns the underlying HostProvider if connected via host, or null otherwise. */\n private getHostProvider(): HostProvider | null {\n if (this.provider && this.state.activeProvider === \"host\") {\n return this.provider as HostProvider;\n }\n return null;\n }\n\n private cancelConnect(): void {\n this.connectController?.abort();\n this.connectController = null;\n }\n\n private cancelReconnect(): void {\n this.reconnectController?.abort();\n this.reconnectController = null;\n }\n\n private cancelOnConnect(): void {\n this.onConnectController?.abort();\n this.onConnectController = null;\n }\n\n private fireOnConnect(account: SignerAccount): void {\n const callback = this.onConnectCallback;\n if (!callback) return;\n\n this.cancelOnConnect();\n const controller = new AbortController();\n this.onConnectController = controller;\n\n const ctx: ConnectContext = {\n signal: controller.signal,\n requestResourceAllocation,\n };\n\n // Defer so connect()/attemptReconnect() return before the callback fires —\n // subscribers see \"connected\" before any onConnect side-effects land.\n queueMicrotask(async () => {\n try {\n await callback(account, ctx);\n } catch (cause) {\n log.warn(\"onConnect callback threw\", { cause });\n } finally {\n // Only clear if this controller is still the active one; a\n // subsequent re-connect may have already swapped in a new one.\n if (this.onConnectController === controller) {\n this.onConnectController = null;\n }\n }\n });\n }\n\n private disconnectInternal(): void {\n for (const cleanup of this.cleanups) {\n cleanup();\n }\n this.cleanups = [];\n\n if (this.provider) {\n this.provider.disconnect();\n this.provider = null;\n }\n }\n\n private persistAccount(address: string): void {\n void this.getPersistence()\n .then((p) => {\n if (p) {\n const key = persistenceStorageKey(this.dappName);\n return Promise.resolve(p.setItem(key, address));\n }\n })\n .catch(() => {\n log.debug(\"failed to persist selected account\");\n });\n }\n\n private async loadPersistedAccount(): Promise<string | null> {\n try {\n const p = await this.getPersistence();\n if (!p) return null;\n const key = persistenceStorageKey(this.dappName);\n const value = await Promise.resolve(p.getItem(key));\n // Treat empty strings as null (hostLocalStorage uses writeString(\"\") for deletion)\n return value || null;\n } catch {\n log.debug(\"failed to load persisted account\");\n return null;\n }\n }\n\n private setState(patch: Partial<SignerState>): void {\n this.state = { ...this.state, ...patch };\n for (const subscriber of this.subscribers) {\n subscriber(this.state);\n }\n }\n}\n\nif (import.meta.vitest) {\n const { test, expect, describe, vi } = import.meta.vitest;\n\n function mockAccount(\n address = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\",\n ): SignerAccount {\n return {\n address,\n h160Address: \"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\",\n publicKey: new Uint8Array(32),\n name: \"MockAccount\",\n source: \"dev\",\n getSigner: () => ({ publicKey: new Uint8Array(32) }) as never,\n };\n }\n\n function mockProvider(accounts: SignerAccount[] = [mockAccount()]) {\n return {\n type: \"dev\" as const,\n connect: vi.fn().mockResolvedValue(ok(accounts)),\n disconnect: vi.fn(),\n onStatusChange: vi.fn().mockReturnValue(() => {}),\n onAccountsChange: vi.fn().mockReturnValue(() => {}),\n } satisfies SignerProvider;\n }\n\n /** Wait for the `onConnect` microtask chain to drain. */\n async function flush(): Promise<void> {\n await Promise.resolve();\n await Promise.resolve();\n }\n\n describe(\"SignerManager.onConnect\", () => {\n test(\"fires once on initial connect with the selected account\", async () => {\n const onConnect = vi.fn();\n const manager = new SignerManager({\n createProvider: () => mockProvider(),\n onConnect,\n });\n const result = await manager.connect(\"dev\");\n await flush();\n expect(result.ok).toBe(true);\n expect(onConnect).toHaveBeenCalledTimes(1);\n expect(onConnect.mock.calls[0][0].address).toBe(\n \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\",\n );\n manager.destroy();\n });\n\n test(\"does not fire on subsequent state mutations while connected\", async () => {\n const onConnect = vi.fn();\n const manager = new SignerManager({\n createProvider: () => mockProvider(),\n onConnect,\n });\n await manager.connect(\"dev\");\n await flush();\n // Mutate state — selecting the same account again.\n manager.selectAccount(\"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\");\n await flush();\n expect(onConnect).toHaveBeenCalledTimes(1);\n manager.destroy();\n });\n\n test(\"fires again after disconnect + reconnect\", async () => {\n const onConnect = vi.fn();\n const manager = new SignerManager({\n createProvider: () => mockProvider(),\n onConnect,\n });\n await manager.connect(\"dev\");\n await flush();\n manager.disconnect();\n await manager.connect(\"dev\");\n await flush();\n expect(onConnect).toHaveBeenCalledTimes(2);\n manager.destroy();\n });\n\n test(\"does not fire when no account is selected (empty accounts list)\", async () => {\n const onConnect = vi.fn();\n const manager = new SignerManager({\n createProvider: () => mockProvider([]),\n onConnect,\n });\n await manager.connect(\"dev\");\n await flush();\n expect(onConnect).not.toHaveBeenCalled();\n manager.destroy();\n });\n\n test(\"errors thrown from onConnect are caught and don't break connected state\", async () => {\n const onConnect = vi.fn().mockRejectedValue(new Error(\"boom\"));\n const manager = new SignerManager({\n createProvider: () => mockProvider(),\n onConnect,\n });\n const result = await manager.connect(\"dev\");\n await flush();\n expect(result.ok).toBe(true);\n expect(manager.getState().status).toBe(\"connected\");\n manager.destroy();\n });\n\n test(\"ctx.signal aborts when disconnect() runs mid-callback\", async () => {\n const signals: AbortSignal[] = [];\n const onConnect = vi.fn().mockImplementation((_, ctx) => {\n signals.push(ctx.signal);\n return new Promise<void>(() => {});\n });\n const manager = new SignerManager({\n createProvider: () => mockProvider(),\n onConnect,\n });\n await manager.connect(\"dev\");\n await flush();\n expect(signals[0]?.aborted).toBe(false);\n manager.disconnect();\n expect(signals[0]?.aborted).toBe(true);\n manager.destroy();\n });\n\n test(\"ctx.signal aborts when destroy() runs mid-callback\", async () => {\n const signals: AbortSignal[] = [];\n const onConnect = vi.fn().mockImplementation((_, ctx) => {\n signals.push(ctx.signal);\n return new Promise<void>(() => {});\n });\n const manager = new SignerManager({\n createProvider: () => mockProvider(),\n onConnect,\n });\n await manager.connect(\"dev\");\n await flush();\n manager.destroy();\n expect(signals[0]?.aborted).toBe(true);\n });\n\n test(\"ctx exposes requestResourceAllocation function\", async () => {\n const onConnect = vi.fn().mockImplementation((_, ctx) => {\n expect(typeof ctx.requestResourceAllocation).toBe(\"function\");\n });\n const manager = new SignerManager({\n createProvider: () => mockProvider(),\n onConnect,\n });\n await manager.connect(\"dev\");\n await flush();\n expect(onConnect).toHaveBeenCalledTimes(1);\n manager.destroy();\n });\n\n test(\"re-connecting cancels in-flight onConnect from the prior session\", async () => {\n const signals: AbortSignal[] = [];\n const onConnect = vi.fn().mockImplementation((_, ctx) => {\n signals.push(ctx.signal);\n return new Promise<void>(() => {});\n });\n const manager = new SignerManager({\n createProvider: () => mockProvider(),\n onConnect,\n });\n await manager.connect(\"dev\");\n await flush();\n await manager.connect(\"dev\");\n await flush();\n expect(signals).toHaveLength(2);\n expect(signals[0].aborted).toBe(true);\n expect(signals[1].aborted).toBe(false);\n manager.destroy();\n });\n });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/types.ts","../src/providers/dev.ts","../src/sleep.ts","../src/retry.ts","../src/providers/host.ts","../src/signer-manager.ts"],"names":["log","createLogger","accounts"],"mappings":"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","file":"index.js","sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport type { ProviderType } from \"./types.js\";\n\n/** Base class for all signer errors. Use `instanceof SignerError` to catch any signer-related error. */\nexport class SignerError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"SignerError\";\n }\n}\n\n/**\n * The Host API is not available.\n *\n * Common causes:\n * - The app is loaded outside a Polkadot host container (a regular browser tab\n * under `npm run dev`, no iframe, no WebView). This is the dominant case\n * during local development.\n * - The optional `@novasamatech/host-api(-wrapper)` peer is not installed.\n *\n * Branch with `instanceof HostUnavailableError` to surface a \"open this app\n * in a Polkadot host, or pick a dev provider\" message to the user.\n */\nexport class HostUnavailableError extends SignerError {\n constructor(message = \"Host API is not available\") {\n super(message);\n this.name = \"HostUnavailableError\";\n }\n}\n\n/** The host rejected the account or signing request. */\nexport class HostRejectedError extends SignerError {\n constructor(message = \"Host rejected the request\") {\n super(message);\n this.name = \"HostRejectedError\";\n }\n}\n\n/** The host connection was lost. */\nexport class HostDisconnectedError extends SignerError {\n constructor(message = \"Host connection lost\") {\n super(message);\n this.name = \"HostDisconnectedError\";\n }\n}\n\n/** A signing operation failed. */\nexport class SigningFailedError extends SignerError {\n constructor(cause: unknown, message?: string) {\n super(\n message ?? `Signing failed: ${cause instanceof Error ? cause.message : String(cause)}`,\n { cause },\n );\n this.name = \"SigningFailedError\";\n }\n}\n\n/** No accounts available from the provider. */\nexport class NoAccountsError extends SignerError {\n readonly provider: ProviderType;\n\n constructor(provider: ProviderType, message?: string) {\n super(message ?? `No accounts available from ${provider} provider`);\n this.name = \"NoAccountsError\";\n this.provider = provider;\n }\n}\n\n/** An operation timed out. */\nexport class TimeoutError extends SignerError {\n readonly operation: string;\n readonly ms: number;\n\n constructor(operation: string, ms: number) {\n super(`Operation \"${operation}\" timed out after ${ms}ms`);\n this.name = \"TimeoutError\";\n this.operation = operation;\n this.ms = ms;\n }\n}\n\n/** An account was not found by address. */\nexport class AccountNotFoundError extends SignerError {\n readonly address: string;\n\n constructor(address: string) {\n super(`Account not found: ${address}`);\n this.name = \"AccountNotFoundError\";\n this.address = address;\n }\n}\n\n/** The SignerManager has been destroyed and is no longer usable. */\nexport class DestroyedError extends SignerError {\n constructor() {\n super(\"SignerManager has been destroyed\");\n this.name = \"DestroyedError\";\n }\n}\n\n// ── Type guards ──────────────────────────────────────────────────────\n\n/** Check if a SignerError is a host-related error. */\nexport function isHostError(\n e: SignerError,\n): e is HostUnavailableError | HostRejectedError | HostDisconnectedError {\n return (\n e instanceof HostUnavailableError ||\n e instanceof HostRejectedError ||\n e instanceof HostDisconnectedError\n );\n}\n\nif (import.meta.vitest) {\n const { test, expect, describe } = import.meta.vitest;\n\n describe(\"error classes\", () => {\n test(\"SignerError is the base class\", () => {\n const e = new HostUnavailableError();\n expect(e).toBeInstanceOf(SignerError);\n expect(e).toBeInstanceOf(Error);\n });\n\n test(\"HostUnavailableError with default message\", () => {\n const e = new HostUnavailableError();\n expect(e.name).toBe(\"HostUnavailableError\");\n expect(e.message).toBe(\"Host API is not available\");\n });\n\n test(\"HostUnavailableError with custom message\", () => {\n const e = new HostUnavailableError(\"custom\");\n expect(e.message).toBe(\"custom\");\n });\n\n test(\"HostRejectedError\", () => {\n const e = new HostRejectedError();\n expect(e).toBeInstanceOf(SignerError);\n expect(e.message).toContain(\"rejected\");\n });\n\n test(\"HostDisconnectedError\", () => {\n const e = new HostDisconnectedError();\n expect(e).toBeInstanceOf(SignerError);\n expect(e.message).toContain(\"lost\");\n });\n\n test(\"SigningFailedError with Error cause\", () => {\n const cause = new Error(\"bad signature\");\n const e = new SigningFailedError(cause);\n expect(e).toBeInstanceOf(SignerError);\n expect(e.cause).toBe(cause);\n expect(e.message).toContain(\"bad signature\");\n });\n\n test(\"SigningFailedError with string cause\", () => {\n const e = new SigningFailedError(\"oops\");\n expect(e.message).toContain(\"oops\");\n });\n\n test(\"SigningFailedError with custom message\", () => {\n const e = new SigningFailedError(\"oops\", \"custom msg\");\n expect(e.message).toBe(\"custom msg\");\n });\n\n test(\"NoAccountsError\", () => {\n const e = new NoAccountsError(\"host\");\n expect(e).toBeInstanceOf(SignerError);\n expect(e.provider).toBe(\"host\");\n expect(e.message).toContain(\"host\");\n });\n\n test(\"NoAccountsError with custom message\", () => {\n const e = new NoAccountsError(\"dev\", \"none found\");\n expect(e.message).toBe(\"none found\");\n });\n\n test(\"TimeoutError\", () => {\n const e = new TimeoutError(\"connect\", 5000);\n expect(e).toBeInstanceOf(SignerError);\n expect(e.operation).toBe(\"connect\");\n expect(e.ms).toBe(5000);\n expect(e.message).toContain(\"5000\");\n });\n\n test(\"AccountNotFoundError\", () => {\n const e = new AccountNotFoundError(\"5GrwvaEF...\");\n expect(e).toBeInstanceOf(SignerError);\n expect(e.address).toBe(\"5GrwvaEF...\");\n });\n\n test(\"DestroyedError\", () => {\n const e = new DestroyedError();\n expect(e).toBeInstanceOf(SignerError);\n expect(e.message).toContain(\"destroyed\");\n });\n\n test(\"all errors have stack traces\", () => {\n const e = new HostUnavailableError();\n expect(e.stack).toBeDefined();\n expect(e.stack).toContain(\"HostUnavailableError\");\n });\n });\n\n describe(\"type guards\", () => {\n test(\"isHostError returns true for host errors\", () => {\n expect(isHostError(new HostUnavailableError())).toBe(true);\n expect(isHostError(new HostRejectedError())).toBe(true);\n expect(isHostError(new HostDisconnectedError())).toBe(true);\n });\n\n test(\"isHostError returns false for non-host errors\", () => {\n expect(isHostError(new SigningFailedError(\"x\"))).toBe(false);\n expect(isHostError(new NoAccountsError(\"dev\"))).toBe(false);\n expect(isHostError(new TimeoutError(\"op\", 100))).toBe(false);\n expect(isHostError(new AccountNotFoundError(\"x\"))).toBe(false);\n expect(isHostError(new DestroyedError())).toBe(false);\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport type { PolkadotSigner } from \"polkadot-api\";\n\nimport type { SS58String } from \"@parity/product-sdk-address\";\nimport type { AllocatableResource, AllocationOutcome } from \"@parity/product-sdk-host\";\n\nimport type { SignerError } from \"./errors.js\";\n\n/** Connection status for a signer provider. */\nexport type ConnectionStatus = \"disconnected\" | \"connecting\" | \"connected\";\n\n/**\n * Identifies the source of an account.\n *\n * - `\"host\"`: Host API provider (Polkadot Desktop / Mobile) — the primary provider\n * - `\"dev\"`: Development accounts (Alice, Bob, etc.) — for testing only\n */\nexport type ProviderType = \"host\" | \"dev\";\n\n/** A signing-capable account from any provider. */\nexport interface SignerAccount {\n /** SS58 address (generic prefix 42 by default). */\n address: SS58String;\n /**\n * H160 EVM address derived from the public key.\n *\n * For native Substrate accounts: keccak256(publicKey), last 20 bytes.\n * For EVM-derived accounts: strips the 0xEE padding.\n * Used for pallet-revive / Asset Hub EVM contract interactions.\n */\n h160Address: `0x${string}`;\n /** Raw public key (32 bytes). May be sr25519, ed25519, or ecdsa depending on the provider. */\n publicKey: Uint8Array;\n /** Human-readable name if available from the provider. */\n name: string | null;\n /** Which provider supplied this account. */\n source: ProviderType;\n /** Get the PolkadotSigner for this account. */\n getSigner(): PolkadotSigner;\n}\n\n/** Full state snapshot emitted to subscribers. */\nexport interface SignerState {\n /** Current connection status. */\n status: ConnectionStatus;\n /** All available accounts across all connected providers. */\n accounts: readonly SignerAccount[];\n /** Currently selected account (null if none selected). */\n selectedAccount: SignerAccount | null;\n /** Which provider is active (null if disconnected). */\n activeProvider: ProviderType | null;\n /** Last error (null if no error). */\n error: SignerError | null;\n}\n\n/** Lightweight Result type for operations that can fail expectedly. */\nexport type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };\n\n/** Create a successful Result. */\nexport function ok<T>(value: T): Result<T, never> {\n return { ok: true, value };\n}\n\n/** Create a failed Result. */\nexport function err<E>(error: E): Result<never, E> {\n return { ok: false, error };\n}\n\n/** Factory function that creates a SignerProvider for a given type. */\nexport type ProviderFactory = (type: ProviderType) => import(\"./providers/types.js\").SignerProvider;\n\n/**\n * Adapter for persisting the selected account address across sessions.\n *\n * `globalThis.localStorage` satisfies this interface. Pass a custom\n * implementation for container environments (e.g., hostLocalStorage)\n * or for testing.\n */\nexport interface AccountPersistence {\n getItem(key: string): string | null | Promise<string | null>;\n setItem(key: string, value: string): void | Promise<void>;\n removeItem(key: string): void | Promise<void>;\n}\n\n/** Options for SignerManager construction. */\nexport interface SignerManagerOptions {\n /** SS58 prefix for address encoding. Default: 42 */\n ss58Prefix?: number;\n /**\n * Maximum time in ms to wait for the Host API.\n * Applied as an AbortSignal timeout on the host provider connection.\n * Default: 10_000\n */\n hostTimeout?: number;\n /** Maximum retry attempts for provider connection. Default: 3 */\n maxRetries?: number;\n /** Custom provider factory. Override to inject test doubles or custom providers. */\n createProvider?: ProviderFactory;\n /**\n * App name used for storage key namespacing. Default: \"product-sdk\"\n * The selected account is persisted under `product-sdk:signer:{dappName}:selectedAccount`.\n */\n dappName?: string;\n /**\n * Storage adapter for persisting selected account.\n * Uses host localStorage when inside a container.\n * Set to `null` to disable persistence entirely.\n */\n persistence?: AccountPersistence | null;\n /**\n * Callback fired exactly when the manager transitions to `connected`\n * with a selected account — not on subsequent state mutations while\n * still connected. Fires again after auto-reconnect, so a fresh host\n * session re-runs the callback.\n *\n * Common use: request product resource allocations once per session.\n * The `ctx` exposes a pre-bound `requestResourceAllocation` helper\n * plus an `AbortSignal` that fires if the user disconnects or\n * destroys the manager mid-flight.\n *\n * `requestResourceAllocation` throws on failure (matches the\n * `@parity/product-sdk-host` export of the same name); errors thrown\n * from `onConnect` are logged but do not affect the connected state —\n * the next reconnect retries.\n *\n * @example\n * ```ts\n * new SignerManager({\n * onConnect: async (_account, { requestResourceAllocation, signal }) => {\n * try {\n * const outcomes = await requestResourceAllocation([\n * { tag: \"AutoSigning\", value: undefined },\n * ]);\n * if (signal.aborted) return;\n * if (outcomes.some((o) => o.tag !== \"Allocated\")) {\n * logWarning(\"partial permissions\", outcomes);\n * }\n * } catch (cause) {\n * logWarning(\"resource allocation failed\", cause);\n * }\n * },\n * });\n * ```\n */\n onConnect?: OnConnect;\n}\n\n/** Context passed to the `onConnect` callback. */\nexport interface ConnectContext {\n /**\n * Aborted when the manager disconnects or is destroyed while the\n * callback is still running. Pass through to `fetch` / cancellation\n * primitives so mid-flight work stops promptly.\n */\n signal: AbortSignal;\n /**\n * Request a batch of host resource allocations. Bound shorthand for\n * `requestResourceAllocation` from `@parity/product-sdk-host` —\n * throws on failure, returns the unwrapped outcomes on success.\n */\n requestResourceAllocation: (resources: AllocatableResource[]) => Promise<AllocationOutcome[]>;\n}\n\n/** Callback signature for {@link SignerManagerOptions.onConnect}. */\nexport type OnConnect = (account: SignerAccount, ctx: ConnectContext) => void | Promise<void>;\n\nif (import.meta.vitest) {\n const { test, expect, describe } = import.meta.vitest;\n\n describe(\"ok\", () => {\n test(\"produces ok result with value\", () => {\n const result = ok(42);\n expect(result.ok).toBe(true);\n expect(result).toEqual({ ok: true, value: 42 });\n });\n\n test(\"works with complex values\", () => {\n const result = ok({ name: \"Alice\", age: 30 });\n expect(result.ok).toBe(true);\n if (result.ok) {\n expect(result.value.name).toBe(\"Alice\");\n }\n });\n\n test(\"works with null value\", () => {\n const result = ok(null);\n expect(result).toEqual({ ok: true, value: null });\n });\n\n test(\"works with undefined value\", () => {\n const result = ok(undefined);\n expect(result).toEqual({ ok: true, value: undefined });\n });\n });\n\n describe(\"err\", () => {\n test(\"produces error result\", () => {\n const result = err(\"something went wrong\");\n expect(result.ok).toBe(false);\n expect(result).toEqual({ ok: false, error: \"something went wrong\" });\n });\n\n test(\"works with typed error objects\", () => {\n const error = { type: \"HOST_UNAVAILABLE\" as const, message: \"no host\" };\n const result = err(error);\n expect(result.ok).toBe(false);\n if (!result.ok) {\n expect(result.error.type).toBe(\"HOST_UNAVAILABLE\");\n }\n });\n });\n\n describe(\"Result type narrowing\", () => {\n test(\"ok narrows to value access\", () => {\n const result: Result<number, string> = ok(42);\n if (result.ok) {\n const value: number = result.value;\n expect(value).toBe(42);\n } else {\n expect.unreachable(\"should be ok\");\n }\n });\n\n test(\"err narrows to error access\", () => {\n const result: Result<number, string> = err(\"fail\");\n if (!result.ok) {\n const error: string = result.error;\n expect(error).toBe(\"fail\");\n } else {\n expect.unreachable(\"should be err\");\n }\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport { seedToAccount } from \"@parity/product-sdk-keys\";\nimport { createLogger } from \"@parity/product-sdk-logger\";\n\nimport type { SignerError } from \"../errors.js\";\nimport type { Result, SignerAccount } from \"../types.js\";\nimport { ok } from \"../types.js\";\nimport type { SignerProvider, Unsubscribe } from \"./types.js\";\n\nconst log = createLogger(\"signer:dev\");\n\n/** The well-known Substrate development mnemonic phrase. */\nconst DEV_PHRASE = \"bottom drive obey lake curtain smoke basket hold race lonely fit walk\";\n\n/** Standard Substrate dev account names. */\nconst DEFAULT_DEV_NAMES = [\"Alice\", \"Bob\", \"Charlie\", \"Dave\", \"Eve\", \"Ferdie\"] as const;\n\n/** A well-known Substrate development account name (Alice, Bob, …) used to derive deterministic dev accounts from the standard Substrate dev mnemonic. */\nexport type DevAccountName = (typeof DEFAULT_DEV_NAMES)[number];\n\n/** Supported key types for dev account derivation. */\nexport type DevKeyType = \"sr25519\" | \"ed25519\";\n\n/** Options for the dev account provider. */\nexport interface DevProviderOptions {\n /** Which dev accounts to create. Default: all 6 standard accounts. */\n names?: readonly string[];\n /** Custom mnemonic phrase instead of DEV_PHRASE. */\n mnemonic?: string;\n /** SS58 prefix for address encoding. Default: 42 */\n ss58Prefix?: number;\n /** Key type for account derivation. Default: \"sr25519\" */\n keyType?: DevKeyType;\n}\n\n/**\n * Provider for Substrate development accounts.\n *\n * Uses the well-known DEV_PHRASE with hard derivation paths (`//Alice`, `//Bob`, etc.)\n * to create deterministic accounts for local development and testing.\n */\nexport class DevProvider implements SignerProvider {\n readonly type = \"dev\" as const;\n private readonly names: readonly string[];\n private readonly mnemonic: string;\n private readonly ss58Prefix: number;\n private readonly keyType: DevKeyType;\n\n constructor(options?: DevProviderOptions) {\n this.names = options?.names ?? DEFAULT_DEV_NAMES;\n this.mnemonic = options?.mnemonic ?? DEV_PHRASE;\n this.ss58Prefix = options?.ss58Prefix ?? 42;\n this.keyType = options?.keyType ?? \"sr25519\";\n }\n\n async connect(): Promise<Result<SignerAccount[], SignerError>> {\n log.debug(\"creating dev accounts\", { names: this.names, keyType: this.keyType });\n\n const accounts: SignerAccount[] = this.names.map((name) => {\n const derived = seedToAccount(\n this.mnemonic,\n `//${name}`,\n this.ss58Prefix,\n this.keyType,\n );\n\n return {\n address: derived.ss58Address,\n h160Address: derived.h160Address,\n publicKey: derived.publicKey,\n name,\n source: \"dev\" as const,\n getSigner: () => derived.signer,\n };\n });\n\n log.info(\"dev accounts ready\", { count: accounts.length });\n return ok(accounts);\n }\n\n disconnect(): void {\n // Dev accounts are stateless — nothing to clean up.\n }\n\n onStatusChange(\n _callback: (status: \"disconnected\" | \"connecting\" | \"connected\") => void,\n ): Unsubscribe {\n // Dev accounts are always \"connected\" — no status changes to emit.\n return () => {};\n }\n\n onAccountsChange(_callback: (accounts: SignerAccount[]) => void): Unsubscribe {\n // Dev accounts are static — no account changes to emit.\n return () => {};\n }\n}\n\nif (import.meta.vitest) {\n const { test, expect, describe } = import.meta.vitest;\n\n // Well-known Alice address on generic substrate (prefix 42)\n const ALICE_ADDRESS = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\";\n\n describe(\"DevProvider\", () => {\n test(\"connect returns 6 accounts by default\", async () => {\n const provider = new DevProvider();\n const result = await provider.connect();\n expect(result.ok).toBe(true);\n if (result.ok) {\n expect(result.value).toHaveLength(6);\n expect(result.value.map((a) => a.name)).toEqual([\n \"Alice\",\n \"Bob\",\n \"Charlie\",\n \"Dave\",\n \"Eve\",\n \"Ferdie\",\n ]);\n }\n });\n\n test(\"all accounts have source 'dev'\", async () => {\n const provider = new DevProvider();\n const result = await provider.connect();\n if (result.ok) {\n for (const account of result.value) {\n expect(account.source).toBe(\"dev\");\n }\n }\n });\n\n test(\"Alice has well-known address\", async () => {\n const provider = new DevProvider();\n const result = await provider.connect();\n if (result.ok) {\n expect(result.value[0].address).toBe(ALICE_ADDRESS);\n }\n });\n\n test(\"addresses are deterministic\", async () => {\n const a = new DevProvider();\n const b = new DevProvider();\n const ra = await a.connect();\n const rb = await b.connect();\n if (ra.ok && rb.ok) {\n expect(ra.value.map((x) => x.address)).toEqual(rb.value.map((x) => x.address));\n }\n });\n\n test(\"each account has 32-byte publicKey\", async () => {\n const provider = new DevProvider();\n const result = await provider.connect();\n if (result.ok) {\n for (const account of result.value) {\n expect(account.publicKey).toBeInstanceOf(Uint8Array);\n expect(account.publicKey.length).toBe(32);\n }\n }\n });\n\n test(\"getSigner returns signer with matching publicKey\", async () => {\n const provider = new DevProvider();\n const result = await provider.connect();\n if (result.ok) {\n for (const account of result.value) {\n const signer = account.getSigner();\n expect(signer.publicKey).toEqual(account.publicKey);\n }\n }\n });\n\n test(\"custom names subset\", async () => {\n const provider = new DevProvider({ names: [\"Alice\", \"Bob\"] });\n const result = await provider.connect();\n if (result.ok) {\n expect(result.value).toHaveLength(2);\n expect(result.value.map((a) => a.name)).toEqual([\"Alice\", \"Bob\"]);\n }\n });\n\n test(\"custom mnemonic produces different addresses\", async () => {\n const customMnemonic =\n \"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about\";\n const defaultProvider = new DevProvider();\n const customProvider = new DevProvider({ mnemonic: customMnemonic });\n const defResult = await defaultProvider.connect();\n const cusResult = await customProvider.connect();\n if (defResult.ok && cusResult.ok) {\n expect(defResult.value[0].address).not.toBe(cusResult.value[0].address);\n }\n });\n\n test(\"custom ss58Prefix changes address encoding\", async () => {\n const generic = new DevProvider({ ss58Prefix: 42 });\n const polkadot = new DevProvider({ ss58Prefix: 0 });\n const rg = await generic.connect();\n const rp = await polkadot.connect();\n if (rg.ok && rp.ok) {\n // Different address strings\n expect(rg.value[0].address).not.toBe(rp.value[0].address);\n // Same underlying public key\n expect(rg.value[0].publicKey).toEqual(rp.value[0].publicKey);\n }\n });\n\n test(\"disconnect is idempotent\", () => {\n const provider = new DevProvider();\n // Should not throw\n provider.disconnect();\n provider.disconnect();\n });\n\n test(\"onStatusChange returns no-op unsubscribe\", () => {\n const provider = new DevProvider();\n const callback = () => {};\n const unsub = provider.onStatusChange(callback);\n expect(typeof unsub).toBe(\"function\");\n unsub(); // should not throw\n });\n\n test(\"onAccountsChange returns no-op unsubscribe\", () => {\n const provider = new DevProvider();\n const callback = () => {};\n const unsub = provider.onAccountsChange(callback);\n expect(typeof unsub).toBe(\"function\");\n unsub(); // should not throw\n });\n\n test(\"type is 'dev'\", () => {\n const provider = new DevProvider();\n expect(provider.type).toBe(\"dev\");\n });\n\n test(\"empty names array returns zero accounts\", async () => {\n const provider = new DevProvider({ names: [] });\n const result = await provider.connect();\n if (result.ok) {\n expect(result.value).toHaveLength(0);\n }\n });\n\n test(\"default keyType is sr25519 (backward compatible)\", async () => {\n const provider = new DevProvider();\n const result = await provider.connect();\n if (result.ok) {\n // Alice address matches well-known sr25519 address\n expect(result.value[0].address).toBe(ALICE_ADDRESS);\n }\n });\n });\n\n describe(\"DevProvider ed25519\", () => {\n test(\"ed25519 produces different addresses than sr25519\", async () => {\n const sr = new DevProvider({ keyType: \"sr25519\" });\n const ed = new DevProvider({ keyType: \"ed25519\" });\n const srResult = await sr.connect();\n const edResult = await ed.connect();\n if (srResult.ok && edResult.ok) {\n expect(srResult.value[0].address).not.toBe(edResult.value[0].address);\n }\n });\n\n test(\"ed25519 addresses are deterministic\", async () => {\n const a = new DevProvider({ keyType: \"ed25519\" });\n const b = new DevProvider({ keyType: \"ed25519\" });\n const ra = await a.connect();\n const rb = await b.connect();\n if (ra.ok && rb.ok) {\n expect(ra.value.map((x) => x.address)).toEqual(rb.value.map((x) => x.address));\n }\n });\n\n test(\"ed25519 getSigner has matching publicKey\", async () => {\n const provider = new DevProvider({ keyType: \"ed25519\" });\n const result = await provider.connect();\n if (result.ok) {\n for (const account of result.value) {\n const signer = account.getSigner();\n expect(signer.publicKey).toEqual(account.publicKey);\n }\n }\n });\n\n test(\"ed25519 accounts have 32-byte publicKey\", async () => {\n const provider = new DevProvider({ keyType: \"ed25519\" });\n const result = await provider.connect();\n if (result.ok) {\n for (const account of result.value) {\n expect(account.publicKey).toBeInstanceOf(Uint8Array);\n expect(account.publicKey.length).toBe(32);\n }\n }\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Sleep for a given duration, cancellable via AbortSignal.\n *\n * Resolves immediately if the signal is already aborted.\n * Cleans up the abort listener when the timer fires naturally\n * to prevent listener accumulation in retry loops.\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal?.aborted) {\n resolve();\n return;\n }\n\n const onDone = () => {\n clearTimeout(timer);\n signal?.removeEventListener(\"abort\", onDone);\n resolve();\n };\n\n const timer = setTimeout(onDone, ms);\n signal?.addEventListener(\"abort\", onDone, { once: true });\n });\n}\n\nif (import.meta.vitest) {\n const { test, expect, describe, vi, beforeEach, afterEach } = import.meta.vitest;\n\n beforeEach(() => {\n vi.useFakeTimers();\n });\n\n afterEach(() => {\n vi.useRealTimers();\n });\n\n describe(\"sleep\", () => {\n test(\"resolves after specified duration\", async () => {\n let resolved = false;\n sleep(100).then(() => {\n resolved = true;\n });\n\n expect(resolved).toBe(false);\n await vi.advanceTimersByTimeAsync(99);\n expect(resolved).toBe(false);\n await vi.advanceTimersByTimeAsync(1);\n expect(resolved).toBe(true);\n });\n\n test(\"resolves immediately when signal is already aborted\", async () => {\n const controller = new AbortController();\n controller.abort();\n\n let resolved = false;\n sleep(10_000, controller.signal).then(() => {\n resolved = true;\n });\n\n // Should resolve on next microtask, not after 10s\n await vi.advanceTimersByTimeAsync(0);\n expect(resolved).toBe(true);\n });\n\n test(\"resolves early when signal is aborted during sleep\", async () => {\n const controller = new AbortController();\n let resolved = false;\n sleep(10_000, controller.signal).then(() => {\n resolved = true;\n });\n\n await vi.advanceTimersByTimeAsync(50);\n expect(resolved).toBe(false);\n\n controller.abort();\n await vi.advanceTimersByTimeAsync(0);\n expect(resolved).toBe(true);\n });\n\n test(\"works without a signal\", async () => {\n let resolved = false;\n sleep(50).then(() => {\n resolved = true;\n });\n\n await vi.advanceTimersByTimeAsync(50);\n expect(resolved).toBe(true);\n });\n\n test(\"cleans up abort listener after natural timer expiry\", async () => {\n const controller = new AbortController();\n const addSpy = vi.spyOn(controller.signal, \"addEventListener\");\n const removeSpy = vi.spyOn(controller.signal, \"removeEventListener\");\n\n sleep(50, controller.signal);\n expect(addSpy).toHaveBeenCalledTimes(1);\n\n await vi.advanceTimersByTimeAsync(50);\n expect(removeSpy).toHaveBeenCalledTimes(1);\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Result-based retry with exponential backoff for signer connection attempts.\n *\n * This retry utility works with `Result<T, E>` return values (not exceptions).\n * It exists separately from the tx package's exception-based `withRetry` because:\n *\n * - **Signer retry**: retries connection attempts that return `Result<T, SignerError>`.\n * Errors are expected values, not exceptional conditions. Supports AbortSignal.\n * - **TX retry**: retries transaction submissions that throw exceptions.\n * Non-retryable errors (dispatch, rejection, timeout) are detected and rethrown.\n *\n * Both use exponential backoff but serve different abstraction layers.\n *\n * @module\n */\nimport { sleep } from \"./sleep.js\";\nimport type { Result } from \"./types.js\";\n\n/** Options for retry with exponential backoff. */\nexport interface RetryOptions {\n /** Maximum number of attempts. Default: 3 */\n maxAttempts?: number;\n /** Initial delay in ms before first retry. Default: 500 */\n initialDelay?: number;\n /** Multiplier applied to delay after each attempt. Default: 2 */\n backoffMultiplier?: number;\n /** Maximum delay cap in ms. Default: 10_000 */\n maxDelay?: number;\n /** AbortSignal to cancel retries early. */\n signal?: AbortSignal;\n}\n\nconst DEFAULT_MAX_ATTEMPTS = 3;\nconst DEFAULT_INITIAL_DELAY = 500;\nconst DEFAULT_BACKOFF_MULTIPLIER = 2;\nconst DEFAULT_MAX_DELAY = 10_000;\n\n/**\n * Retry an async operation with exponential backoff.\n *\n * Calls `fn` up to `maxAttempts` times. If `fn` returns an error result,\n * waits with exponential backoff before the next attempt. Returns the first\n * successful result or the last error.\n */\nexport async function withRetry<T, E>(\n fn: (attempt: number) => Promise<Result<T, E>>,\n options?: RetryOptions,\n): Promise<Result<T, E>> {\n const maxAttempts = Math.max(1, options?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS);\n const initialDelay = options?.initialDelay ?? DEFAULT_INITIAL_DELAY;\n const backoffMultiplier = options?.backoffMultiplier ?? DEFAULT_BACKOFF_MULTIPLIER;\n const maxDelay = options?.maxDelay ?? DEFAULT_MAX_DELAY;\n const signal = options?.signal;\n\n let lastResult: Result<T, E> | undefined;\n let delay = initialDelay;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n if (signal?.aborted && lastResult) {\n return lastResult;\n }\n\n lastResult = await fn(attempt);\n if (lastResult.ok) {\n return lastResult;\n }\n\n // Don't delay after the last attempt\n if (attempt < maxAttempts - 1) {\n await sleep(delay, signal);\n delay = Math.min(delay * backoffMultiplier, maxDelay);\n }\n }\n\n // lastResult is always defined here since maxAttempts >= 1\n return lastResult!;\n}\n\nif (import.meta.vitest) {\n const { test, expect, describe, vi, beforeEach, afterEach } = import.meta.vitest;\n const { ok, err } = await import(\"./types.js\");\n\n beforeEach(() => {\n vi.useFakeTimers();\n });\n\n afterEach(() => {\n vi.useRealTimers();\n });\n\n describe(\"withRetry\", () => {\n test(\"succeeds on first attempt with no delay\", async () => {\n const fn = vi.fn().mockResolvedValue(ok(\"done\"));\n const promise = withRetry(fn);\n const result = await promise;\n expect(result).toEqual(ok(\"done\"));\n expect(fn).toHaveBeenCalledTimes(1);\n expect(fn).toHaveBeenCalledWith(0);\n });\n\n test(\"retries on failure and succeeds on second attempt\", async () => {\n const fn = vi\n .fn()\n .mockResolvedValueOnce(err(\"fail1\"))\n .mockResolvedValueOnce(ok(\"success\"));\n\n const promise = withRetry(fn, { initialDelay: 100 });\n await vi.advanceTimersByTimeAsync(100);\n const result = await promise;\n\n expect(result).toEqual(ok(\"success\"));\n expect(fn).toHaveBeenCalledTimes(2);\n expect(fn).toHaveBeenNthCalledWith(1, 0);\n expect(fn).toHaveBeenNthCalledWith(2, 1);\n });\n\n test(\"exhausts maxAttempts and returns last error\", async () => {\n const fn = vi\n .fn()\n .mockResolvedValueOnce(err(\"fail1\"))\n .mockResolvedValueOnce(err(\"fail2\"))\n .mockResolvedValueOnce(err(\"fail3\"));\n\n const promise = withRetry(fn, {\n maxAttempts: 3,\n initialDelay: 100,\n backoffMultiplier: 2,\n });\n\n await vi.advanceTimersByTimeAsync(100); // delay after attempt 0\n await vi.advanceTimersByTimeAsync(200); // delay after attempt 1\n const result = await promise;\n\n expect(result).toEqual(err(\"fail3\"));\n expect(fn).toHaveBeenCalledTimes(3);\n });\n\n test(\"respects AbortSignal cancellation\", async () => {\n const controller = new AbortController();\n const fn = vi.fn().mockResolvedValue(err(\"fail\"));\n\n const promise = withRetry(fn, {\n maxAttempts: 5,\n initialDelay: 1000,\n signal: controller.signal,\n });\n\n // First attempt runs, then abort during delay\n await vi.advanceTimersByTimeAsync(0);\n controller.abort();\n await vi.advanceTimersByTimeAsync(0);\n const result = await promise;\n\n expect(result.ok).toBe(false);\n // Should not have retried all 5 times\n expect(fn.mock.calls.length).toBeLessThan(5);\n });\n\n test(\"backoff delay increases correctly\", async () => {\n const fn = vi\n .fn()\n .mockResolvedValueOnce(err(\"e1\"))\n .mockResolvedValueOnce(err(\"e2\"))\n .mockResolvedValueOnce(err(\"e3\"))\n .mockResolvedValueOnce(ok(\"done\"));\n\n const promise = withRetry(fn, {\n maxAttempts: 4,\n initialDelay: 100,\n backoffMultiplier: 2,\n });\n\n // After attempt 0: delay 100ms\n await vi.advanceTimersByTimeAsync(100);\n // After attempt 1: delay 200ms\n await vi.advanceTimersByTimeAsync(200);\n // After attempt 2: delay 400ms\n await vi.advanceTimersByTimeAsync(400);\n const result = await promise;\n\n expect(result).toEqual(ok(\"done\"));\n expect(fn).toHaveBeenCalledTimes(4);\n });\n\n test(\"caps delay at maxDelay\", async () => {\n const fn = vi.fn().mockImplementation(async () => {\n return err(\"fail\");\n });\n\n const promise = withRetry(fn, {\n maxAttempts: 4,\n initialDelay: 5000,\n backoffMultiplier: 3,\n maxDelay: 8000,\n });\n\n // Attempt 0, delay 5000ms\n await vi.advanceTimersByTimeAsync(5000);\n // Attempt 1, delay min(15000, 8000) = 8000ms\n await vi.advanceTimersByTimeAsync(8000);\n // Attempt 2, delay min(24000, 8000) = 8000ms\n await vi.advanceTimersByTimeAsync(8000);\n const result = await promise;\n\n expect(result.ok).toBe(false);\n expect(fn).toHaveBeenCalledTimes(4);\n });\n\n test(\"attempt number is passed correctly to fn\", async () => {\n const attempts: number[] = [];\n const fn = vi.fn().mockImplementation(async (attempt: number) => {\n attempts.push(attempt);\n return attempt < 2 ? err(\"retry\") : ok(\"done\");\n });\n\n const promise = withRetry(fn, {\n maxAttempts: 3,\n initialDelay: 50,\n });\n await vi.advanceTimersByTimeAsync(50);\n await vi.advanceTimersByTimeAsync(100);\n await promise;\n\n expect(attempts).toEqual([0, 1, 2]);\n });\n\n test(\"single attempt with maxAttempts=1\", async () => {\n const fn = vi.fn().mockResolvedValue(err(\"fail\"));\n\n const result = await withRetry(fn, { maxAttempts: 1 });\n expect(result).toEqual(err(\"fail\"));\n expect(fn).toHaveBeenCalledTimes(1);\n });\n\n test(\"signal already aborted before first attempt — fn still called once\", async () => {\n const controller = new AbortController();\n controller.abort();\n\n const fn = vi.fn().mockResolvedValue(err(\"fail\"));\n const result = await withRetry(fn, {\n maxAttempts: 3,\n signal: controller.signal,\n });\n\n expect(result.ok).toBe(false);\n // fn is called once so it can produce a properly-typed error\n expect(fn).toHaveBeenCalledTimes(1);\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport { deriveH160, ss58Encode } from \"@parity/product-sdk-address\";\nimport { createLogger } from \"@parity/product-sdk-logger\";\n\nimport {\n HostRejectedError,\n HostUnavailableError,\n NoAccountsError,\n type SignerError,\n} from \"../errors.js\";\nimport { withRetry } from \"../retry.js\";\nimport type { ConnectionStatus, ProviderType, Result, SignerAccount } from \"../types.js\";\nimport { err, ok } from \"../types.js\";\nimport type { SignerProvider, Unsubscribe } from \"./types.js\";\n\nconst log = createLogger(\"signer:host\");\n\n/** Options for the Host API provider. */\nexport interface HostProviderOptions {\n /** SS58 prefix for address encoding. Default: 42 */\n ss58Prefix?: number;\n /** Max retry attempts for initial connection. Default: 3 */\n maxRetries?: number;\n /** Initial retry delay in ms. Default: 500 */\n retryDelay?: number;\n /**\n * Custom SDK loader. Defaults to `import(\"@novasamatech/host-api-wrapper\")`.\n * Override this for testing or custom SDK setups.\n * @internal\n */\n loadSdk?: () => Promise<ProductSdkModule>;\n /**\n * Custom loader for `@novasamatech/host-api` (used to construct the\n * `ChainSubmit` permission request). Defaults to dynamic import.\n * @internal\n */\n loadHostApiEnum?: () => Promise<HostApiEnumHelper>;\n /**\n * Whether to request the host's `ChainSubmit` permission after a\n * successful `connect()`. Without this, subsequent signing requests are\n * rejected by the host with `PermissionDenied`. Default: `true`.\n *\n * Set to `false` if your app needs to defer the permission prompt or\n * drives it manually.\n *\n * (Previously named `requestTransactionSubmitPermission` — alias kept\n * for backwards compatibility but the new wire format uses `ChainSubmit`.)\n */\n requestChainSubmitPermission?: boolean;\n /** @deprecated Renamed to `requestChainSubmitPermission`. */\n requestTransactionSubmitPermission?: boolean;\n /**\n * If set, `connect()` returns a single product account for the given\n * `dotNsIdentifier`, skipping the legacy fetch entirely. For apps\n * that sign exclusively with a per-dapp derived account.\n *\n * Signing is pinned to `createTransaction` (see PR #96).\n */\n productAccount?: {\n /** App identifier (e.g., `\"playground.dot\"`). */\n dotNsIdentifier: string;\n /** Derivation index within the app scope. Default: 0. */\n derivationIndex?: number;\n /**\n * Populate `SignerAccount.name` best-effort from\n * `accounts.getUserId().primaryUsername`.\n *\n * On by default. Set to `false` to skip the fetch: `getUserId`\n * triggers a host identity-permission prompt, so apps that don't\n * render the user's name (those with their own display chain, e.g.\n * registry username → fallback) can opt out and avoid the prompt.\n * When enabled and the fetch fails (NotConnected, PermissionDenied,\n * codec drift) the name stays null and connect still succeeds. The\n * name can also be fetched later on demand via\n * {@link HostProvider.getUserId}. Default: `true`.\n */\n requestName?: boolean;\n };\n}\n\n/**\n * A product account — an app-scoped derived account managed by the host wallet.\n *\n * The host derives a unique keypair for each app (identified by `dotNsIdentifier`)\n * so apps get their own account that the user controls but is scoped to the app.\n */\nexport interface ProductAccount {\n /** App identifier (e.g., \"mark3t.dot\"). */\n dotNsIdentifier: string;\n /** Derivation index within the app scope. Default: 0 */\n derivationIndex: number;\n /** Raw public key (32 bytes). */\n publicKey: Uint8Array;\n}\n\n/**\n * A contextual alias obtained from Ring VRF.\n *\n * Proves account membership in a ring without revealing which account.\n */\nexport interface ContextualAlias {\n /** Ring context (32 bytes). */\n context: Uint8Array;\n /** The Ring VRF alias bytes. */\n alias: Uint8Array;\n}\n\n/**\n * Location of a Ring VRF ring on-chain.\n *\n * Matches the product-sdk's `RingLocation` codec shape.\n */\nexport interface RingLocation {\n genesisHash: string;\n ringRootHash: string;\n hints?: { palletInstance?: number } | undefined;\n}\n\n// Minimal types matching product-sdk's actual API shape.\n// We define these locally so the SDK remains an optional peer dep.\ninterface RawAccount {\n publicKey: Uint8Array;\n name?: string | undefined;\n}\n\n// Minimal neverthrow ResultAsync shape (product-sdk uses neverthrow internally)\ninterface NeverthrowResultAsync<T, E> {\n match: <A, B = A>(ok: (t: T) => A, err: (e: E) => B) => Promise<A | B>;\n}\n\n/**\n * Pin product-account signing to Nova's `host_create_transaction` path.\n *\n * The `createTransaction` path forwards opaque signed-extension bytes to\n * the host for metadata-driven decoding, so unknown extensions (e.g.\n * `AsPgas` on Paseo Next) survive end-to-end. The alternate\n * `\"signPayload\"` path wraps via PJS and throws\n * `\"PJS does not support this signed-extension: AsPgas\"` on those chains.\n *\n * Nova's `host-api-wrapper@0.8.0` already defaults to `\"createTransaction\"`,\n * so this is a defensive pin rather than an opt-in — it guards against a\n * future upstream default flip and makes the routing legible at the call\n * site. The legacy-account signer doesn't expose this switch.\n */\nconst PRODUCT_SIGNER_TYPE = \"createTransaction\" as const;\n\n/** @internal */\nexport interface AccountsProvider {\n getLegacyAccounts: () => NeverthrowResultAsync<RawAccount[], unknown>;\n getLegacyAccountSigner: (account: ProductAccount) => import(\"polkadot-api\").PolkadotSigner;\n getProductAccount: (\n dotNsIdentifier: string,\n derivationIndex?: number,\n ) => NeverthrowResultAsync<RawAccount, unknown>;\n getProductAccountSigner: (\n account: ProductAccount,\n signerType?: \"signPayload\" | \"createTransaction\",\n ) => import(\"polkadot-api\").PolkadotSigner;\n getProductAccountAlias: (\n dotNsIdentifier: string,\n derivationIndex?: number,\n ) => NeverthrowResultAsync<ContextualAlias, unknown>;\n getUserId: () => NeverthrowResultAsync<{ primaryUsername: string }, unknown>;\n createRingVRFProof: (\n dotNsIdentifier: string,\n derivationIndex: number,\n location: unknown,\n message: Uint8Array,\n ) => NeverthrowResultAsync<Uint8Array, unknown>;\n subscribeAccountConnectionStatus: (\n callback: (status: string) => void,\n ) => { unsubscribe: () => void } | (() => void);\n}\n\n/** @internal */\nexport interface HostApiPermissionBridge {\n /**\n * Request a Host API permission. Product-sdk's `hostApi.permission(...)`\n * takes a tagged enum like `enumValue(\"v1\", { tag: \"TransactionSubmit\" })`\n * and returns a neverthrow ResultAsync.\n */\n permission: (request: unknown) => NeverthrowResultAsync<unknown, unknown>;\n}\n\n/** @internal */\nexport interface HostApiEnumHelper {\n enumValue: (version: string, value: { tag: string; value?: unknown }) => unknown;\n}\n\n/** @internal */\nexport interface ProductSdkModule {\n createAccountsProvider: () => AccountsProvider;\n /** Present from product-sdk ≥ 0.6; used to request TransactionSubmit. */\n hostApi?: HostApiPermissionBridge;\n /**\n * `sandboxTransport.isCorrectEnvironment()` returns `false` when the app\n * is loaded outside a Polkadot host container (e.g. a regular browser\n * tab). Calling `getLegacyAccounts()` / `getProductAccount()` in that\n * state surfaces the upstream `Environment is not correct` exception,\n * so we pre-check during `connect()` and raise a specific\n * {@link HostUnavailableError} with actionable guidance instead.\n */\n sandboxTransport?: { isCorrectEnvironment(): boolean };\n}\n\n/* @integration */\nasync function defaultLoadSdk(): Promise<ProductSdkModule> {\n return (await import(\"@novasamatech/host-api-wrapper\")) as unknown as ProductSdkModule;\n}\n\n/* @integration */\nasync function defaultLoadHostApiEnum(): Promise<HostApiEnumHelper> {\n return (await import(\"@novasamatech/host-api\")) as unknown as HostApiEnumHelper;\n}\n\n/**\n * Provider for the Host API (Polkadot Desktop / Android).\n *\n * Dynamically imports `@novasamatech/host-api-wrapper` at runtime. Apps running\n * outside a host container — e.g. a plain browser tab during `npm run dev` —\n * resolve to {@link HostUnavailableError} with guidance on what to do (open\n * the app inside a Polkadot host or pick a non-host provider). The check uses\n * the wrapper's `sandboxTransport.isCorrectEnvironment()` predicate and runs\n * before any host RPC call, so the user never sees the upstream\n * `Environment is not correct` exception leaking through.\n *\n * Supports both non-product accounts (user's external wallets) and product\n * accounts (app-scoped derived accounts managed by the host).\n */\nexport class HostProvider implements SignerProvider {\n readonly type: ProviderType = \"host\";\n private readonly ss58Prefix: number;\n private readonly maxRetries: number;\n private readonly retryDelay: number;\n private readonly loadSdk: () => Promise<ProductSdkModule>;\n private readonly loadHostApiEnum: () => Promise<HostApiEnumHelper>;\n private readonly requestChainSubmitPermission: boolean;\n private readonly productAccount: HostProviderOptions[\"productAccount\"];\n\n private accountsProvider: AccountsProvider | null = null;\n private statusCleanup: (() => void) | null = null;\n private statusListeners = new Set<(status: ConnectionStatus) => void>();\n private accountListeners = new Set<(accounts: SignerAccount[]) => void>();\n\n constructor(options?: HostProviderOptions) {\n this.ss58Prefix = options?.ss58Prefix ?? 42;\n this.maxRetries = options?.maxRetries ?? 3;\n this.retryDelay = options?.retryDelay ?? 500;\n this.loadSdk = options?.loadSdk ?? defaultLoadSdk;\n this.loadHostApiEnum = options?.loadHostApiEnum ?? defaultLoadHostApiEnum;\n // New name takes precedence; fall back to the deprecated alias.\n this.requestChainSubmitPermission =\n options?.requestChainSubmitPermission ??\n options?.requestTransactionSubmitPermission ??\n true;\n this.productAccount = options?.productAccount;\n }\n\n async connect(signal?: AbortSignal): Promise<Result<SignerAccount[], SignerError>> {\n log.debug(\"attempting Host API connection\");\n\n return withRetry(\n async () => {\n if (signal?.aborted) {\n return err(new HostUnavailableError(\"Connection aborted\"));\n }\n return this.tryConnect();\n },\n {\n maxAttempts: this.maxRetries,\n initialDelay: this.retryDelay,\n signal,\n },\n );\n }\n\n disconnect(): void {\n if (this.statusCleanup) {\n this.statusCleanup();\n this.statusCleanup = null;\n }\n this.accountsProvider = null;\n this.statusListeners.clear();\n this.accountListeners.clear();\n log.debug(\"host provider disconnected\");\n }\n\n onStatusChange(callback: (status: ConnectionStatus) => void): Unsubscribe {\n this.statusListeners.add(callback);\n return () => {\n this.statusListeners.delete(callback);\n };\n }\n\n onAccountsChange(callback: (accounts: SignerAccount[]) => void): Unsubscribe {\n this.accountListeners.add(callback);\n return () => {\n this.accountListeners.delete(callback);\n };\n }\n\n // ── Product Account API ──────────────────────────────────────────\n\n /**\n * Get an app-scoped product account from the host.\n *\n * Product accounts are derived by the host wallet for each app, identified\n * by `dotNsIdentifier` (e.g., \"mark3t.dot\"). The user controls these accounts\n * but they are scoped to the requesting app.\n *\n * Requires a prior successful `connect()` call.\n */\n async getProductAccount(\n dotNsIdentifier: string,\n derivationIndex = 0,\n ): Promise<Result<SignerAccount, SignerError>> {\n if (!this.accountsProvider) {\n return err(new HostUnavailableError(\"Host provider is not connected\"));\n }\n\n try {\n const raw = (await this.accountsProvider\n .getProductAccount(dotNsIdentifier, derivationIndex)\n .match(\n (account) => account,\n (error) => {\n throw new Error(\n `Host rejected product account request: ${formatError(error)}`,\n );\n },\n )) as RawAccount;\n\n const address = ss58Encode(raw.publicKey, this.ss58Prefix);\n const productAccount: ProductAccount = {\n dotNsIdentifier,\n derivationIndex,\n publicKey: raw.publicKey,\n };\n\n return ok({\n address,\n h160Address: deriveH160(raw.publicKey),\n publicKey: raw.publicKey,\n name: raw.name ?? null,\n source: \"host\" as const,\n getSigner: () => {\n if (!this.accountsProvider) {\n throw new Error(\"Host provider is disconnected\");\n }\n return this.accountsProvider.getProductAccountSigner(\n productAccount,\n PRODUCT_SIGNER_TYPE,\n );\n },\n });\n } catch (cause) {\n log.error(\"failed to get product account\", { cause });\n return err(\n new HostRejectedError(\n cause instanceof Error ? cause.message : \"Failed to get product account\",\n ),\n );\n }\n }\n\n /**\n * Get a PolkadotSigner for a product account.\n *\n * Convenience method for when you already have the product account details.\n * Requires a prior successful `connect()` call.\n *\n * Routing is pinned to `signerType: \"createTransaction\"` via\n * {@link PRODUCT_SIGNER_TYPE} so unknown signed extensions (e.g. `AsPgas`\n * on Paseo Next) are forwarded to the host as opaque bytes for\n * metadata-driven decoding, rather than going through the PJS bridge\n * that throws on unknown extensions.\n */\n getProductAccountSigner(account: ProductAccount): import(\"polkadot-api\").PolkadotSigner {\n if (!this.accountsProvider) {\n throw new Error(\"Host provider is not connected\");\n }\n return this.accountsProvider.getProductAccountSigner(account, PRODUCT_SIGNER_TYPE);\n }\n\n /**\n * Get a contextual alias for a product account via Ring VRF.\n *\n * Aliases prove account membership in a ring without revealing which\n * account produced the alias.\n *\n * Requires a prior successful `connect()` call.\n */\n async getProductAccountAlias(\n dotNsIdentifier: string,\n derivationIndex = 0,\n ): Promise<Result<ContextualAlias, SignerError>> {\n if (!this.accountsProvider) {\n return err(new HostUnavailableError(\"Host provider is not connected\"));\n }\n\n try {\n const alias = (await this.accountsProvider\n .getProductAccountAlias(dotNsIdentifier, derivationIndex)\n .match(\n (result) => result,\n (error) => {\n throw new Error(`Host rejected alias request: ${formatError(error)}`);\n },\n )) as ContextualAlias;\n\n return ok(alias);\n } catch (cause) {\n log.error(\"failed to get product account alias\", { cause });\n return err(\n new HostRejectedError(\n cause instanceof Error ? cause.message : \"Failed to get product account alias\",\n ),\n );\n }\n }\n\n /**\n * Fetch the connected user's primary username from the host.\n *\n * Use this to retrieve the name lazily — e.g. on a profile screen that\n * actually displays it — when `connect()` ran without\n * `productAccount.requestName` (the default) and so never fetched it.\n * Like the connect-time fetch this triggers a host identity-permission\n * prompt; unlike it, the result is returned as a structured `Result` so\n * callers can react to a `PermissionDenied` / `NotConnected` rejection\n * explicitly instead of silently falling back to a nameless account.\n *\n * Requires a prior successful `connect()` call.\n */\n async getUserId(): Promise<Result<{ primaryUsername: string }, SignerError>> {\n if (!this.accountsProvider) {\n return err(new HostUnavailableError(\"Host provider is not connected\"));\n }\n\n try {\n const result = (await this.accountsProvider.getUserId().match(\n (value) => value,\n (error) => {\n throw new Error(`Host rejected user id request: ${formatError(error)}`);\n },\n )) as { primaryUsername: string };\n\n return ok(result);\n } catch (cause) {\n log.error(\"failed to get user id\", { cause });\n return err(\n new HostRejectedError(\n cause instanceof Error ? cause.message : \"Failed to get user id\",\n ),\n );\n }\n }\n\n /**\n * Create a Ring VRF proof for anonymous operations.\n *\n * Proves that the signer is a member of the ring at the given location\n * without revealing which member. Used for privacy-preserving protocols.\n *\n * Requires a prior successful `connect()` call.\n */\n async createRingVRFProof(\n dotNsIdentifier: string,\n derivationIndex: number,\n location: RingLocation,\n message: Uint8Array,\n ): Promise<Result<Uint8Array, SignerError>> {\n if (!this.accountsProvider) {\n return err(new HostUnavailableError(\"Host provider is not connected\"));\n }\n\n try {\n const proof = (await this.accountsProvider\n .createRingVRFProof(dotNsIdentifier, derivationIndex, location, message)\n .match(\n (result) => result,\n (error) => {\n throw new Error(\n `Host rejected Ring VRF proof request: ${formatError(error)}`,\n );\n },\n )) as Uint8Array;\n\n return ok(proof);\n } catch (cause) {\n log.error(\"failed to create Ring VRF proof\", { cause });\n return err(\n new HostRejectedError(\n cause instanceof Error ? cause.message : \"Failed to create Ring VRF proof\",\n ),\n );\n }\n }\n\n // ── Private ──────────────────────────────────────────────────────\n\n private async tryConnect(): Promise<Result<SignerAccount[], SignerError>> {\n // Step 1: Load product-sdk\n let sdk: ProductSdkModule;\n try {\n sdk = await this.loadSdk();\n } catch (cause) {\n log.warn(\"product-sdk not available\", { cause });\n return err(\n new HostUnavailableError(\n cause instanceof Error\n ? `product-sdk import failed: ${cause.message}`\n : \"product-sdk is not installed\",\n ),\n );\n }\n\n // Step 2: Verify we're actually running inside a host container.\n //\n // The upstream `host-api` transport throws `Error('Environment is not\n // correct')` from inside `getLegacyAccounts()` / `getProductAccount()`\n // when `sandboxTransport.isCorrectEnvironment()` returns false (i.e.\n // we're not in an iframe under Polkadot Desktop, or a WebView under\n // Polkadot Mobile). Without this pre-check, that exception used to\n // surface as `HostRejectedError(\"Host rejected account request:\n // Environment is not correct\")` — misleading because no host rejected\n // anything; there's no host at all.\n //\n // Returning `HostUnavailableError` here matches the TSDoc contract\n // (\"Apps running outside a host container will gracefully get a\n // HOST_UNAVAILABLE error\") and gives consumers actionable guidance.\n //\n // The `sandboxTransport` field is optional in `ProductSdkModule` so\n // older wrapper versions (or test mocks that don't supply it) keep\n // working — we fall through to the existing flow and rely on the\n // catch in Step 4 as a safety net.\n if (sdk.sandboxTransport && !sdk.sandboxTransport.isCorrectEnvironment()) {\n log.warn(\"not inside a host container — Host API unavailable\");\n return err(\n new HostUnavailableError(\n \"Host API is not available: not running inside a Polkadot host container. \" +\n \"Open this app inside Polkadot Desktop or the Polkadot Mobile WebView, \" +\n \"or pick a non-host signer provider (e.g. dev accounts).\",\n ),\n );\n }\n\n // Step 3: Create accounts provider\n const provider = sdk.createAccountsProvider();\n this.accountsProvider = provider;\n\n // Step 4: Fetch accounts.\n //\n // When `productAccount` is configured, skip the legacy fetch entirely\n // and return a single product account. Product-account-only apps\n // (no wallet picker) often run against hosts that have no legacy\n // accounts to surface — calling `getLegacyAccounts()` there returns\n // an empty list and the connect would fail with `NoAccountsError`.\n let signerAccounts: SignerAccount[];\n if (this.productAccount) {\n const accountResult = await this.fetchProductSignerAccount(\n provider,\n this.productAccount.dotNsIdentifier,\n this.productAccount.derivationIndex ?? 0,\n this.productAccount.requestName ?? true,\n );\n if (!accountResult.ok) return accountResult;\n signerAccounts = [accountResult.value];\n } else {\n let rawAccounts: RawAccount[];\n try {\n rawAccounts = (await provider.getLegacyAccounts().match(\n (accounts) => accounts,\n (error) => {\n throw new Error(`Host rejected account request: ${formatError(error)}`);\n },\n )) as RawAccount[];\n } catch (cause) {\n // Safety net: upstream `host-api/transport.js` throws\n // `Error('Environment is not correct')` synchronously inside\n // `getLegacyAccounts()` when the env check fails. The Step 2\n // pre-check catches this normally, but we also re-classify\n // here for older wrappers that don't expose `sandboxTransport`\n // and for races where the env flips after the pre-check.\n // Without this, the user sees a misleading \"Host rejected\n // account request:\" prefix for an error nothing rejected.\n if (cause instanceof Error && /environment is not correct/i.test(cause.message)) {\n log.warn(\"not inside a host container (detected at getLegacyAccounts)\");\n return err(\n new HostUnavailableError(\n \"Host API is not available: not running inside a Polkadot host container. \" +\n \"Open this app inside Polkadot Desktop or the Polkadot Mobile WebView, \" +\n \"or pick a non-host signer provider (e.g. dev accounts).\",\n ),\n );\n }\n log.error(\"failed to get accounts from host\", { cause });\n return err(\n new HostRejectedError(\n cause instanceof Error ? cause.message : \"Failed to get accounts from host\",\n ),\n );\n }\n\n if (rawAccounts.length === 0) {\n log.warn(\"host returned no accounts\");\n return err(new NoAccountsError(\"host\"));\n }\n\n signerAccounts = this.mapAccounts(rawAccounts);\n }\n\n // Step 5: Request ChainSubmit permission up-front.\n //\n // The host gates signing on this permission — without it, the\n // production host rejects every sign request with `PermissionDenied`\n // at both `handleSignPayload` (legacy account path) and\n // `host_create_transaction` (product-account path), which typically\n // manifests as a silently-hanging tx. Doing it once during connect()\n // matches what production apps need and spares consumers the\n // boilerplate.\n //\n // We don't fail `connect()` if this step fails: the consumer can still\n // use the signer for read-only code paths, and the actual sign call\n // will surface a clear error if permission is missing.\n //\n // The legal v1 RemotePermission variants per\n // `@novasamatech/host-api@0.8.0` are: Remote, WebRtc, ChainSubmit,\n // PreimageSubmit, StatementSubmit. ChainSubmit is the chain-tx\n // permission (was named TransactionSubmit in earlier host-api\n // revisions; renamed in 0.7). `WebRtc` was spelled `WebRTC` before\n // 0.8.\n if (this.requestChainSubmitPermission && sdk.hostApi) {\n try {\n const hostApiEnum = await this.loadHostApiEnum();\n const request = hostApiEnum.enumValue(\"v1\", {\n tag: \"ChainSubmit\",\n value: undefined,\n });\n await sdk.hostApi.permission(request).match(\n () => {\n log.debug(\"ChainSubmit permission granted\");\n },\n (error) => {\n log.warn(\"ChainSubmit permission rejected by host\", {\n error: formatError(error),\n });\n },\n );\n } catch (cause) {\n log.warn(\"failed to request ChainSubmit permission\", { cause });\n }\n }\n\n log.info(\"host connected\", { accounts: signerAccounts.length });\n\n // Step 6: Subscribe to connection status\n const sub = provider.subscribeAccountConnectionStatus((status) => {\n const mapped: ConnectionStatus = status === \"connected\" ? \"connected\" : \"disconnected\";\n log.debug(\"host status changed\", { status: mapped });\n for (const listener of this.statusListeners) {\n listener(mapped);\n }\n });\n this.statusCleanup = typeof sub === \"function\" ? sub : () => sub.unsubscribe();\n\n return ok(signerAccounts);\n }\n\n private async fetchProductSignerAccount(\n provider: AccountsProvider,\n dotNsIdentifier: string,\n derivationIndex: number,\n requestName: boolean,\n ): Promise<Result<SignerAccount, SignerError>> {\n // The name fetch is on by default; `requestName: false` opts out.\n // `getUserId` triggers a host identity-permission prompt, so apps\n // that don't render the user's name can skip it. When enabled it\n // runs in parallel with the account fetch — they're independent host\n // RPCs — and its failures (NotConnected, PermissionDenied, codec\n // drift) resolve to `null` so they never abort connect; the account\n // name then falls back to whatever `getProductAccount` returned\n // (typically also null, since product accounts are nameless on the\n // host side).\n const fetchUsername = async (): Promise<string | null> => {\n if (!requestName) return null;\n try {\n return await provider.getUserId().match(\n (result) => result.primaryUsername,\n (error) => {\n log.debug(\"getUserId failed; product account name stays null\", {\n error: formatError(error),\n });\n return null as string | null;\n },\n );\n } catch (cause) {\n log.debug(\"getUserId threw; product account name stays null\", { cause });\n return null;\n }\n };\n const [accountResult, primaryUsername] = await Promise.all([\n this.getProductAccount(dotNsIdentifier, derivationIndex),\n fetchUsername(),\n ]);\n if (!accountResult.ok) return accountResult;\n const account = accountResult.value;\n return ok({ ...account, name: account.name ?? primaryUsername });\n }\n\n private mapAccounts(rawAccounts: ReadonlyArray<RawAccount>): SignerAccount[] {\n return rawAccounts.map((raw) => {\n const address = ss58Encode(raw.publicKey, this.ss58Prefix);\n const h160Address = deriveH160(raw.publicKey);\n return {\n address,\n h160Address,\n publicKey: raw.publicKey,\n name: raw.name ?? null,\n source: \"host\" as const,\n getSigner: () => {\n if (!this.accountsProvider) {\n throw new Error(\"Host provider is disconnected\");\n }\n return this.accountsProvider.getLegacyAccountSigner({\n dotNsIdentifier: \"\",\n derivationIndex: 0,\n publicKey: raw.publicKey,\n });\n },\n };\n });\n }\n}\n\n/**\n * Format a host-error for logging.\n *\n * host-api errors come back as `{ tag: \"v1\", value: <inner> }` where the\n * inner can be either another tagged enum (with its own tag/value) or a\n * plain `Error`-shaped object surfacing client-side codec failures\n * (e.g. `GenericError: inner[tag] is not a function` when the SDK\n * encodes a request the codec doesn't understand).\n *\n * Walking the value side as well as the tag means schema drift between\n * host-api versions and the SDK produces something more diagnostic than\n * just the outermost wrapper tag.\n */\nfunction formatError(error: unknown): string {\n if (!error || typeof error !== \"object\") return String(error);\n const e = error as Record<string, unknown>;\n if (!(\"tag\" in e)) return String(error);\n\n const outerTag = String(e.tag);\n const inner = e.value;\n\n // Inner is an Error-shaped object with name/message — surface those.\n if (inner && typeof inner === \"object\") {\n const innerObj = inner as Record<string, unknown>;\n if (typeof innerObj.message === \"string\") {\n const innerName =\n typeof innerObj.name === \"string\" && innerObj.name !== \"Error\"\n ? `${innerObj.name}: `\n : \"\";\n return `${outerTag} → ${innerName}${innerObj.message}`;\n }\n // Inner is a nested tagged-enum — recurse.\n if (\"tag\" in innerObj) {\n return `${outerTag} → ${formatError(inner)}`;\n }\n }\n\n // Inner is a primitive or absent — fall back to the outer tag alone.\n if (inner !== undefined) {\n return `${outerTag} (${String(inner)})`;\n }\n return outerTag;\n}\n\nif (import.meta.vitest) {\n const { test, expect, describe, vi, beforeEach } = import.meta.vitest;\n\n interface RawAccountTest {\n publicKey: Uint8Array;\n name?: string | undefined;\n }\n\n function createMockProvider(\n options: {\n accounts?: RawAccountTest[];\n shouldReject?: boolean;\n error?: unknown;\n primaryUsername?: string;\n } = {},\n ) {\n const accounts = options.accounts ?? [];\n const shouldReject = options.shouldReject ?? false;\n const mockSigner = {\n publicKey: new Uint8Array(32).fill(0xbb),\n } as unknown as import(\"polkadot-api\").PolkadotSigner;\n\n return {\n getLegacyAccounts: vi.fn().mockReturnValue({\n match: async (\n onOk: (v: RawAccountTest[]) => unknown,\n onErr: (e: unknown) => unknown,\n ) => {\n if (shouldReject) {\n return onErr(options.error ?? \"Unknown\");\n }\n return onOk(accounts);\n },\n }),\n getLegacyAccountSigner: vi.fn().mockReturnValue(mockSigner),\n getProductAccount: vi.fn().mockReturnValue({\n match: async (\n onOk: (v: RawAccountTest) => unknown,\n onErr: (e: unknown) => unknown,\n ) => {\n if (shouldReject) {\n return onErr(options.error ?? \"Unknown\");\n }\n return onOk(accounts[0] ?? { publicKey: new Uint8Array(32), name: undefined });\n },\n }),\n getProductAccountSigner: vi.fn().mockReturnValue(mockSigner),\n getProductAccountAlias: vi.fn().mockReturnValue({\n match: async (onOk: (v: unknown) => unknown, onErr: (e: unknown) => unknown) => {\n if (shouldReject) {\n return onErr(options.error ?? \"Unknown\");\n }\n return onOk({\n context: new Uint8Array(32).fill(0x01),\n alias: new Uint8Array(64).fill(0x02),\n });\n },\n }),\n createRingVRFProof: vi.fn().mockReturnValue({\n match: async (onOk: (v: unknown) => unknown, onErr: (e: unknown) => unknown) => {\n if (shouldReject) {\n return onErr(options.error ?? \"Unknown\");\n }\n return onOk(new Uint8Array(128).fill(0x03));\n },\n }),\n subscribeAccountConnectionStatus: vi.fn().mockReturnValue(() => {}),\n getUserId: vi.fn().mockReturnValue({\n match: async (\n onOk: (v: { primaryUsername: string }) => unknown,\n onErr: (e: unknown) => unknown,\n ) => {\n if (shouldReject) {\n return onErr(options.error ?? \"Unknown\");\n }\n return onOk({ primaryUsername: options.primaryUsername ?? \"\" });\n },\n }),\n };\n }\n\n function createMockSdk(\n mockProvider: ReturnType<typeof createMockProvider>,\n opts?: {\n hostApi?: HostApiPermissionBridge;\n /**\n * When provided, the mock's `sandboxTransport.isCorrectEnvironment()`\n * returns this value — exercises the env-check branch added in the\n * `connect()` flow. Omit to skip the check entirely (older-wrapper\n * compatibility path).\n */\n isCorrectEnvironment?: boolean;\n },\n ): ProductSdkModule {\n return {\n createAccountsProvider: () => mockProvider as unknown as AccountsProvider,\n ...(opts?.hostApi ? { hostApi: opts.hostApi } : {}),\n ...(opts?.isCorrectEnvironment !== undefined\n ? { sandboxTransport: { isCorrectEnvironment: () => opts.isCorrectEnvironment! } }\n : {}),\n };\n }\n\n /**\n * A fake neverthrow ResultAsync-like object. Resolves via `onOk` when\n * `error === undefined`, otherwise via `onErr`.\n */\n function fakeResult<T>(value: T, error?: unknown): NeverthrowResultAsync<T, unknown> {\n return {\n match: async (onOk, onErr) => {\n if (error !== undefined) return onErr(error);\n return onOk(value);\n },\n };\n }\n\n const fakeHostApiEnum: HostApiEnumHelper = {\n enumValue: (version, value) => ({ version, value }),\n };\n\n beforeEach(() => {\n vi.restoreAllMocks();\n });\n\n describe(\"HostProvider\", () => {\n test(\"returns HOST_UNAVAILABLE when SDK load fails\", async () => {\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.reject(new Error(\"Cannot find module\")),\n });\n const result = await provider.connect();\n\n expect(result.ok).toBe(false);\n if (!result.ok) {\n expect(result.error).toBeInstanceOf(HostUnavailableError);\n expect(result.error.message).toContain(\"Cannot find module\");\n }\n });\n\n test(\"returns HOST_UNAVAILABLE with actionable guidance when not inside a host container\", async () => {\n // Repro for playground-cli#4: `pg mod foo` + `npm run dev` opens\n // localhost in a plain browser tab (no iframe, no WebView).\n // sandboxTransport.isCorrectEnvironment() returns false, and\n // pre-fix we surfaced the upstream \"Environment is not correct\"\n // as `HostRejectedError(\"Host rejected account request: ...\")`.\n // Post-fix: we pre-check during connect() and return a specific\n // HostUnavailableError naming the host container and pointing\n // the user at the fix path.\n const mockProvider = createMockProvider({ accounts: [] });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () =>\n Promise.resolve(createMockSdk(mockProvider, { isCorrectEnvironment: false })),\n });\n const result = await provider.connect();\n\n expect(result.ok).toBe(false);\n if (!result.ok) {\n expect(result.error).toBeInstanceOf(HostUnavailableError);\n expect(result.error.message).toMatch(\n /not running inside a Polkadot host container/i,\n );\n expect(result.error.message).toMatch(/Polkadot Desktop|Polkadot Mobile/i);\n }\n // We never reached `getLegacyAccounts()` — proves the env check\n // short-circuits before any RPC call, so users in a dev browser\n // never see the upstream exception text leak through.\n expect(mockProvider.getLegacyAccounts).not.toHaveBeenCalled();\n });\n\n test(\"safety net: re-classifies upstream 'Environment is not correct' as HOST_UNAVAILABLE\", async () => {\n // For older wrappers (or test mocks) that don't supply\n // `sandboxTransport`, the Step 2 pre-check is skipped and the\n // upstream throw surfaces at `getLegacyAccounts()`. The catch\n // in Step 4 must re-classify it rather than wrapping with the\n // misleading \"Host rejected account request:\" prefix.\n const mockProvider = createMockProvider({\n shouldReject: true,\n error: \"Environment is not correct\",\n });\n const provider = new HostProvider({\n maxRetries: 1,\n // sandboxTransport intentionally omitted — exercises the\n // safety-net path, not the pre-check path.\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider)),\n });\n const result = await provider.connect();\n\n expect(result.ok).toBe(false);\n if (!result.ok) {\n expect(result.error).toBeInstanceOf(HostUnavailableError);\n // Must NOT contain the misleading \"Host rejected account request:\" prefix.\n expect(result.error.message).not.toMatch(/Host rejected/i);\n expect(result.error.message).toMatch(\n /not running inside a Polkadot host container/i,\n );\n }\n });\n\n test(\"connect proceeds when sandboxTransport reports a correct environment\", async () => {\n // Mirror of the existing happy path, but with an explicit\n // `isCorrectEnvironment: true` to prove the pre-check doesn't\n // false-fail when the env IS correct.\n const rawAccounts: RawAccountTest[] = [\n { publicKey: new Uint8Array(32).fill(0x42), name: \"Alice\" },\n ];\n const mockProvider = createMockProvider({ accounts: rawAccounts });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () =>\n Promise.resolve(createMockSdk(mockProvider, { isCorrectEnvironment: true })),\n });\n const result = await provider.connect();\n\n expect(result.ok).toBe(true);\n if (result.ok) {\n expect(result.value).toHaveLength(1);\n expect(result.value[0].address).toMatch(/^5/);\n }\n expect(mockProvider.getLegacyAccounts).toHaveBeenCalled();\n });\n\n test(\"returns HOST_REJECTED when getLegacyAccounts fails\", async () => {\n const mockProvider = createMockProvider({ shouldReject: true, error: \"Rejected\" });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider)),\n });\n const result = await provider.connect();\n\n expect(result.ok).toBe(false);\n if (!result.ok) {\n expect(result.error).toBeInstanceOf(HostRejectedError);\n }\n });\n\n test(\"returns NO_ACCOUNTS when host returns empty list\", async () => {\n const mockProvider = createMockProvider({ accounts: [] });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider)),\n });\n const result = await provider.connect();\n\n expect(result.ok).toBe(false);\n if (!result.ok) {\n expect(result.error).toBeInstanceOf(NoAccountsError);\n }\n });\n\n test(\"maps accounts correctly on success\", async () => {\n const rawAccounts: RawAccountTest[] = [\n { publicKey: new Uint8Array(32).fill(0xaa), name: \"Alice\" },\n { publicKey: new Uint8Array(32).fill(0xbb), name: undefined },\n ];\n const mockProvider = createMockProvider({ accounts: rawAccounts });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider)),\n });\n const result = await provider.connect();\n\n expect(result.ok).toBe(true);\n if (result.ok) {\n expect(result.value).toHaveLength(2);\n expect(result.value[0].name).toBe(\"Alice\");\n expect(result.value[0].source).toBe(\"host\");\n expect(result.value[0].publicKey).toEqual(rawAccounts[0].publicKey);\n expect(result.value[1].name).toBeNull();\n }\n });\n\n test(\"getProductAccountSigner pins signerType to 'createTransaction'\", async () => {\n // Regression guard: the alternate \"signPayload\" route goes through\n // PJS and throws on unknown signed extensions (e.g. AsPgas on\n // Paseo Next). If a future refactor drops the explicit pin and\n // upstream's default ever flips back to signPayload, this would\n // silently regress.\n const rawAccounts: RawAccountTest[] = [\n { publicKey: new Uint8Array(32).fill(0xaa), name: \"Alice\" },\n ];\n const mockProvider = createMockProvider({ accounts: rawAccounts });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider)),\n });\n await provider.connect();\n\n // Path 1: HostProvider.getProductAccountSigner(...)\n provider.getProductAccountSigner({\n dotNsIdentifier: \"test.dot\",\n derivationIndex: 0,\n publicKey: rawAccounts[0].publicKey,\n });\n expect(mockProvider.getProductAccountSigner).toHaveBeenLastCalledWith(\n expect.anything(),\n \"createTransaction\",\n );\n\n // Path 2: getSigner() returned from HostProvider.getProductAccount(...)\n const productAccountResult = await provider.getProductAccount(\"test.dot\", 0);\n expect(productAccountResult.ok).toBe(true);\n if (productAccountResult.ok) {\n productAccountResult.value.getSigner();\n expect(mockProvider.getProductAccountSigner).toHaveBeenLastCalledWith(\n expect.anything(),\n \"createTransaction\",\n );\n }\n });\n\n test(\"disconnect is idempotent\", () => {\n const provider = new HostProvider();\n provider.disconnect();\n provider.disconnect();\n });\n\n test(\"type is 'host'\", () => {\n const provider = new HostProvider();\n expect(provider.type).toBe(\"host\");\n });\n\n test(\"onAccountsChange adds and removes listener\", () => {\n const provider = new HostProvider();\n const cb = () => {};\n const unsub = provider.onAccountsChange(cb);\n expect(typeof unsub).toBe(\"function\");\n unsub();\n });\n\n test(\"productAccount populates name via getUserId by default and skips the legacy fetch\", async () => {\n const productPubkey = new Uint8Array(32).fill(0xcc);\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: productPubkey, name: undefined }],\n primaryUsername: \"alice\",\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider)),\n productAccount: { dotNsIdentifier: \"myapp.dot\", derivationIndex: 0 },\n });\n const result = await provider.connect();\n\n expect(result.ok).toBe(true);\n if (result.ok) {\n expect(result.value).toHaveLength(1);\n expect(result.value[0].publicKey).toEqual(productPubkey);\n expect(result.value[0].source).toBe(\"host\");\n expect(result.value[0].name).toBe(\"alice\");\n result.value[0].getSigner();\n expect(mockProvider.getProductAccountSigner).toHaveBeenLastCalledWith(\n expect.objectContaining({\n dotNsIdentifier: \"myapp.dot\",\n derivationIndex: 0,\n }),\n \"createTransaction\",\n );\n }\n expect(mockProvider.getProductAccount).toHaveBeenCalledWith(\"myapp.dot\", 0);\n expect(mockProvider.getUserId).toHaveBeenCalled();\n expect(mockProvider.getLegacyAccounts).not.toHaveBeenCalled();\n });\n\n test(\"productAccount with requestName:false skips getUserId (no identity prompt) and leaves name null\", async () => {\n // Opt-out: `getUserId` triggers a host identity-permission prompt,\n // so apps that don't render the name set `requestName: false`.\n const productPubkey = new Uint8Array(32).fill(0xab);\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: productPubkey, name: undefined }],\n primaryUsername: \"alice\",\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider)),\n productAccount: { dotNsIdentifier: \"myapp.dot\", requestName: false },\n });\n const result = await provider.connect();\n\n expect(result.ok).toBe(true);\n if (result.ok) {\n expect(result.value).toHaveLength(1);\n expect(result.value[0].publicKey).toEqual(productPubkey);\n expect(result.value[0].name).toBeNull();\n }\n expect(mockProvider.getProductAccount).toHaveBeenCalledWith(\"myapp.dot\", 0);\n expect(mockProvider.getUserId).not.toHaveBeenCalled();\n expect(mockProvider.getLegacyAccounts).not.toHaveBeenCalled();\n });\n\n test(\"productAccount survives getUserId failure (name stays null, connect still succeeds)\", async () => {\n const productPubkey = new Uint8Array(32).fill(0xee);\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: productPubkey, name: undefined }],\n });\n // Force getUserId to reject — connect must still succeed with name=null.\n mockProvider.getUserId.mockReturnValue({\n match: async (\n _onOk: (v: { primaryUsername: string }) => unknown,\n onErr: (e: unknown) => unknown,\n ) => onErr({ tag: \"v1\", value: { tag: \"GetUserIdErr::PermissionDenied\" } }),\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider)),\n productAccount: { dotNsIdentifier: \"myapp.dot\" },\n });\n const result = await provider.connect();\n\n expect(result.ok).toBe(true);\n if (result.ok) {\n expect(result.value[0].name).toBeNull();\n }\n });\n\n test(\"getUserId() retrieves the primary username after a connect that opted out of the name fetch\", async () => {\n // The escape hatch for `requestName: false`: connect without the\n // prompt (name=null), then fetch the name lazily later — e.g. when\n // a profile screen needs to display it.\n const productPubkey = new Uint8Array(32).fill(0xa1);\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: productPubkey, name: undefined }],\n primaryUsername: \"alice\",\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider)),\n productAccount: { dotNsIdentifier: \"myapp.dot\", requestName: false },\n });\n const connectResult = await provider.connect();\n expect(connectResult.ok).toBe(true);\n if (connectResult.ok) expect(connectResult.value[0].name).toBeNull();\n // Not fetched during connect...\n expect(mockProvider.getUserId).not.toHaveBeenCalled();\n\n // ...but reachable on demand afterwards.\n const userId = await provider.getUserId();\n expect(userId.ok).toBe(true);\n if (userId.ok) expect(userId.value.primaryUsername).toBe(\"alice\");\n expect(mockProvider.getUserId).toHaveBeenCalledTimes(1);\n });\n\n test(\"getUserId() returns HostUnavailableError before connect\", async () => {\n const provider = new HostProvider({ maxRetries: 1 });\n const result = await provider.getUserId();\n expect(result.ok).toBe(false);\n if (!result.ok) expect(result.error).toBeInstanceOf(HostUnavailableError);\n });\n\n test(\"getUserId() surfaces a host rejection as HostRejectedError\", async () => {\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: new Uint8Array(32).fill(0xa2), name: undefined }],\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider)),\n productAccount: { dotNsIdentifier: \"myapp.dot\", requestName: false },\n });\n await provider.connect();\n // After connect, force the host to reject the on-demand fetch.\n mockProvider.getUserId.mockReturnValue({\n match: async (\n _onOk: (v: { primaryUsername: string }) => unknown,\n onErr: (e: unknown) => unknown,\n ) => onErr({ tag: \"v1\", value: { tag: \"GetUserIdErr::PermissionDenied\" } }),\n });\n const result = await provider.getUserId();\n expect(result.ok).toBe(false);\n if (!result.ok) expect(result.error).toBeInstanceOf(HostRejectedError);\n });\n\n test(\"productAccount option succeeds when host has no legacy accounts (regression: signer 0.5.0 NoAccountsError)\", async () => {\n // Without the option, this scenario returned `err(NoAccountsError)`\n // before any product-account fetch could happen — breaking every\n // product-only app whose host doesn't surface legacy accounts.\n const productPubkey = new Uint8Array(32).fill(0xdd);\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: productPubkey, name: undefined }],\n });\n // Force the legacy path to look empty if it were ever consulted.\n mockProvider.getLegacyAccounts.mockReturnValue({\n match: async (\n onOk: (v: RawAccountTest[]) => unknown,\n _onErr: (e: unknown) => unknown,\n ) => onOk([]),\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider)),\n productAccount: { dotNsIdentifier: \"playground.dot\" },\n });\n const result = await provider.connect();\n\n expect(result.ok).toBe(true);\n if (result.ok) {\n expect(result.value).toHaveLength(1);\n expect(result.value[0].publicKey).toEqual(productPubkey);\n }\n });\n });\n\n describe(\"ChainSubmit permission request\", () => {\n test(\"sends a v1 ChainSubmit request (regression guard for the TransactionSubmit bug)\", async () => {\n const captured: unknown[] = [];\n const hostApi: HostApiPermissionBridge = {\n permission: (request) => {\n captured.push(request);\n return fakeResult(undefined);\n },\n };\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: new Uint8Array(32).fill(0x01) }],\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider, { hostApi })),\n loadHostApiEnum: () => Promise.resolve(fakeHostApiEnum),\n });\n\n await provider.connect();\n\n expect(captured).toHaveLength(1);\n // The fake hostApiEnum returns `{ version, value }` so we can\n // assert on the exact wire shape that would reach\n // host-api's RemotePermission codec.\n expect(captured[0]).toEqual({\n version: \"v1\",\n value: { tag: \"ChainSubmit\", value: undefined },\n });\n });\n\n test(\"does NOT send a TransactionSubmit tag (the bug)\", async () => {\n const captured: unknown[] = [];\n const hostApi: HostApiPermissionBridge = {\n permission: (request) => {\n captured.push(request);\n return fakeResult(undefined);\n },\n };\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: new Uint8Array(32).fill(0x01) }],\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider, { hostApi })),\n loadHostApiEnum: () => Promise.resolve(fakeHostApiEnum),\n });\n\n await provider.connect();\n\n const sent = JSON.stringify(captured[0]);\n expect(sent).not.toContain(\"TransactionSubmit\");\n });\n\n test(\"skipped when sdk.hostApi is unavailable (older product-sdk)\", async () => {\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: new Uint8Array(32).fill(0x01) }],\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider /* no hostApi */)),\n loadHostApiEnum: () => Promise.resolve(fakeHostApiEnum),\n });\n\n const result = await provider.connect();\n // Connect should succeed even without the hostApi bridge —\n // permission is best-effort.\n expect(result.ok).toBe(true);\n });\n\n test(\"skipped when requestChainSubmitPermission is false\", async () => {\n const captured: unknown[] = [];\n const hostApi: HostApiPermissionBridge = {\n permission: (request) => {\n captured.push(request);\n return fakeResult(undefined);\n },\n };\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: new Uint8Array(32).fill(0x01) }],\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider, { hostApi })),\n loadHostApiEnum: () => Promise.resolve(fakeHostApiEnum),\n requestChainSubmitPermission: false,\n });\n\n await provider.connect();\n expect(captured).toHaveLength(0);\n });\n\n test(\"deprecated requestTransactionSubmitPermission alias still controls the request\", async () => {\n const captured: unknown[] = [];\n const hostApi: HostApiPermissionBridge = {\n permission: (request) => {\n captured.push(request);\n return fakeResult(undefined);\n },\n };\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: new Uint8Array(32).fill(0x01) }],\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider, { hostApi })),\n loadHostApiEnum: () => Promise.resolve(fakeHostApiEnum),\n // Old name; new code path should still respect it as `false`.\n requestTransactionSubmitPermission: false,\n });\n\n await provider.connect();\n expect(captured).toHaveLength(0);\n });\n\n test(\"connect succeeds even when permission request rejects\", async () => {\n // Whatever the host says about permission, connect() should\n // still return ok — the consumer can sign later with whatever\n // permission they negotiate.\n const hostApi: HostApiPermissionBridge = {\n permission: () => fakeResult(undefined, { tag: \"PermissionDenied\" }),\n };\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: new Uint8Array(32).fill(0x01) }],\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider, { hostApi })),\n loadHostApiEnum: () => Promise.resolve(fakeHostApiEnum),\n });\n\n const result = await provider.connect();\n expect(result.ok).toBe(true);\n });\n\n test(\"connect succeeds even when the hostApiEnum loader throws (codec drift)\", async () => {\n // The original bug: the v1 RemotePermission codec didn't\n // recognize the TransactionSubmit tag and threw client-side.\n // Even when something like that happens, connect() must\n // remain ok — permission is best-effort.\n const hostApi: HostApiPermissionBridge = {\n permission: () => fakeResult(undefined),\n };\n const mockProvider = createMockProvider({\n accounts: [{ publicKey: new Uint8Array(32).fill(0x01) }],\n });\n const provider = new HostProvider({\n maxRetries: 1,\n loadSdk: () => Promise.resolve(createMockSdk(mockProvider, { hostApi })),\n loadHostApiEnum: () => Promise.reject(new Error(\"codec drift\")),\n });\n\n const result = await provider.connect();\n expect(result.ok).toBe(true);\n });\n });\n\n describe(\"formatError\", () => {\n // Direct unit tests for the helper. The previous implementation\n // collapsed any tagged-enum error to its outer tag — losing the\n // inner reason. The fix surfaces the inner Error-shape (name +\n // message) and recurses through nested tagged enums.\n\n test(\"returns a string for a primitive error\", () => {\n expect(formatError(\"Rejected\")).toBe(\"Rejected\");\n expect(formatError(42)).toBe(\"42\");\n expect(formatError(null)).toBe(\"null\");\n expect(formatError(undefined)).toBe(\"undefined\");\n });\n\n test(\"surfaces inner Error name + message under the outer tag\", () => {\n // Simulates the exact shape the original bug produced:\n // `{ tag: \"v1\", value: { name: \"GenericError\", message: \"...\" } }`\n const wrapped = {\n tag: \"v1\",\n value: {\n name: \"GenericError\",\n message: \"Unknown error: inner[tag] is not a function\",\n },\n };\n const out = formatError(wrapped);\n expect(out).toContain(\"v1\");\n expect(out).toContain(\"GenericError\");\n expect(out).toContain(\"inner[tag] is not a function\");\n });\n\n test(\"strips the redundant 'Error' name when the inner is a plain Error\", () => {\n const wrapped = {\n tag: \"v1\",\n value: { name: \"Error\", message: \"boom\" },\n };\n expect(formatError(wrapped)).toBe(\"v1 → boom\");\n });\n\n test(\"recurses through nested tagged-enum errors\", () => {\n const wrapped = {\n tag: \"v1\",\n value: { tag: \"Inner\", value: { name: \"NestedErr\", message: \"deep\" } },\n };\n expect(formatError(wrapped)).toContain(\"v1\");\n expect(formatError(wrapped)).toContain(\"Inner\");\n expect(formatError(wrapped)).toContain(\"NestedErr\");\n expect(formatError(wrapped)).toContain(\"deep\");\n });\n\n test(\"returns just the outer tag when value is undefined\", () => {\n expect(formatError({ tag: \"PermissionDenied\" })).toBe(\"PermissionDenied\");\n });\n\n test(\"formats a primitive inner value alongside the tag\", () => {\n expect(formatError({ tag: \"v1\", value: \"code-42\" })).toBe(\"v1 (code-42)\");\n });\n });\n\n describe(\"RemotePermission codec interop\", () => {\n // Smoke test that the wire payload we build (`ChainSubmit`) round-trips\n // through the real host-api codec. The previous bug shipped\n // `TransactionSubmit`, which the codec rejects — locking this in here\n // catches a regression at the codec layer without needing the host.\n test(\"encodes ChainSubmit payload without throwing\", async () => {\n const { RemotePermission } = await import(\"@novasamatech/host-api\");\n const payload = { tag: \"ChainSubmit\" as const, value: undefined };\n const encoded = RemotePermission.enc(payload);\n expect(encoded).toBeInstanceOf(Uint8Array);\n const decoded = RemotePermission.dec(encoded);\n expect(decoded.tag).toBe(\"ChainSubmit\");\n });\n\n test(\"rejects the legacy TransactionSubmit tag\", async () => {\n const { RemotePermission } = await import(\"@novasamatech/host-api\");\n // `TransactionSubmit` is not a valid variant in v1 — the codec\n // should refuse to encode it. This proves the codec actually\n // validates tags (so test 1 isn't a tautology).\n expect(() =>\n RemotePermission.enc({\n tag: \"TransactionSubmit\",\n value: undefined,\n } as never),\n ).toThrow();\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport type { PolkadotSigner } from \"polkadot-api\";\n\nimport { createLogger } from \"@parity/product-sdk-logger\";\n\nimport {\n AccountNotFoundError,\n DestroyedError,\n HostDisconnectedError,\n HostUnavailableError,\n SigningFailedError,\n type SignerError,\n} from \"./errors.js\";\nimport { getHostLocalStorage, requestResourceAllocation } from \"@parity/product-sdk-host\";\nimport { DevProvider } from \"./providers/dev.js\";\nimport { HostProvider } from \"./providers/host.js\";\nimport type { ContextualAlias, ProductAccount, RingLocation } from \"./providers/host.js\";\nimport type { SignerProvider } from \"./providers/types.js\";\nimport { withRetry } from \"./retry.js\";\nimport type {\n AccountPersistence,\n ConnectContext,\n ConnectionStatus,\n OnConnect,\n ProviderType,\n Result,\n SignerAccount,\n SignerManagerOptions,\n SignerState,\n} from \"./types.js\";\nimport { err, ok } from \"./types.js\";\n\nconst log = createLogger(\"signer\");\n\nconst DEFAULT_HOST_TIMEOUT = 10_000;\nconst DEFAULT_MAX_RETRIES = 3;\nconst DEFAULT_SS58_PREFIX = 42;\nconst DEFAULT_DAPP_NAME = \"product-sdk\";\n\n// Auto-reconnect settings for host disconnect events\nconst RECONNECT_MAX_ATTEMPTS = 5;\nconst RECONNECT_INITIAL_DELAY = 1_000;\nconst RECONNECT_MAX_DELAY = 15_000;\n\nfunction persistenceStorageKey(dappName: string): string {\n return `product-sdk:signer:${dappName}:selectedAccount`;\n}\n\n/* @integration */\n/**\n * Auto-detect the best available persistence adapter.\n *\n * Uses hostLocalStorage from the host container. Returns null if unavailable.\n */\nasync function detectPersistence(): Promise<AccountPersistence | null> {\n try {\n const hostStorage = await getHostLocalStorage();\n if (hostStorage) {\n log.debug(\"using hostLocalStorage for persistence\");\n return {\n getItem: (key) => hostStorage.readString(key),\n setItem: (key, value) => hostStorage.writeString(key, value),\n removeItem: (key) => hostStorage.writeString(key, \"\"),\n };\n }\n } catch {\n // host storage not available\n }\n return null;\n}\n\nfunction initialState(): SignerState {\n return {\n status: \"disconnected\",\n accounts: [],\n selectedAccount: null,\n activeProvider: null,\n error: null,\n };\n}\n\nfunction resolveSelectedAccount(\n accounts: readonly SignerAccount[],\n preferredAddress: string | null | undefined,\n): SignerAccount | null {\n if (preferredAddress) {\n const found = accounts.find((a) => a.address === preferredAddress);\n if (found) return found;\n }\n return accounts[0] ?? null;\n}\n\n/**\n * Core orchestrator for signer management.\n *\n * Manages account discovery and signer creation via the Host API.\n * Framework-agnostic — use the `subscribe()` pattern to integrate with\n * React, Vue, or any framework.\n *\n * ## Lifecycle\n *\n * ```\n * disconnected → connecting → connected ──── selectAccount / signRaw / …\n * ▲ │ │\n * │ ▼ ▼\n * └── disconnect() provider drops → auto-reconnect → connected\n * (onConnect re-fires)\n *\n * ┌─ destroy() ──► (terminal — manager unusable)\n * ▼\n * ```\n *\n * ## Callbacks\n *\n * - **`subscribe(cb)`** fires synchronously on every state mutation, in\n * registration order, inside the call stack that mutated state. It does\n * **not** fire with the initial state — call `getState()` if you need a\n * priming read. Multiple mutations during the same operation produce\n * multiple notifications.\n *\n * - **`onConnect(account, ctx)`** (from `SignerManagerOptions`) fires\n * exactly when the manager transitions from non-connected to\n * `\"connected\"` with a selected account. It fires on a microtask\n * *after* the `subscribe` notification, so subscribers always observe\n * `state.status === \"connected\"` before `onConnect`'s side effects run.\n * It re-fires after auto-reconnect (the SDK reconnects automatically\n * when the provider drops), and re-fires after a fresh `connect()`.\n *\n * - **Internal `onAccountsChange` wiring** is worth a behavioral note:\n * when the provider reports an updated account list, the manager\n * preserves the current selection if its address is still present, or\n * sets `selectedAccount` to `null` if it isn't — it does **not** fall\n * back to `accounts[0]`. The fallback-to-first only applies on\n * connect-success, where there is no prior selection to preserve.\n *\n * ## `disconnect()` vs `destroy()`\n *\n * - `disconnect()` resets state to initial. Subsequent `connect()` calls\n * work normally. Reversible.\n * - `destroy()` is **terminal**: the instance is marked unusable, all\n * subscribers are cleared, and any further call returns `DestroyedError`.\n * Use in framework teardown (React `useEffect` cleanup, HMR dispose).\n *\n * Both methods cancel any in-flight `connect`, reconnect attempt, and\n * `onConnect` callback (the `ctx.signal` becomes aborted).\n *\n * @example\n * ```ts\n * const manager = new SignerManager({\n * onConnect: async (_account, { requestResourceAllocation }) => {\n * await requestResourceAllocation([{ tag: \"AutoSigning\", value: undefined }]);\n * },\n * });\n * manager.subscribe(state => console.log(state.status));\n *\n * await manager.connect(); // host provider (default)\n * await manager.connect(\"dev\"); // Alice / Bob / … for testing\n *\n * manager.selectAccount(\"5GrwvaEF…\");\n * const signer = manager.getSigner();\n * ```\n */\nexport class SignerManager {\n private state: SignerState;\n private provider: SignerProvider | null = null;\n private subscribers = new Set<(state: SignerState) => void>();\n private cleanups: (() => void)[] = [];\n private isDestroyed = false;\n private reconnectController: AbortController | null = null;\n private connectController: AbortController | null = null;\n\n private readonly ss58Prefix: number;\n private readonly hostTimeout: number;\n private readonly maxRetries: number;\n private readonly providerFactory: ((type: ProviderType) => SignerProvider) | undefined;\n private readonly dappName: string;\n private readonly persistenceOption: AccountPersistence | null | undefined;\n private resolvedPersistence: AccountPersistence | null | undefined;\n private readonly onConnectCallback: OnConnect | undefined;\n private onConnectController: AbortController | null = null;\n\n constructor(options?: SignerManagerOptions) {\n this.ss58Prefix = options?.ss58Prefix ?? DEFAULT_SS58_PREFIX;\n this.hostTimeout = options?.hostTimeout ?? DEFAULT_HOST_TIMEOUT;\n this.maxRetries = options?.maxRetries ?? DEFAULT_MAX_RETRIES;\n this.providerFactory = options?.createProvider;\n this.dappName = options?.dappName ?? DEFAULT_DAPP_NAME;\n // null = disabled, undefined = auto-detect, AccountPersistence = explicit\n this.persistenceOption = options?.persistence;\n this.resolvedPersistence = options?.persistence;\n this.onConnectCallback = options?.onConnect;\n this.state = initialState();\n }\n\n private async getPersistence(): Promise<AccountPersistence | null> {\n if (this.persistenceOption === null) return null;\n if (this.persistenceOption !== undefined) return this.persistenceOption;\n // Auto-detect (lazy, cached)\n if (this.resolvedPersistence === undefined) {\n this.resolvedPersistence = await detectPersistence();\n }\n return this.resolvedPersistence ?? null;\n }\n\n /** Get a snapshot of the current state. */\n getState(): SignerState {\n return this.state;\n }\n\n /**\n * Subscribe to state changes.\n *\n * The callback fires synchronously on every state mutation, in\n * registration order, inside the call stack that mutated state. It\n * does **not** fire with the current state at subscription time —\n * call {@link getState} if you need a priming read.\n *\n * For \"fired once when the user connects\" semantics, prefer the\n * {@link SignerManagerOptions.onConnect} option instead of gating on\n * `state.status` inside this callback — `subscribe` will fire many\n * times while connected (`selectAccount`, account swaps, etc.).\n *\n * @returns an unsubscribe function.\n */\n subscribe(callback: (state: SignerState) => void): () => void {\n this.subscribers.add(callback);\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n /**\n * Connect to a provider.\n *\n * If no provider type is specified, connects to the Host API.\n * The SDK is designed to run exclusively inside a host container.\n *\n * When connecting to a specific provider type:\n * - `\"host\"`: Connect to the Host API (default, recommended)\n * - `\"dev\"`: Connect using dev accounts (for testing)\n */\n async connect(providerType?: ProviderType): Promise<Result<SignerAccount[], SignerError>> {\n if (this.isDestroyed) {\n return err(new DestroyedError());\n }\n\n // Cancel any in-flight connection or reconnect attempt\n this.cancelConnect();\n this.cancelReconnect();\n this.cancelOnConnect();\n this.connectController = new AbortController();\n const signal = this.connectController.signal;\n\n // Clean up previous connection\n this.disconnectInternal();\n\n this.setState({ status: \"connecting\", error: null });\n\n // Default to host provider - the SDK is designed for container-only usage\n const targetProvider = providerType ?? \"host\";\n return this.connectToProvider(targetProvider, signal);\n }\n\n /**\n * Disconnect from the current provider and reset state to initial.\n *\n * Reversible — subsequent `connect()` calls work normally. Cancels\n * any in-flight `connect`, reconnect attempt, or `onConnect` callback\n * (`ctx.signal` becomes aborted).\n */\n disconnect(): void {\n this.cancelConnect();\n this.cancelReconnect();\n this.cancelOnConnect();\n this.disconnectInternal();\n this.setState(initialState());\n log.info(\"disconnected\");\n }\n\n /**\n * Select an account by address.\n * Returns the account on success, or ACCOUNT_NOT_FOUND error.\n */\n selectAccount(address: string): Result<SignerAccount, SignerError> {\n if (this.isDestroyed) {\n return err(new DestroyedError());\n }\n\n const account = this.state.accounts.find((a) => a.address === address);\n if (!account) {\n log.warn(\"account not found\", { address });\n return err(new AccountNotFoundError(address));\n }\n\n this.setState({ selectedAccount: account });\n this.persistAccount(address);\n log.debug(\"account selected\", { address });\n return ok(account);\n }\n\n /**\n * Get the PolkadotSigner for the currently selected account.\n * Returns null if no account is selected or manager is disconnected.\n */\n getSigner(): PolkadotSigner | null {\n return this.state.selectedAccount?.getSigner() ?? null;\n }\n\n /**\n * Sign arbitrary bytes with the currently selected account.\n *\n * Convenience wrapper around `PolkadotSigner.signBytes` — useful for\n * master key derivation, message signing, and proof generation without\n * constructing a full transaction.\n *\n * Returns a SIGNING_FAILED error if no account is selected or signing fails.\n */\n async signRaw(data: Uint8Array): Promise<Result<Uint8Array, SignerError>> {\n if (this.isDestroyed) {\n return err(new DestroyedError());\n }\n\n const signer = this.getSigner();\n if (!signer) {\n return err(new SigningFailedError(null, \"No account selected\"));\n }\n\n try {\n const signature = await signer.signBytes(data);\n return ok(signature);\n } catch (cause) {\n log.error(\"signRaw failed\", { cause });\n return err(new SigningFailedError(cause));\n }\n }\n\n // ── Host-only: Product Account API ─────────────────────────────\n\n /**\n * Fetch the connected user's host identity.\n *\n * This uses the Host API identity permission path and is only available\n * when connected through the host provider. The primary username can be\n * used by higher-level helpers that need to bind an action to the user's\n * DotNS / people username.\n */\n async getUserId(): Promise<Result<{ primaryUsername: string }, SignerError>> {\n if (this.isDestroyed) return err(new DestroyedError());\n\n const host = this.getHostProvider();\n if (!host) {\n return err(\n new HostUnavailableError(\"User identity requires a host provider connection\"),\n );\n }\n return host.getUserId();\n }\n\n /**\n * Get an app-scoped product account from the host.\n *\n * Product accounts are derived by the host wallet for each app, identified\n * by `dotNsIdentifier` (e.g., \"mark3t.dot\"). Only available when connected\n * via the host provider — returns HOST_UNAVAILABLE otherwise.\n *\n * @example\n * ```ts\n * const result = await manager.getProductAccount(\"myapp.dot\");\n * if (result.ok) {\n * const signer = result.value.getSigner();\n * }\n * ```\n */\n async getProductAccount(\n dotNsIdentifier: string,\n derivationIndex = 0,\n ): Promise<Result<SignerAccount, SignerError>> {\n if (this.isDestroyed) return err(new DestroyedError());\n\n const host = this.getHostProvider();\n if (!host) {\n return err(\n new HostUnavailableError(\"Product accounts require a host provider connection\"),\n );\n }\n return host.getProductAccount(dotNsIdentifier, derivationIndex);\n }\n\n /**\n * Get a contextual alias for a product account via Ring VRF.\n *\n * Aliases prove account membership in a ring without revealing which\n * account produced the alias. Only available when connected via the host\n * provider — returns HOST_UNAVAILABLE otherwise.\n */\n async getProductAccountAlias(\n dotNsIdentifier: string,\n derivationIndex = 0,\n ): Promise<Result<ContextualAlias, SignerError>> {\n if (this.isDestroyed) return err(new DestroyedError());\n\n const host = this.getHostProvider();\n if (!host) {\n return err(\n new HostUnavailableError(\n \"Product account aliases require a host provider connection\",\n ),\n );\n }\n return host.getProductAccountAlias(dotNsIdentifier, derivationIndex);\n }\n\n /**\n * Create a Ring VRF proof for anonymous operations.\n *\n * Proves that the signer is a member of the ring at the given location\n * without revealing which member. Only available when connected via the\n * host provider — returns HOST_UNAVAILABLE otherwise.\n */\n async createRingVRFProof(\n dotNsIdentifier: string,\n derivationIndex: number,\n location: RingLocation,\n message: Uint8Array,\n ): Promise<Result<Uint8Array, SignerError>> {\n if (this.isDestroyed) return err(new DestroyedError());\n\n const host = this.getHostProvider();\n if (!host) {\n return err(\n new HostUnavailableError(\"Ring VRF proofs require a host provider connection\"),\n );\n }\n return host.createRingVRFProof(dotNsIdentifier, derivationIndex, location, message);\n }\n\n /**\n * Destroy the manager and release all resources.\n *\n * **Terminal** — clears all subscribers, cancels in-flight work, and\n * marks the instance unusable. Any subsequent method returns\n * `DestroyedError`. Idempotent. Use in framework teardown (React\n * `useEffect` cleanup, HMR dispose). For a reversible reset, use\n * {@link disconnect} instead.\n */\n destroy(): void {\n if (this.isDestroyed) return;\n this.isDestroyed = true;\n this.cancelConnect();\n this.cancelReconnect();\n this.cancelOnConnect();\n this.disconnectInternal();\n this.subscribers.clear();\n this.state = initialState();\n log.info(\"manager destroyed\");\n }\n\n // ── Private ──────────────────────────────────────────────────────\n\n private async connectToProvider(\n type: ProviderType,\n signal?: AbortSignal,\n ): Promise<Result<SignerAccount[], SignerError>> {\n const provider = this.createProvider(type);\n\n const result = await provider.connect(signal);\n if (!result.ok) {\n provider.disconnect();\n this.setState({ status: \"disconnected\", error: result.error });\n return result;\n }\n\n // Success — set up provider\n this.provider = provider;\n\n // Wire status change listener\n const statusUnsub = provider.onStatusChange((status) => {\n this.handleProviderStatusChange(status);\n });\n this.cleanups.push(statusUnsub);\n\n // Wire account change listener\n const accountUnsub = provider.onAccountsChange((accounts) => {\n this.setState({\n accounts,\n // Clear selected if no longer in list\n selectedAccount:\n accounts.find((a) => a.address === this.state.selectedAccount?.address) ?? null,\n });\n });\n this.cleanups.push(accountUnsub);\n\n const accounts = result.value;\n\n const persisted = await this.loadPersistedAccount();\n const selectedAccount = resolveSelectedAccount(accounts, persisted);\n\n this.setState({\n status: \"connected\",\n accounts,\n activeProvider: type,\n selectedAccount,\n error: null,\n });\n\n if (selectedAccount) {\n this.persistAccount(selectedAccount.address);\n this.fireOnConnect(selectedAccount);\n }\n\n log.info(\"connected\", { provider: type, accounts: accounts.length });\n return result;\n }\n\n private createProvider(type: ProviderType): SignerProvider {\n if (this.providerFactory) {\n return this.providerFactory(type);\n }\n\n switch (type) {\n case \"host\":\n return new HostProvider({\n ss58Prefix: this.ss58Prefix,\n maxRetries: this.maxRetries,\n retryDelay: 500,\n });\n case \"dev\":\n return new DevProvider({\n ss58Prefix: this.ss58Prefix,\n });\n default:\n throw new Error(\n `Unsupported provider type: ${type}. The SDK only supports \"host\" and \"dev\" providers.`,\n );\n }\n }\n\n /* @integration */\n private handleProviderStatusChange(status: ConnectionStatus): void {\n if (status === \"disconnected\" && this.state.status === \"connected\") {\n log.warn(\"provider disconnected, attempting reconnect\");\n this.attemptReconnect();\n }\n }\n\n /* @integration */\n private attemptReconnect(): void {\n this.cancelReconnect();\n\n const providerType = this.state.activeProvider;\n if (!providerType) return;\n\n this.reconnectController = new AbortController();\n const signal = this.reconnectController.signal;\n\n this.setState({ status: \"connecting\" });\n\n withRetry(\n async () => {\n if (signal.aborted) {\n return err(new HostDisconnectedError(\"Reconnect cancelled\"));\n }\n\n this.disconnectInternal();\n const provider = this.createProvider(providerType);\n\n // Compose hostTimeout with reconnect signal for host providers\n const connectSignal =\n providerType === \"host\"\n ? AbortSignal.any([signal, AbortSignal.timeout(this.hostTimeout)])\n : signal;\n const result = await provider.connect(connectSignal);\n\n if (!result.ok) return result;\n\n // Re-wire provider\n this.provider = provider;\n const statusUnsub = provider.onStatusChange((s) =>\n this.handleProviderStatusChange(s),\n );\n this.cleanups.push(statusUnsub);\n\n const accountUnsub = provider.onAccountsChange((accounts) => {\n this.setState({\n accounts,\n selectedAccount:\n accounts.find(\n (a) => a.address === this.state.selectedAccount?.address,\n ) ?? null,\n });\n });\n this.cleanups.push(accountUnsub);\n\n const accounts = result.value;\n const selectedAccount = resolveSelectedAccount(\n accounts,\n this.state.selectedAccount?.address,\n );\n this.setState({\n status: \"connected\",\n accounts,\n activeProvider: providerType,\n selectedAccount,\n error: null,\n });\n\n if (selectedAccount) {\n this.fireOnConnect(selectedAccount);\n }\n\n log.info(\"reconnected\", { provider: providerType });\n return result;\n },\n {\n maxAttempts: RECONNECT_MAX_ATTEMPTS,\n initialDelay: RECONNECT_INITIAL_DELAY,\n maxDelay: RECONNECT_MAX_DELAY,\n signal,\n },\n )\n .then((result) => {\n if (!result.ok && !signal.aborted) {\n log.error(\"reconnect failed after all retries\", { error: result.error });\n this.setState({\n status: \"disconnected\",\n error: new HostDisconnectedError(\"Reconnect failed after all retries\"),\n });\n }\n })\n .catch((cause) => {\n log.error(\"unexpected reconnect error\", { cause });\n this.setState({\n status: \"disconnected\",\n error: new HostDisconnectedError(\"Reconnect failed unexpectedly\"),\n });\n });\n }\n\n /** Returns the underlying HostProvider if connected via host, or null otherwise. */\n private getHostProvider(): HostProvider | null {\n if (this.provider && this.state.activeProvider === \"host\") {\n return this.provider as HostProvider;\n }\n return null;\n }\n\n private cancelConnect(): void {\n this.connectController?.abort();\n this.connectController = null;\n }\n\n private cancelReconnect(): void {\n this.reconnectController?.abort();\n this.reconnectController = null;\n }\n\n private cancelOnConnect(): void {\n this.onConnectController?.abort();\n this.onConnectController = null;\n }\n\n private fireOnConnect(account: SignerAccount): void {\n const callback = this.onConnectCallback;\n if (!callback) return;\n\n this.cancelOnConnect();\n const controller = new AbortController();\n this.onConnectController = controller;\n\n const ctx: ConnectContext = {\n signal: controller.signal,\n requestResourceAllocation,\n };\n\n // Defer so connect()/attemptReconnect() return before the callback fires —\n // subscribers see \"connected\" before any onConnect side-effects land.\n queueMicrotask(async () => {\n try {\n await callback(account, ctx);\n } catch (cause) {\n log.warn(\"onConnect callback threw\", { cause });\n } finally {\n // Only clear if this controller is still the active one; a\n // subsequent re-connect may have already swapped in a new one.\n if (this.onConnectController === controller) {\n this.onConnectController = null;\n }\n }\n });\n }\n\n private disconnectInternal(): void {\n for (const cleanup of this.cleanups) {\n cleanup();\n }\n this.cleanups = [];\n\n if (this.provider) {\n this.provider.disconnect();\n this.provider = null;\n }\n }\n\n private persistAccount(address: string): void {\n void this.getPersistence()\n .then((p) => {\n if (p) {\n const key = persistenceStorageKey(this.dappName);\n return Promise.resolve(p.setItem(key, address));\n }\n })\n .catch(() => {\n log.debug(\"failed to persist selected account\");\n });\n }\n\n private async loadPersistedAccount(): Promise<string | null> {\n try {\n const p = await this.getPersistence();\n if (!p) return null;\n const key = persistenceStorageKey(this.dappName);\n const value = await Promise.resolve(p.getItem(key));\n // Treat empty strings as null (hostLocalStorage uses writeString(\"\") for deletion)\n return value || null;\n } catch {\n log.debug(\"failed to load persisted account\");\n return null;\n }\n }\n\n private setState(patch: Partial<SignerState>): void {\n this.state = { ...this.state, ...patch };\n for (const subscriber of this.subscribers) {\n subscriber(this.state);\n }\n }\n}\n\nif (import.meta.vitest) {\n const { test, expect, describe, vi } = import.meta.vitest;\n\n function mockAccount(\n address = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\",\n ): SignerAccount {\n return {\n address,\n h160Address: \"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\",\n publicKey: new Uint8Array(32),\n name: \"MockAccount\",\n source: \"dev\",\n getSigner: () => ({ publicKey: new Uint8Array(32) }) as never,\n };\n }\n\n function mockProvider(accounts: SignerAccount[] = [mockAccount()]) {\n return {\n type: \"dev\" as const,\n connect: vi.fn().mockResolvedValue(ok(accounts)),\n disconnect: vi.fn(),\n onStatusChange: vi.fn().mockReturnValue(() => {}),\n onAccountsChange: vi.fn().mockReturnValue(() => {}),\n } satisfies SignerProvider;\n }\n\n /** Wait for the `onConnect` microtask chain to drain. */\n async function flush(): Promise<void> {\n await Promise.resolve();\n await Promise.resolve();\n }\n\n describe(\"SignerManager.onConnect\", () => {\n test(\"fires once on initial connect with the selected account\", async () => {\n const onConnect = vi.fn();\n const manager = new SignerManager({\n createProvider: () => mockProvider(),\n onConnect,\n });\n const result = await manager.connect(\"dev\");\n await flush();\n expect(result.ok).toBe(true);\n expect(onConnect).toHaveBeenCalledTimes(1);\n expect(onConnect.mock.calls[0][0].address).toBe(\n \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\",\n );\n manager.destroy();\n });\n\n test(\"does not fire on subsequent state mutations while connected\", async () => {\n const onConnect = vi.fn();\n const manager = new SignerManager({\n createProvider: () => mockProvider(),\n onConnect,\n });\n await manager.connect(\"dev\");\n await flush();\n // Mutate state — selecting the same account again.\n manager.selectAccount(\"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\");\n await flush();\n expect(onConnect).toHaveBeenCalledTimes(1);\n manager.destroy();\n });\n\n test(\"fires again after disconnect + reconnect\", async () => {\n const onConnect = vi.fn();\n const manager = new SignerManager({\n createProvider: () => mockProvider(),\n onConnect,\n });\n await manager.connect(\"dev\");\n await flush();\n manager.disconnect();\n await manager.connect(\"dev\");\n await flush();\n expect(onConnect).toHaveBeenCalledTimes(2);\n manager.destroy();\n });\n\n test(\"does not fire when no account is selected (empty accounts list)\", async () => {\n const onConnect = vi.fn();\n const manager = new SignerManager({\n createProvider: () => mockProvider([]),\n onConnect,\n });\n await manager.connect(\"dev\");\n await flush();\n expect(onConnect).not.toHaveBeenCalled();\n manager.destroy();\n });\n\n test(\"errors thrown from onConnect are caught and don't break connected state\", async () => {\n const onConnect = vi.fn().mockRejectedValue(new Error(\"boom\"));\n const manager = new SignerManager({\n createProvider: () => mockProvider(),\n onConnect,\n });\n const result = await manager.connect(\"dev\");\n await flush();\n expect(result.ok).toBe(true);\n expect(manager.getState().status).toBe(\"connected\");\n manager.destroy();\n });\n\n test(\"ctx.signal aborts when disconnect() runs mid-callback\", async () => {\n const signals: AbortSignal[] = [];\n const onConnect = vi.fn().mockImplementation((_, ctx) => {\n signals.push(ctx.signal);\n return new Promise<void>(() => {});\n });\n const manager = new SignerManager({\n createProvider: () => mockProvider(),\n onConnect,\n });\n await manager.connect(\"dev\");\n await flush();\n expect(signals[0]?.aborted).toBe(false);\n manager.disconnect();\n expect(signals[0]?.aborted).toBe(true);\n manager.destroy();\n });\n\n test(\"ctx.signal aborts when destroy() runs mid-callback\", async () => {\n const signals: AbortSignal[] = [];\n const onConnect = vi.fn().mockImplementation((_, ctx) => {\n signals.push(ctx.signal);\n return new Promise<void>(() => {});\n });\n const manager = new SignerManager({\n createProvider: () => mockProvider(),\n onConnect,\n });\n await manager.connect(\"dev\");\n await flush();\n manager.destroy();\n expect(signals[0]?.aborted).toBe(true);\n });\n\n test(\"ctx exposes requestResourceAllocation function\", async () => {\n const onConnect = vi.fn().mockImplementation((_, ctx) => {\n expect(typeof ctx.requestResourceAllocation).toBe(\"function\");\n });\n const manager = new SignerManager({\n createProvider: () => mockProvider(),\n onConnect,\n });\n await manager.connect(\"dev\");\n await flush();\n expect(onConnect).toHaveBeenCalledTimes(1);\n manager.destroy();\n });\n\n test(\"re-connecting cancels in-flight onConnect from the prior session\", async () => {\n const signals: AbortSignal[] = [];\n const onConnect = vi.fn().mockImplementation((_, ctx) => {\n signals.push(ctx.signal);\n return new Promise<void>(() => {});\n });\n const manager = new SignerManager({\n createProvider: () => mockProvider(),\n onConnect,\n });\n await manager.connect(\"dev\");\n await flush();\n await manager.connect(\"dev\");\n await flush();\n expect(signals).toHaveLength(2);\n expect(signals[0].aborted).toBe(true);\n expect(signals[1].aborted).toBe(false);\n manager.destroy();\n });\n });\n\n describe(\"SignerManager.getUserId\", () => {\n test(\"returns HostUnavailableError when not connected via host (dev provider)\", async () => {\n const manager = new SignerManager({\n createProvider: () => mockProvider(),\n });\n await manager.connect(\"dev\");\n await flush();\n\n // Dev provider is active, not host — getUserId() should refuse\n // rather than attempt a host RPC.\n const result = await manager.getUserId();\n expect(result.ok).toBe(false);\n if (!result.ok) {\n expect(result.error).toBeInstanceOf(HostUnavailableError);\n expect(result.error.message).toMatch(/host provider/i);\n }\n manager.destroy();\n });\n\n test(\"returns DestroyedError after destroy()\", async () => {\n const manager = new SignerManager({\n createProvider: () => mockProvider(),\n });\n await manager.connect(\"dev\");\n await flush();\n manager.destroy();\n\n const result = await manager.getUserId();\n expect(result.ok).toBe(false);\n if (!result.ok) {\n expect(result.error).toBeInstanceOf(DestroyedError);\n }\n });\n\n test(\"delegates to HostProvider.getUserId when connected via host\", async () => {\n // Build a host-shaped fake provider that satisfies the SignerManager's\n // `activeProvider === \"host\"` check and exposes a getUserId that\n // resolves to a known username.\n const fakeHostProvider = {\n type: \"host\" as const,\n connect: vi.fn().mockResolvedValue(ok([mockAccount()])),\n disconnect: vi.fn(),\n onStatusChange: vi.fn().mockReturnValue(() => {}),\n onAccountsChange: vi.fn().mockReturnValue(() => {}),\n getUserId: vi.fn().mockResolvedValue(ok({ primaryUsername: \"alice.dot\" })),\n };\n\n const manager = new SignerManager({\n createProvider: (type) =>\n type === \"host\"\n ? (fakeHostProvider as unknown as SignerProvider)\n : mockProvider(),\n });\n await manager.connect(\"host\");\n await flush();\n\n const result = await manager.getUserId();\n expect(result.ok).toBe(true);\n if (result.ok) {\n expect(result.value.primaryUsername).toBe(\"alice.dot\");\n }\n expect(fakeHostProvider.getUserId).toHaveBeenCalledTimes(1);\n manager.destroy();\n });\n });\n}\n"]}
|