@parity/product-sdk-host 0.19.1 → 0.21.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 +19 -0
- package/dist/index.js +27 -3
- package/dist/index.js.map +1 -1
- package/dist/testing.js +4 -0
- package/dist/testing.js.map +1 -1
- package/package.json +4 -4
- package/src/accounts.ts +108 -3
- package/src/testing.ts +4 -0
- package/src/truapi.ts +34 -0
package/dist/testing.js
CHANGED
|
@@ -102,6 +102,8 @@ function createFakeTruApiClient(options) {
|
|
|
102
102
|
createTransactionWithLegacyAccount: () => okAsync({ transaction: signature }),
|
|
103
103
|
signRaw: () => okAsync({ signature }),
|
|
104
104
|
signRawWithLegacyAccount: () => okAsync({ signature }),
|
|
105
|
+
signRawUnwatermarkedDeprecated: () => okAsync({ signature }),
|
|
106
|
+
signRawUnwatermarkedDeprecatedWithLegacyAccount: () => okAsync({ signature }),
|
|
105
107
|
signPayload: () => okAsync({ signature }),
|
|
106
108
|
signPayloadWithLegacyAccount: () => okAsync({ signature })
|
|
107
109
|
},
|
|
@@ -146,6 +148,8 @@ function createFakeTruApiClient(options) {
|
|
|
146
148
|
notifications: notModeled("notifications"),
|
|
147
149
|
payment: notModeled("payment"),
|
|
148
150
|
permissions: notModeled("permissions"),
|
|
151
|
+
pocket: notModeled("pocket"),
|
|
152
|
+
renderer: notModeled("renderer"),
|
|
149
153
|
resourceAllocation: notModeled("resourceAllocation"),
|
|
150
154
|
theme: notModeled("theme")
|
|
151
155
|
};
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/testing.ts"],"names":[],"mappings":"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","file":"testing.js","sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Test fakes for `@parity/product-sdk-host`.\n *\n * The host reaches its container through one cached `TrUApiClient`.\n * `createFakeHost()` injects a fake via `setTruApiClient` so every accessor\n * (`getHostLocalStorage`, `getAccountsProvider`, `getStatementStore`, `getTruApi`,\n * …) resolves against it and `isInsideContainer()` reports `true` — which also\n * makes a default `SignerManager`, `local-storage` auto-detection, and the\n * `statement-store` / `cloud-storage` host paths testable.\n *\n * Of the `chain` domain only `getChainInfo` is modeled, so host chain discovery\n * (and `getChainAPI()` on top of it) resolves in tests; see the `chainInfo`\n * option. Not modeled: the rest of the PAPI `chain` JSON-RPC surface behind\n * `getHostProvider()` — there's no chain-read fake, by design; the host owns RPC\n * selection — the `system` domain's `info` / `getProductContext`, and the\n * `chat` / `coinPayment` / `entropy` / `locale` / `notifications` / `payment` /\n * `permissions` / `resourceAllocation` / `theme` domains. Touching\n * an unmodeled domain throws a descriptive error rather than failing with\n * `undefined is not a function`.\n *\n * @packageDocumentation\n */\nimport type { ObservableLike, Observer, Subscription, TrUApiClient } from \"@parity/truapi\";\nimport { errAsync, okAsync } from \"neverthrow\";\n\nimport type { HostChainIdentifier } from \"./chain-discovery.js\";\n\nimport { type HostConnectionStatus, emitConnectionStatus, setTruApiClient } from \"./transport.js\";\n\nexport { setTruApiClient, emitConnectionStatus };\n\n/**\n * The public surface of a generated truapi domain client. `keyof` skips private\n * members, so this is the shape a plain object can implement — the classes\n * themselves are unimplementable outside truapi because of their\n * `private transport`.\n */\ntype PublicSurface<C> = { [K in keyof C]: C[K] };\n\n/**\n * `TrUApiClient` seen through public surfaces only. The fake is checked against\n * this member-by-member, so drift from the generated client — a renamed method,\n * a changed request or response type — fails compilation here instead of\n * surfacing as a runtime mismatch in consumers' tests.\n */\ntype PublicTruApiClient = { [D in keyof TrUApiClient]: PublicSurface<TrUApiClient[D]> };\n\n/** Inlined to keep the `truapi` facade out of this entry's bundle. */\nfunction toHex(bytes: Uint8Array): `0x${string}` {\n return `0x${Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n\nfunction fakeSubscription(): Subscription {\n return { unsubscribe: () => {}, subscriptionId: \"fake-subscription\" };\n}\n\n/**\n * Wrap a `subscribe` implementation into a full `ObservableLike`. The\n * `Symbol.observable` interop member only lands on the object when the symbol\n * is polyfilled (e.g. by rxjs) — same as the generated client — and nothing in\n * the SDK reads it; it exists to satisfy the interface.\n */\nfunction makeObservable<Item, Reason = never>(\n subscribe: (observer?: Partial<Observer<Item, Reason>>) => Subscription,\n): ObservableLike<Item, Reason> {\n const observable: ObservableLike<Item, Reason> = {\n subscribe,\n [Symbol.observable]() {\n return observable;\n },\n };\n return observable;\n}\n\n/** An `ObservableLike` that never emits. Drive statement delivery with `createFakeStatementTransport`. */\nfunction inertObservable<Item, Reason = never>(): ObservableLike<Item, Reason> {\n return makeObservable(() => fakeSubscription());\n}\n\n/**\n * An `ObservableLike` that emits `item` once, asynchronously. The microtask defer\n * matters: the caller assigns its subscription handle from the return value, so\n * the item must arrive after `subscribe()` returns, not during it.\n */\nfunction oneShotObservable<Item>(item: Item): ObservableLike<Item> {\n return makeObservable((observer) => {\n queueMicrotask(() => observer?.next?.(item));\n return fakeSubscription();\n });\n}\n\n/**\n * A domain the fake deliberately doesn't model (see the module header). Any\n * member access throws with a pointer here, instead of the bare TypeError an\n * empty stub would give. The empty-object cast is the one concession a Proxy\n * needs; every modeled domain is checked structurally.\n *\n * `modeled` carries the members that _are_ implemented, for a domain where the\n * fake covers some calls but not the whole surface.\n */\nfunction notModeled<D extends keyof TrUApiClient>(\n domain: D,\n modeled?: Partial<PublicSurface<TrUApiClient[D]>>,\n): PublicSurface<TrUApiClient[D]> {\n return new Proxy((modeled ?? {}) as PublicSurface<TrUApiClient[D]>, {\n get(target, member) {\n if (member in target) return target[member as keyof typeof target];\n // Stay quiet for inspection probes (console.log, await-resolution).\n if (typeof member === \"symbol\" || member === \"then\") return undefined;\n throw new Error(\n `createFakeTruApiClient: \\`${domain}.${member}\\` is not modeled by the fake. See the @parity/product-sdk-host/testing module docs for what is covered.`,\n );\n },\n });\n}\n\n/** Deterministic `0x`-prefixed preimage key derived from a hex value (FNV-1a). */\nfunction preimageKey(hexValue: string): `0x${string}` {\n let h = 0x811c9dc5;\n for (let i = 0; i < hexValue.length; i++) {\n h ^= hexValue.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return `0x${(h >>> 0).toString(16).padStart(8, \"0\")}`;\n}\n\n/**\n * What a fake host reports through `chain.getChainInfo`: the network id it is\n * configured for, and a genesis hash per chain role it serves.\n *\n * Roles left out are answered `NotSupported`, exactly as a host that does not\n * serve them. Use the genesis hashes the descriptors expose (e.g.\n * `paseo_asset_hub.genesis`) if the test drives `getChainAPI()`, since the\n * environment is derived by matching the asset hub genesis against the bundle.\n */\nexport interface FakeChainInfo {\n /** Ecosystem the fake host claims, e.g. `\"paseo\"`. */\n network: string;\n /** Genesis hash per chain role served. */\n chains: Partial<Record<HostChainIdentifier, `0x${string}`>>;\n}\n\n/** Options for {@link createFakeTruApiClient}. */\nexport interface CreateFakeTruApiClientOptions {\n /** `account.getUserId` primary username. Default `\"alice.dot\"`. */\n primaryUsername?: string;\n /** Product-account public key. Default 32 bytes of `0x11`. */\n publicKey?: Uint8Array;\n /** Bytes returned by the signing domain (signatures / proofs / transactions). Default 64 bytes of `0x22`. */\n signature?: Uint8Array;\n /** Whether `system.featureSupported` reports the chain supported. Default `true`. */\n chainSupported?: boolean;\n /** Seed the in-memory host localStorage, keyed by storage key. */\n localStorage?: Record<string, Uint8Array>;\n /** Legacy accounts returned by `account.getLegacyAccounts`. Default none. */\n legacyAccounts?: Array<{ publicKey: Uint8Array; name: string }>;\n /** Seed the in-memory preimage store, keyed by the `0x` preimage key. */\n preimages?: Record<string, Uint8Array>;\n /**\n * What `chain.getChainInfo` reports. Omit to model a host predating chain\n * discovery: the call is refused as `Unsupported`, so `getHostChainInfo`\n * resolves `null` and `getChainAPI` needs an explicit environment.\n */\n chainInfo?: FakeChainInfo;\n}\n\n/**\n * Build a fake `TrUApiClient` covering the domains the host accessors use:\n * `localStorage` (real in-memory KV), `account` / `signing` (canned data),\n * `statementStore`, `preimage`, and `system`. Unmodeled domains (`chain` et al —\n * see the module header) throw on member access.\n */\nexport function createFakeTruApiClient(options?: CreateFakeTruApiClientOptions): TrUApiClient {\n const primaryUsername = options?.primaryUsername ?? \"alice.dot\";\n const publicKey = toHex(options?.publicKey ?? new Uint8Array(32).fill(0x11));\n const signature = toHex(options?.signature ?? new Uint8Array(64).fill(0x22));\n const chainSupported = options?.chainSupported ?? true;\n const chainInfo = options?.chainInfo;\n\n // Real in-memory KV (hex values) so getHostLocalStorage()/createLocalKvStore() round-trip.\n const kv = new Map<string, `0x${string}`>();\n for (const [key, value] of Object.entries(options?.localStorage ?? {})) {\n kv.set(key, toHex(value));\n }\n\n const legacyAccounts = (options?.legacyAccounts ?? []).map((a) => ({\n publicKey: toHex(a.publicKey),\n name: a.name,\n }));\n\n // Preimage store (hex key -> hex value), for getPreimageManager() and submit/lookup round-trips.\n const preimages = new Map<string, `0x${string}`>();\n for (const [key, value] of Object.entries(options?.preimages ?? {})) {\n preimages.set(key, toHex(value));\n }\n\n // Typed against the generated client's public surface: every method below is\n // structurally checked, so a truapi signature change breaks this file's build,\n // not a consumer's test run.\n const client: PublicTruApiClient = {\n localStorage: {\n read: ({ key }) => okAsync({ value: kv.get(key) }),\n write: ({ key, value }) => {\n kv.set(key, value);\n return okAsync(undefined);\n },\n clear: ({ key }) => {\n kv.delete(key);\n return okAsync(undefined);\n },\n },\n account: {\n getUserId: () => okAsync({ primaryUsername }),\n requestLogin: () => okAsync(\"Success\"),\n getAccount: () => okAsync({ account: { publicKey } }),\n getAccountAlias: () =>\n okAsync({ context: toHex(new Uint8Array([1])), alias: toHex(new Uint8Array([2])) }),\n getLegacyAccounts: () => okAsync({ accounts: legacyAccounts }),\n createAccountProof: () =>\n okAsync({\n proof: signature,\n contextualAlias: {\n context: toHex(new Uint8Array([1])),\n alias: toHex(new Uint8Array([2])),\n },\n ringIndex: 0,\n ringRevision: 0,\n }),\n registerRingVrfKey: () => okAsync(publicKey),\n listRingVrfKeys: () => okAsync([]),\n ringVrfSign: () => okAsync(signature),\n signVrf: () => okAsync({ preOutput: publicKey, proof: signature }),\n connectionStatusSubscribe: () => inertObservable(),\n },\n signing: {\n createTransaction: () => okAsync({ transaction: signature }),\n createTransactionWithLegacyAccount: () => okAsync({ transaction: signature }),\n signRaw: () => okAsync({ signature }),\n signRawWithLegacyAccount: () => okAsync({ signature }),\n signPayload: () => okAsync({ signature }),\n signPayloadWithLegacyAccount: () => okAsync({ signature }),\n },\n statementStore: {\n subscribe: () => inertObservable(),\n createProof: () =>\n okAsync({ proof: { tag: \"Sr25519\", value: { signature, signer: publicKey } } }),\n createProofAuthorized: () =>\n okAsync({ proof: { tag: \"Sr25519\", value: { signature, signer: publicKey } } }),\n submit: () => okAsync(undefined),\n },\n // `info` and `getProductContext` are not modeled; they still throw.\n system: notModeled(\"system\", {\n handshake: () => okAsync(undefined),\n featureSupported: () => okAsync({ supported: chainSupported }),\n navigateTo: () => okAsync(undefined),\n }),\n preimage: {\n lookupSubscribe: ({ request: { key } }) =>\n oneShotObservable({ value: preimages.get(key) }),\n submit: (value) => {\n const key = preimageKey(value);\n preimages.set(key, value);\n return okAsync(key);\n },\n },\n // Only `getChainInfo` is modeled; the rest of the domain still throws.\n chain: notModeled(\"chain\", {\n getChainInfo: ({ chain }) => {\n if (!chainInfo) return errAsync({ tag: \"Unsupported\" } as const);\n const genesisHash = chainInfo.chains[chain];\n if (!genesisHash) {\n return errAsync({\n tag: \"Domain\",\n value: { tag: \"V1\", value: { tag: \"NotSupported\" } },\n } as const);\n }\n return okAsync({ network: chainInfo.network, chain, genesisHash });\n },\n }),\n chat: notModeled(\"chat\"),\n coinPayment: notModeled(\"coinPayment\"),\n entropy: notModeled(\"entropy\"),\n locale: notModeled(\"locale\"),\n notifications: notModeled(\"notifications\"),\n payment: notModeled(\"payment\"),\n permissions: notModeled(\"permissions\"),\n resourceAllocation: notModeled(\"resourceAllocation\"),\n theme: notModeled(\"theme\"),\n };\n\n // A plain downcast, not an `unknown` bridge: each generated class is\n // assignable to its `PublicSurface`, and TS has already verified every fake\n // member against the generated signatures in the annotation above.\n return client as TrUApiClient;\n}\n\n/**\n * A `beforeEach`-registered test hook, e.g. Vitest's `onTestFinished`. Read off\n * `globalThis` so we couple to no test framework: present under Vitest's globals\n * mode, absent everywhere else (Jest, `node:test`, plain scripts).\n */\ntype TestFinishedHook = (fn: () => void) => void;\n\n/** Handle returned by {@link createFakeHost}. */\nexport interface FakeHost extends Disposable {\n /** The injected fake client (also what `getTruApi()` returns). */\n client: TrUApiClient;\n /**\n * Push a status to `subscribeConnectionStatus` subscribers, so a product's\n * reconnecting / offline UI can be exercised. `dispose()` already reports\n * `\"disconnected\"`; use this to drive the states in between.\n */\n emitConnectionStatus(status: HostConnectionStatus): void;\n /** Clear the override. Idempotent; safe to call more than once. */\n dispose(): void;\n /** `using host = createFakeHost()` restores the real client at scope end. */\n [Symbol.dispose](): void;\n}\n\n/**\n * Inject a fake client via {@link setTruApiClient} and return a disposer.\n *\n * Cleanup is guaranteed three ways, so a forgotten reset can't leak the override\n * into the next test or file: prefer `using` (scope-end disposal), otherwise the\n * handle self-registers `onTestFinished` when created inside a Vitest run, and\n * `dispose()` is always there to call by hand.\n *\n * @example\n * ```ts\n * import { createFakeHost } from \"@parity/product-sdk-host/testing\";\n *\n * test(\"host storage is reachable\", async () => {\n * using host = createFakeHost({ primaryUsername: \"carol.dot\" });\n * // getHostLocalStorage(), getAccountsProvider(), a default SignerManager, etc.\n * // resolve the fake; the real client is restored when the test scope ends.\n * });\n * ```\n */\nexport function createFakeHost(options?: CreateFakeTruApiClientOptions): FakeHost {\n const client = createFakeTruApiClient(options);\n setTruApiClient(client);\n\n let disposed = false;\n const dispose = () => {\n if (disposed) return;\n disposed = true;\n setTruApiClient(null);\n };\n\n // Best-effort auto-cleanup for the common case (a bare `const host =\n // createFakeHost()` with no `using` and no manual reset). Under Vitest's\n // globals mode `onTestFinished` is on `globalThis`; elsewhere it's absent and\n // we quietly rely on `using` / manual `dispose()`.\n const onTestFinished = (globalThis as { onTestFinished?: TestFinishedHook }).onTestFinished;\n if (typeof onTestFinished === \"function\") {\n try {\n onTestFinished(dispose);\n } catch {\n // Not inside a running test (e.g. called at module scope) — ignore.\n }\n }\n\n return {\n client,\n emitConnectionStatus,\n dispose,\n [Symbol.dispose]: dispose,\n };\n}\n\nif (import.meta.vitest) {\n // Round-trip guard: drive the fake through the *real* host accessors.\n const { describe, test, expect, afterEach } = import.meta.vitest;\n const { getHostLocalStorage, getStatementStore, isInsideContainer } = await import(\n \"./container.js\"\n );\n const { getAccountsProvider } = await import(\"./accounts.js\");\n const { getHostChainInfo } = await import(\"./chain-discovery.js\");\n const { getPreimageManager } = await import(\"./truapi.js\");\n\n const lookupOnce = (\n manager: NonNullable<Awaited<ReturnType<typeof getPreimageManager>>>,\n key: `0x${string}`,\n ) =>\n new Promise<Uint8Array | null>((resolve) => {\n manager.lookup(key, (preimage) => {\n if (preimage !== null) resolve(preimage);\n });\n });\n\n afterEach(() => setTruApiClient(null));\n\n describe(\"createFakeHost / createFakeTruApiClient\", () => {\n test(\"host localStorage round-trips through the real adapter\", async () => {\n createFakeHost();\n const ls = await getHostLocalStorage();\n expect(ls).not.toBeNull();\n await ls?.writeString(\"k\", \"v\");\n expect(await ls?.readString(\"k\")).toBe(\"v\");\n await ls?.writeJSON(\"j\", { a: 1 });\n expect(await ls?.readJSON(\"j\")).toEqual({ a: 1 });\n expect(await ls?.readString(\"missing\")).toBe(\"\");\n });\n\n test(\"seeded localStorage is readable\", async () => {\n createFakeHost({ localStorage: { greeting: new TextEncoder().encode(\"hi\") } });\n const ls = await getHostLocalStorage();\n expect(await ls?.readString(\"greeting\")).toBe(\"hi\");\n });\n\n test(\"accounts provider resolves the configured user\", async () => {\n createFakeHost({ primaryUsername: \"carol.dot\" });\n const accounts = await getAccountsProvider();\n expect(accounts).not.toBeNull();\n const userId = await accounts?.getUserId().match(\n (v) => v,\n () => null,\n );\n expect(userId?.primaryUsername).toBe(\"carol.dot\");\n });\n\n test(\"signVrf round-trips through the real adapter\", async () => {\n // The fake is the only way a product can test a VRF flow: there is no\n // dev-provider implementation and the e2e test host does not expose the\n // call. So the fake's wire shape has to stay decodable by the adapter.\n createFakeHost();\n const accounts = await getAccountsProvider();\n const signature = await accounts\n ?.signVrf({ dotNsIdentifier: \"app.dot\" }, new Uint8Array([1]), [\n { label: new Uint8Array([2]), value: new Uint8Array([3]) },\n ])\n .match(\n (s) => s,\n () => null,\n );\n expect(signature?.preOutput).toBeInstanceOf(Uint8Array);\n expect(signature?.proof).toBeInstanceOf(Uint8Array);\n expect(signature?.preOutput).toHaveLength(32);\n expect(signature?.proof).toHaveLength(64);\n });\n\n test(\"chain discovery is refused by default, so a legacy host is modeled\", async () => {\n createFakeHost();\n // Refused, not unmodeled: a bare `getChainAPI(\"paseo\")` in a consumer\n // test must not warn about the fake on every call.\n expect(await getHostChainInfo([\"AssetHub\"])).toBeNull();\n });\n\n test(\"chainInfo drives discovery, with unserved roles left out\", async () => {\n createFakeHost({\n chainInfo: { network: \"paseo\", chains: { AssetHub: \"0xaa\", Bulletin: \"0xbb\" } },\n });\n expect(await getHostChainInfo([\"AssetHub\", \"Bulletin\", \"People\"])).toEqual({\n network: \"paseo\",\n chains: { AssetHub: \"0xaa\", Bulletin: \"0xbb\" },\n });\n });\n\n test(\"the rest of the chain domain still reports itself unmodeled\", async () => {\n const host = createFakeHost();\n expect(() => (host.client.chain as { chainName?: unknown }).chainName).toThrow(\n /not modeled by the fake/,\n );\n });\n\n test(\"statement store resolves\", async () => {\n createFakeHost();\n expect(await getStatementStore()).not.toBeNull();\n });\n\n test(\"preimage manager reads seeded bytes and round-trips submit -> lookup\", async () => {\n createFakeHost({ preimages: { \"0xabc\": new Uint8Array([1, 2, 3]) } });\n const pm = await getPreimageManager();\n expect(pm).not.toBeNull();\n\n const seeded = await lookupOnce(pm!, \"0xabc\");\n expect(Array.from(seeded ?? [])).toEqual([1, 2, 3]);\n\n const key = await pm!.submit(new Uint8Array([9, 9]));\n const back = await lookupOnce(pm!, key);\n expect(Array.from(back ?? [])).toEqual([9, 9]);\n });\n\n test(\"isInsideContainer is true while set, false after dispose\", async () => {\n const host = createFakeHost();\n expect(await isInsideContainer()).toBe(true);\n host.dispose();\n expect(await isInsideContainer()).toBe(false);\n expect(await getHostLocalStorage()).toBeNull();\n });\n\n test(\"Symbol.dispose clears the override and dispose is idempotent\", async () => {\n {\n using host = createFakeHost();\n expect(await isInsideContainer()).toBe(true);\n host.dispose(); // explicit + scope-end Symbol.dispose must not double-clear badly\n }\n expect(await isInsideContainer()).toBe(false);\n });\n });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/testing.ts"],"names":[],"mappings":"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","file":"testing.js","sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Test fakes for `@parity/product-sdk-host`.\n *\n * The host reaches its container through one cached `TrUApiClient`.\n * `createFakeHost()` injects a fake via `setTruApiClient` so every accessor\n * (`getHostLocalStorage`, `getAccountsProvider`, `getStatementStore`, `getTruApi`,\n * …) resolves against it and `isInsideContainer()` reports `true` — which also\n * makes a default `SignerManager`, `local-storage` auto-detection, and the\n * `statement-store` / `cloud-storage` host paths testable.\n *\n * Of the `chain` domain only `getChainInfo` is modeled, so host chain discovery\n * (and `getChainAPI()` on top of it) resolves in tests; see the `chainInfo`\n * option. Not modeled: the rest of the PAPI `chain` JSON-RPC surface behind\n * `getHostProvider()` — there's no chain-read fake, by design; the host owns RPC\n * selection — the `system` domain's `info` / `getProductContext`, and the\n * `chat` / `coinPayment` / `entropy` / `locale` / `notifications` / `payment` /\n * `permissions` / `resourceAllocation` / `theme` domains. Touching\n * an unmodeled domain throws a descriptive error rather than failing with\n * `undefined is not a function`.\n *\n * @packageDocumentation\n */\nimport type { ObservableLike, Observer, Subscription, TrUApiClient } from \"@parity/truapi\";\nimport { errAsync, okAsync } from \"neverthrow\";\n\nimport type { HostChainIdentifier } from \"./chain-discovery.js\";\n\nimport { type HostConnectionStatus, emitConnectionStatus, setTruApiClient } from \"./transport.js\";\n\nexport { setTruApiClient, emitConnectionStatus };\n\n/**\n * The public surface of a generated truapi domain client. `keyof` skips private\n * members, so this is the shape a plain object can implement — the classes\n * themselves are unimplementable outside truapi because of their\n * `private transport`.\n */\ntype PublicSurface<C> = { [K in keyof C]: C[K] };\n\n/**\n * `TrUApiClient` seen through public surfaces only. The fake is checked against\n * this member-by-member, so drift from the generated client — a renamed method,\n * a changed request or response type — fails compilation here instead of\n * surfacing as a runtime mismatch in consumers' tests.\n */\ntype PublicTruApiClient = { [D in keyof TrUApiClient]: PublicSurface<TrUApiClient[D]> };\n\n/** Inlined to keep the `truapi` facade out of this entry's bundle. */\nfunction toHex(bytes: Uint8Array): `0x${string}` {\n return `0x${Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n\nfunction fakeSubscription(): Subscription {\n return { unsubscribe: () => {}, subscriptionId: \"fake-subscription\" };\n}\n\n/**\n * Wrap a `subscribe` implementation into a full `ObservableLike`. The\n * `Symbol.observable` interop member only lands on the object when the symbol\n * is polyfilled (e.g. by rxjs) — same as the generated client — and nothing in\n * the SDK reads it; it exists to satisfy the interface.\n */\nfunction makeObservable<Item, Reason = never>(\n subscribe: (observer?: Partial<Observer<Item, Reason>>) => Subscription,\n): ObservableLike<Item, Reason> {\n const observable: ObservableLike<Item, Reason> = {\n subscribe,\n [Symbol.observable]() {\n return observable;\n },\n };\n return observable;\n}\n\n/** An `ObservableLike` that never emits. Drive statement delivery with `createFakeStatementTransport`. */\nfunction inertObservable<Item, Reason = never>(): ObservableLike<Item, Reason> {\n return makeObservable(() => fakeSubscription());\n}\n\n/**\n * An `ObservableLike` that emits `item` once, asynchronously. The microtask defer\n * matters: the caller assigns its subscription handle from the return value, so\n * the item must arrive after `subscribe()` returns, not during it.\n */\nfunction oneShotObservable<Item>(item: Item): ObservableLike<Item> {\n return makeObservable((observer) => {\n queueMicrotask(() => observer?.next?.(item));\n return fakeSubscription();\n });\n}\n\n/**\n * A domain the fake deliberately doesn't model (see the module header). Any\n * member access throws with a pointer here, instead of the bare TypeError an\n * empty stub would give. The empty-object cast is the one concession a Proxy\n * needs; every modeled domain is checked structurally.\n *\n * `modeled` carries the members that _are_ implemented, for a domain where the\n * fake covers some calls but not the whole surface.\n */\nfunction notModeled<D extends keyof TrUApiClient>(\n domain: D,\n modeled?: Partial<PublicSurface<TrUApiClient[D]>>,\n): PublicSurface<TrUApiClient[D]> {\n return new Proxy((modeled ?? {}) as PublicSurface<TrUApiClient[D]>, {\n get(target, member) {\n if (member in target) return target[member as keyof typeof target];\n // Stay quiet for inspection probes (console.log, await-resolution).\n if (typeof member === \"symbol\" || member === \"then\") return undefined;\n throw new Error(\n `createFakeTruApiClient: \\`${domain}.${member}\\` is not modeled by the fake. See the @parity/product-sdk-host/testing module docs for what is covered.`,\n );\n },\n });\n}\n\n/** Deterministic `0x`-prefixed preimage key derived from a hex value (FNV-1a). */\nfunction preimageKey(hexValue: string): `0x${string}` {\n let h = 0x811c9dc5;\n for (let i = 0; i < hexValue.length; i++) {\n h ^= hexValue.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return `0x${(h >>> 0).toString(16).padStart(8, \"0\")}`;\n}\n\n/**\n * What a fake host reports through `chain.getChainInfo`: the network id it is\n * configured for, and a genesis hash per chain role it serves.\n *\n * Roles left out are answered `NotSupported`, exactly as a host that does not\n * serve them. Use the genesis hashes the descriptors expose (e.g.\n * `paseo_asset_hub.genesis`) if the test drives `getChainAPI()`, since the\n * environment is derived by matching the asset hub genesis against the bundle.\n */\nexport interface FakeChainInfo {\n /** Ecosystem the fake host claims, e.g. `\"paseo\"`. */\n network: string;\n /** Genesis hash per chain role served. */\n chains: Partial<Record<HostChainIdentifier, `0x${string}`>>;\n}\n\n/** Options for {@link createFakeTruApiClient}. */\nexport interface CreateFakeTruApiClientOptions {\n /** `account.getUserId` primary username. Default `\"alice.dot\"`. */\n primaryUsername?: string;\n /** Product-account public key. Default 32 bytes of `0x11`. */\n publicKey?: Uint8Array;\n /** Bytes returned by the signing domain (signatures / proofs / transactions). Default 64 bytes of `0x22`. */\n signature?: Uint8Array;\n /** Whether `system.featureSupported` reports the chain supported. Default `true`. */\n chainSupported?: boolean;\n /** Seed the in-memory host localStorage, keyed by storage key. */\n localStorage?: Record<string, Uint8Array>;\n /** Legacy accounts returned by `account.getLegacyAccounts`. Default none. */\n legacyAccounts?: Array<{ publicKey: Uint8Array; name: string }>;\n /** Seed the in-memory preimage store, keyed by the `0x` preimage key. */\n preimages?: Record<string, Uint8Array>;\n /**\n * What `chain.getChainInfo` reports. Omit to model a host predating chain\n * discovery: the call is refused as `Unsupported`, so `getHostChainInfo`\n * resolves `null` and `getChainAPI` needs an explicit environment.\n */\n chainInfo?: FakeChainInfo;\n}\n\n/**\n * Build a fake `TrUApiClient` covering the domains the host accessors use:\n * `localStorage` (real in-memory KV), `account` / `signing` (canned data),\n * `statementStore`, `preimage`, and `system`. Unmodeled domains (`chain` et al —\n * see the module header) throw on member access.\n */\nexport function createFakeTruApiClient(options?: CreateFakeTruApiClientOptions): TrUApiClient {\n const primaryUsername = options?.primaryUsername ?? \"alice.dot\";\n const publicKey = toHex(options?.publicKey ?? new Uint8Array(32).fill(0x11));\n const signature = toHex(options?.signature ?? new Uint8Array(64).fill(0x22));\n const chainSupported = options?.chainSupported ?? true;\n const chainInfo = options?.chainInfo;\n\n // Real in-memory KV (hex values) so getHostLocalStorage()/createLocalKvStore() round-trip.\n const kv = new Map<string, `0x${string}`>();\n for (const [key, value] of Object.entries(options?.localStorage ?? {})) {\n kv.set(key, toHex(value));\n }\n\n const legacyAccounts = (options?.legacyAccounts ?? []).map((a) => ({\n publicKey: toHex(a.publicKey),\n name: a.name,\n }));\n\n // Preimage store (hex key -> hex value), for getPreimageManager() and submit/lookup round-trips.\n const preimages = new Map<string, `0x${string}`>();\n for (const [key, value] of Object.entries(options?.preimages ?? {})) {\n preimages.set(key, toHex(value));\n }\n\n // Typed against the generated client's public surface: every method below is\n // structurally checked, so a truapi signature change breaks this file's build,\n // not a consumer's test run.\n const client: PublicTruApiClient = {\n localStorage: {\n read: ({ key }) => okAsync({ value: kv.get(key) }),\n write: ({ key, value }) => {\n kv.set(key, value);\n return okAsync(undefined);\n },\n clear: ({ key }) => {\n kv.delete(key);\n return okAsync(undefined);\n },\n },\n account: {\n getUserId: () => okAsync({ primaryUsername }),\n requestLogin: () => okAsync(\"Success\"),\n getAccount: () => okAsync({ account: { publicKey } }),\n getAccountAlias: () =>\n okAsync({ context: toHex(new Uint8Array([1])), alias: toHex(new Uint8Array([2])) }),\n getLegacyAccounts: () => okAsync({ accounts: legacyAccounts }),\n createAccountProof: () =>\n okAsync({\n proof: signature,\n contextualAlias: {\n context: toHex(new Uint8Array([1])),\n alias: toHex(new Uint8Array([2])),\n },\n ringIndex: 0,\n ringRevision: 0,\n }),\n registerRingVrfKey: () => okAsync(publicKey),\n listRingVrfKeys: () => okAsync([]),\n ringVrfSign: () => okAsync(signature),\n signVrf: () => okAsync({ preOutput: publicKey, proof: signature }),\n connectionStatusSubscribe: () => inertObservable(),\n },\n signing: {\n createTransaction: () => okAsync({ transaction: signature }),\n createTransactionWithLegacyAccount: () => okAsync({ transaction: signature }),\n signRaw: () => okAsync({ signature }),\n signRawWithLegacyAccount: () => okAsync({ signature }),\n signRawUnwatermarkedDeprecated: () => okAsync({ signature }),\n signRawUnwatermarkedDeprecatedWithLegacyAccount: () => okAsync({ signature }),\n signPayload: () => okAsync({ signature }),\n signPayloadWithLegacyAccount: () => okAsync({ signature }),\n },\n statementStore: {\n subscribe: () => inertObservable(),\n createProof: () =>\n okAsync({ proof: { tag: \"Sr25519\", value: { signature, signer: publicKey } } }),\n createProofAuthorized: () =>\n okAsync({ proof: { tag: \"Sr25519\", value: { signature, signer: publicKey } } }),\n submit: () => okAsync(undefined),\n },\n // `info` and `getProductContext` are not modeled; they still throw.\n system: notModeled(\"system\", {\n handshake: () => okAsync(undefined),\n featureSupported: () => okAsync({ supported: chainSupported }),\n navigateTo: () => okAsync(undefined),\n }),\n preimage: {\n lookupSubscribe: ({ request: { key } }) =>\n oneShotObservable({ value: preimages.get(key) }),\n submit: (value) => {\n const key = preimageKey(value);\n preimages.set(key, value);\n return okAsync(key);\n },\n },\n // Only `getChainInfo` is modeled; the rest of the domain still throws.\n chain: notModeled(\"chain\", {\n getChainInfo: ({ chain }) => {\n if (!chainInfo) return errAsync({ tag: \"Unsupported\" } as const);\n const genesisHash = chainInfo.chains[chain];\n if (!genesisHash) {\n return errAsync({\n tag: \"Domain\",\n value: { tag: \"V1\", value: { tag: \"NotSupported\" } },\n } as const);\n }\n return okAsync({ network: chainInfo.network, chain, genesisHash });\n },\n }),\n chat: notModeled(\"chat\"),\n coinPayment: notModeled(\"coinPayment\"),\n entropy: notModeled(\"entropy\"),\n locale: notModeled(\"locale\"),\n notifications: notModeled(\"notifications\"),\n payment: notModeled(\"payment\"),\n permissions: notModeled(\"permissions\"),\n pocket: notModeled(\"pocket\"),\n renderer: notModeled(\"renderer\"),\n resourceAllocation: notModeled(\"resourceAllocation\"),\n theme: notModeled(\"theme\"),\n };\n\n // A plain downcast, not an `unknown` bridge: each generated class is\n // assignable to its `PublicSurface`, and TS has already verified every fake\n // member against the generated signatures in the annotation above.\n return client as TrUApiClient;\n}\n\n/**\n * A `beforeEach`-registered test hook, e.g. Vitest's `onTestFinished`. Read off\n * `globalThis` so we couple to no test framework: present under Vitest's globals\n * mode, absent everywhere else (Jest, `node:test`, plain scripts).\n */\ntype TestFinishedHook = (fn: () => void) => void;\n\n/** Handle returned by {@link createFakeHost}. */\nexport interface FakeHost extends Disposable {\n /** The injected fake client (also what `getTruApi()` returns). */\n client: TrUApiClient;\n /**\n * Push a status to `subscribeConnectionStatus` subscribers, so a product's\n * reconnecting / offline UI can be exercised. `dispose()` already reports\n * `\"disconnected\"`; use this to drive the states in between.\n */\n emitConnectionStatus(status: HostConnectionStatus): void;\n /** Clear the override. Idempotent; safe to call more than once. */\n dispose(): void;\n /** `using host = createFakeHost()` restores the real client at scope end. */\n [Symbol.dispose](): void;\n}\n\n/**\n * Inject a fake client via {@link setTruApiClient} and return a disposer.\n *\n * Cleanup is guaranteed three ways, so a forgotten reset can't leak the override\n * into the next test or file: prefer `using` (scope-end disposal), otherwise the\n * handle self-registers `onTestFinished` when created inside a Vitest run, and\n * `dispose()` is always there to call by hand.\n *\n * @example\n * ```ts\n * import { createFakeHost } from \"@parity/product-sdk-host/testing\";\n *\n * test(\"host storage is reachable\", async () => {\n * using host = createFakeHost({ primaryUsername: \"carol.dot\" });\n * // getHostLocalStorage(), getAccountsProvider(), a default SignerManager, etc.\n * // resolve the fake; the real client is restored when the test scope ends.\n * });\n * ```\n */\nexport function createFakeHost(options?: CreateFakeTruApiClientOptions): FakeHost {\n const client = createFakeTruApiClient(options);\n setTruApiClient(client);\n\n let disposed = false;\n const dispose = () => {\n if (disposed) return;\n disposed = true;\n setTruApiClient(null);\n };\n\n // Best-effort auto-cleanup for the common case (a bare `const host =\n // createFakeHost()` with no `using` and no manual reset). Under Vitest's\n // globals mode `onTestFinished` is on `globalThis`; elsewhere it's absent and\n // we quietly rely on `using` / manual `dispose()`.\n const onTestFinished = (globalThis as { onTestFinished?: TestFinishedHook }).onTestFinished;\n if (typeof onTestFinished === \"function\") {\n try {\n onTestFinished(dispose);\n } catch {\n // Not inside a running test (e.g. called at module scope) — ignore.\n }\n }\n\n return {\n client,\n emitConnectionStatus,\n dispose,\n [Symbol.dispose]: dispose,\n };\n}\n\nif (import.meta.vitest) {\n // Round-trip guard: drive the fake through the *real* host accessors.\n const { describe, test, expect, afterEach } = import.meta.vitest;\n const { getHostLocalStorage, getStatementStore, isInsideContainer } = await import(\n \"./container.js\"\n );\n const { getAccountsProvider } = await import(\"./accounts.js\");\n const { getHostChainInfo } = await import(\"./chain-discovery.js\");\n const { getPreimageManager } = await import(\"./truapi.js\");\n\n const lookupOnce = (\n manager: NonNullable<Awaited<ReturnType<typeof getPreimageManager>>>,\n key: `0x${string}`,\n ) =>\n new Promise<Uint8Array | null>((resolve) => {\n manager.lookup(key, (preimage) => {\n if (preimage !== null) resolve(preimage);\n });\n });\n\n afterEach(() => setTruApiClient(null));\n\n describe(\"createFakeHost / createFakeTruApiClient\", () => {\n test(\"host localStorage round-trips through the real adapter\", async () => {\n createFakeHost();\n const ls = await getHostLocalStorage();\n expect(ls).not.toBeNull();\n await ls?.writeString(\"k\", \"v\");\n expect(await ls?.readString(\"k\")).toBe(\"v\");\n await ls?.writeJSON(\"j\", { a: 1 });\n expect(await ls?.readJSON(\"j\")).toEqual({ a: 1 });\n expect(await ls?.readString(\"missing\")).toBe(\"\");\n });\n\n test(\"seeded localStorage is readable\", async () => {\n createFakeHost({ localStorage: { greeting: new TextEncoder().encode(\"hi\") } });\n const ls = await getHostLocalStorage();\n expect(await ls?.readString(\"greeting\")).toBe(\"hi\");\n });\n\n test(\"accounts provider resolves the configured user\", async () => {\n createFakeHost({ primaryUsername: \"carol.dot\" });\n const accounts = await getAccountsProvider();\n expect(accounts).not.toBeNull();\n const userId = await accounts?.getUserId().match(\n (v) => v,\n () => null,\n );\n expect(userId?.primaryUsername).toBe(\"carol.dot\");\n });\n\n test(\"signVrf round-trips through the real adapter\", async () => {\n // The fake is the only way a product can test a VRF flow: there is no\n // dev-provider implementation and the e2e test host does not expose the\n // call. So the fake's wire shape has to stay decodable by the adapter.\n createFakeHost();\n const accounts = await getAccountsProvider();\n const signature = await accounts\n ?.signVrf({ dotNsIdentifier: \"app.dot\" }, new Uint8Array([1]), [\n { label: new Uint8Array([2]), value: new Uint8Array([3]) },\n ])\n .match(\n (s) => s,\n () => null,\n );\n expect(signature?.preOutput).toBeInstanceOf(Uint8Array);\n expect(signature?.proof).toBeInstanceOf(Uint8Array);\n expect(signature?.preOutput).toHaveLength(32);\n expect(signature?.proof).toHaveLength(64);\n });\n\n test(\"chain discovery is refused by default, so a legacy host is modeled\", async () => {\n createFakeHost();\n // Refused, not unmodeled: a bare `getChainAPI(\"paseo\")` in a consumer\n // test must not warn about the fake on every call.\n expect(await getHostChainInfo([\"AssetHub\"])).toBeNull();\n });\n\n test(\"chainInfo drives discovery, with unserved roles left out\", async () => {\n createFakeHost({\n chainInfo: { network: \"paseo\", chains: { AssetHub: \"0xaa\", Bulletin: \"0xbb\" } },\n });\n expect(await getHostChainInfo([\"AssetHub\", \"Bulletin\", \"People\"])).toEqual({\n network: \"paseo\",\n chains: { AssetHub: \"0xaa\", Bulletin: \"0xbb\" },\n });\n });\n\n test(\"the rest of the chain domain still reports itself unmodeled\", async () => {\n const host = createFakeHost();\n expect(() => (host.client.chain as { chainName?: unknown }).chainName).toThrow(\n /not modeled by the fake/,\n );\n });\n\n test(\"statement store resolves\", async () => {\n createFakeHost();\n expect(await getStatementStore()).not.toBeNull();\n });\n\n test(\"preimage manager reads seeded bytes and round-trips submit -> lookup\", async () => {\n createFakeHost({ preimages: { \"0xabc\": new Uint8Array([1, 2, 3]) } });\n const pm = await getPreimageManager();\n expect(pm).not.toBeNull();\n\n const seeded = await lookupOnce(pm!, \"0xabc\");\n expect(Array.from(seeded ?? [])).toEqual([1, 2, 3]);\n\n const key = await pm!.submit(new Uint8Array([9, 9]));\n const back = await lookupOnce(pm!, key);\n expect(Array.from(back ?? [])).toEqual([9, 9]);\n });\n\n test(\"isInsideContainer is true while set, false after dispose\", async () => {\n const host = createFakeHost();\n expect(await isInsideContainer()).toBe(true);\n host.dispose();\n expect(await isInsideContainer()).toBe(false);\n expect(await getHostLocalStorage()).toBeNull();\n });\n\n test(\"Symbol.dispose clears the override and dispose is idempotent\", async () => {\n {\n using host = createFakeHost();\n expect(await isInsideContainer()).toBe(true);\n host.dispose(); // explicit + scope-end Symbol.dispose must not double-clear badly\n }\n expect(await isInsideContainer()).toBe(false);\n });\n });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parity/product-sdk-host",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Host container detection and storage access for Polkadot Desktop and Mobile environments",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
"src"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@parity/truapi": "^0.
|
|
25
|
+
"@parity/truapi": "^0.17.0",
|
|
26
26
|
"@polkadot-api/json-rpc-provider": "^0.2.0",
|
|
27
27
|
"@polkadot-api/substrate-bindings": "^0.20.3",
|
|
28
28
|
"neverthrow": "^8.2.0",
|
|
29
29
|
"polkadot-api": "^2.1.6",
|
|
30
|
-
"@parity/product-sdk-
|
|
30
|
+
"@parity/product-sdk-errors": "0.2.0",
|
|
31
31
|
"@parity/result": "0.2.0",
|
|
32
|
-
"@parity/product-sdk-
|
|
32
|
+
"@parity/product-sdk-logger": "0.1.1"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"tsup": "^8.5.1",
|
package/src/accounts.ts
CHANGED
|
@@ -19,6 +19,14 @@
|
|
|
19
19
|
* bridge is involved, so opaque signed extensions (e.g. Paseo Next's `AsPgas`)
|
|
20
20
|
* survive end-to-end.
|
|
21
21
|
*
|
|
22
|
+
* Every one of those raw-signing paths `<Bytes>`-wraps the payload before the
|
|
23
|
+
* key touches it. A runtime that verifies a bare-byte ownership proof — today
|
|
24
|
+
* People chain's `Resources.register_person` — therefore rejects them, so the
|
|
25
|
+
* provider also exposes the deprecated `signRawUnwatermarkedDeprecated` pair,
|
|
26
|
+
* which signs the bytes exactly as given. Reach for those two only when a
|
|
27
|
+
* runtime check leaves no alternative; they are temporary on the host side too
|
|
28
|
+
* (paritytech/host-rust-core#612).
|
|
29
|
+
*
|
|
22
30
|
* @module
|
|
23
31
|
*/
|
|
24
32
|
|
|
@@ -35,6 +43,7 @@ import type {
|
|
|
35
43
|
ProductAccount as WireProductAccount,
|
|
36
44
|
ProductAccountId,
|
|
37
45
|
ProductProofContext,
|
|
46
|
+
RawPayload,
|
|
38
47
|
RegisteredRingVrfKey as WireRegisteredRingVrfKey,
|
|
39
48
|
RingLocation,
|
|
40
49
|
RingVrfKeyDisclosure,
|
|
@@ -366,6 +375,18 @@ export interface AccountsProvider {
|
|
|
366
375
|
* derived from `publicKey` alone.
|
|
367
376
|
*/
|
|
368
377
|
getLegacyAccountSigner(account: { publicKey: Uint8Array; name?: string }): PolkadotSigner;
|
|
378
|
+
/**
|
|
379
|
+
* @deprecated Temporary — signs `data` with NO `<Bytes>` watermark so a
|
|
380
|
+
* People-chain runtime that verifies a raw-byte proof accepts it
|
|
381
|
+
* (paritytech/host-rust-core#612). Removed once the runtime accepts
|
|
382
|
+
* watermarked proofs. Hosts show a stronger warning for this call.
|
|
383
|
+
*/
|
|
384
|
+
signRawUnwatermarkedDeprecated(account: ProductAccount, data: Uint8Array): Promise<Uint8Array>;
|
|
385
|
+
/** @deprecated Same as {@link signRawUnwatermarkedDeprecated} for a legacy (wallet) account. */
|
|
386
|
+
signRawUnwatermarkedDeprecatedWithLegacyAccount(
|
|
387
|
+
account: { publicKey: Uint8Array },
|
|
388
|
+
data: Uint8Array,
|
|
389
|
+
): Promise<Uint8Array>;
|
|
369
390
|
subscribeAccountConnectionStatus(
|
|
370
391
|
callback: (status: HostAccountConnectionStatusSubscribeItem) => void,
|
|
371
392
|
): HostSubscription;
|
|
@@ -432,6 +453,17 @@ function toWireProductAccountId({
|
|
|
432
453
|
return { dotNsIdentifier, derivationIndex: { tag: "Index", value: derivationIndex } };
|
|
433
454
|
}
|
|
434
455
|
|
|
456
|
+
// The SS58 address the host's raw-signing calls match a legacy (wallet) account
|
|
457
|
+
// by, unlike `createTransactionWithLegacyAccount`, which takes the raw hex
|
|
458
|
+
// account id. One codec for the whole module: `AccountId()` is a pure factory,
|
|
459
|
+
// so rebuilding it per signer bought nothing.
|
|
460
|
+
const accountIdCodec = AccountId();
|
|
461
|
+
|
|
462
|
+
/** Wrap raw bytes in the wire's `Bytes` raw-payload variant, shared by all four raw-signing calls. */
|
|
463
|
+
function toBytesPayload(data: Uint8Array): RawPayload {
|
|
464
|
+
return { tag: "Bytes", value: { bytes: toHex(data) } };
|
|
465
|
+
}
|
|
466
|
+
|
|
435
467
|
/**
|
|
436
468
|
* Route a thrown/rejected response-decode error onto the `Result` err channel.
|
|
437
469
|
*
|
|
@@ -611,7 +643,7 @@ function adaptAccountsProvider(client: TrUApiClient): AccountsProvider {
|
|
|
611
643
|
const response = await unwrapHostResult(
|
|
612
644
|
signing.signRaw({
|
|
613
645
|
account: productAccountId,
|
|
614
|
-
payload:
|
|
646
|
+
payload: toBytesPayload(data),
|
|
615
647
|
}),
|
|
616
648
|
"signRaw failed",
|
|
617
649
|
);
|
|
@@ -624,7 +656,7 @@ function adaptAccountsProvider(client: TrUApiClient): AccountsProvider {
|
|
|
624
656
|
// raw account id (hex public key); `signRawWithLegacyAccount` takes an
|
|
625
657
|
// SS58 address the wallet can match. Compute both up front.
|
|
626
658
|
const signerHex = toHex(account_.publicKey);
|
|
627
|
-
const ss58Address =
|
|
659
|
+
const ss58Address = accountIdCodec.dec(account_.publicKey);
|
|
628
660
|
|
|
629
661
|
return {
|
|
630
662
|
publicKey: account_.publicKey,
|
|
@@ -650,7 +682,7 @@ function adaptAccountsProvider(client: TrUApiClient): AccountsProvider {
|
|
|
650
682
|
const response = await unwrapHostResult(
|
|
651
683
|
signing.signRawWithLegacyAccount({
|
|
652
684
|
signer: ss58Address,
|
|
653
|
-
payload:
|
|
685
|
+
payload: toBytesPayload(data),
|
|
654
686
|
}),
|
|
655
687
|
"signRawWithLegacyAccount failed",
|
|
656
688
|
);
|
|
@@ -658,6 +690,26 @@ function adaptAccountsProvider(client: TrUApiClient): AccountsProvider {
|
|
|
658
690
|
},
|
|
659
691
|
};
|
|
660
692
|
},
|
|
693
|
+
async signRawUnwatermarkedDeprecated(account_, data) {
|
|
694
|
+
const response = await unwrapHostResult(
|
|
695
|
+
signing.signRawUnwatermarkedDeprecated({
|
|
696
|
+
account: toWireProductAccountId(account_),
|
|
697
|
+
payload: toBytesPayload(data),
|
|
698
|
+
}),
|
|
699
|
+
"signRawUnwatermarkedDeprecated failed",
|
|
700
|
+
);
|
|
701
|
+
return fromHex(response.signature);
|
|
702
|
+
},
|
|
703
|
+
async signRawUnwatermarkedDeprecatedWithLegacyAccount(account_, data) {
|
|
704
|
+
const response = await unwrapHostResult(
|
|
705
|
+
signing.signRawUnwatermarkedDeprecatedWithLegacyAccount({
|
|
706
|
+
signer: accountIdCodec.dec(account_.publicKey),
|
|
707
|
+
payload: toBytesPayload(data),
|
|
708
|
+
}),
|
|
709
|
+
"signRawUnwatermarkedDeprecatedWithLegacyAccount failed",
|
|
710
|
+
);
|
|
711
|
+
return fromHex(response.signature);
|
|
712
|
+
},
|
|
661
713
|
subscribeAccountConnectionStatus(callback) {
|
|
662
714
|
return subscribeWithInterrupt(account.connectionStatusSubscribe(), callback);
|
|
663
715
|
},
|
|
@@ -787,6 +839,13 @@ if (import.meta.vitest) {
|
|
|
787
839
|
signRawWithLegacyAccount: method("signRawWithLegacyAccount", {
|
|
788
840
|
signature: "0xcafe",
|
|
789
841
|
}),
|
|
842
|
+
signRawUnwatermarkedDeprecated: method("signRawUnwatermarkedDeprecated", {
|
|
843
|
+
signature: "0xd00d",
|
|
844
|
+
}),
|
|
845
|
+
signRawUnwatermarkedDeprecatedWithLegacyAccount: method(
|
|
846
|
+
"signRawUnwatermarkedDeprecatedWithLegacyAccount",
|
|
847
|
+
{ signature: "0xf00d" },
|
|
848
|
+
),
|
|
790
849
|
},
|
|
791
850
|
} as unknown as TrUApiClient;
|
|
792
851
|
}
|
|
@@ -1076,6 +1135,52 @@ if (import.meta.vitest) {
|
|
|
1076
1135
|
expect(signature).toEqual(fromHex("0xcafe"));
|
|
1077
1136
|
});
|
|
1078
1137
|
|
|
1138
|
+
test("signRawUnwatermarkedDeprecated sends the product account and the raw Bytes payload", async () => {
|
|
1139
|
+
const calls: Array<[string, unknown]> = [];
|
|
1140
|
+
const client = makeFakeClient({ onCall: (m, a) => calls.push([m, a]) });
|
|
1141
|
+
const provider = adaptAccountsProvider(client);
|
|
1142
|
+
const signature = await provider.signRawUnwatermarkedDeprecated(
|
|
1143
|
+
{
|
|
1144
|
+
dotNsIdentifier: "app.dot",
|
|
1145
|
+
derivationIndex: 3,
|
|
1146
|
+
publicKey: new Uint8Array(32).fill(0xaa),
|
|
1147
|
+
},
|
|
1148
|
+
new Uint8Array([9, 9]),
|
|
1149
|
+
);
|
|
1150
|
+
// Same wire request as `signRaw`: only the method the host resolves differs,
|
|
1151
|
+
// so a mix-up here is what silently re-introduces the `<Bytes>` wrap.
|
|
1152
|
+
expect(calls.at(-1)).toEqual([
|
|
1153
|
+
"signRawUnwatermarkedDeprecated",
|
|
1154
|
+
{
|
|
1155
|
+
account: {
|
|
1156
|
+
dotNsIdentifier: "app.dot",
|
|
1157
|
+
derivationIndex: { tag: "Index", value: 3 },
|
|
1158
|
+
},
|
|
1159
|
+
payload: { tag: "Bytes", value: { bytes: toHex(new Uint8Array([9, 9])) } },
|
|
1160
|
+
},
|
|
1161
|
+
]);
|
|
1162
|
+
expect(signature).toEqual(fromHex("0xd00d"));
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
test("signRawUnwatermarkedDeprecatedWithLegacyAccount addresses the signer by SS58 address", async () => {
|
|
1166
|
+
const calls: Array<[string, unknown]> = [];
|
|
1167
|
+
const client = makeFakeClient({ onCall: (m, a) => calls.push([m, a]) });
|
|
1168
|
+
const provider = adaptAccountsProvider(client);
|
|
1169
|
+
const publicKey = new Uint8Array(32).fill(0xbb);
|
|
1170
|
+
const signature = await provider.signRawUnwatermarkedDeprecatedWithLegacyAccount(
|
|
1171
|
+
{ publicKey },
|
|
1172
|
+
new Uint8Array([7, 7]),
|
|
1173
|
+
);
|
|
1174
|
+
expect(calls.at(-1)).toEqual([
|
|
1175
|
+
"signRawUnwatermarkedDeprecatedWithLegacyAccount",
|
|
1176
|
+
{
|
|
1177
|
+
signer: AccountId().dec(publicKey),
|
|
1178
|
+
payload: { tag: "Bytes", value: { bytes: toHex(new Uint8Array([7, 7])) } },
|
|
1179
|
+
},
|
|
1180
|
+
]);
|
|
1181
|
+
expect(signature).toEqual(fromHex("0xf00d"));
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1079
1184
|
test("the legacy signer's signTx throws without a CheckGenesis extension", async () => {
|
|
1080
1185
|
const provider = adaptAccountsProvider(makeFakeClient());
|
|
1081
1186
|
const signer = provider.getLegacyAccountSigner({
|
package/src/testing.ts
CHANGED
|
@@ -239,6 +239,8 @@ export function createFakeTruApiClient(options?: CreateFakeTruApiClientOptions):
|
|
|
239
239
|
createTransactionWithLegacyAccount: () => okAsync({ transaction: signature }),
|
|
240
240
|
signRaw: () => okAsync({ signature }),
|
|
241
241
|
signRawWithLegacyAccount: () => okAsync({ signature }),
|
|
242
|
+
signRawUnwatermarkedDeprecated: () => okAsync({ signature }),
|
|
243
|
+
signRawUnwatermarkedDeprecatedWithLegacyAccount: () => okAsync({ signature }),
|
|
242
244
|
signPayload: () => okAsync({ signature }),
|
|
243
245
|
signPayloadWithLegacyAccount: () => okAsync({ signature }),
|
|
244
246
|
},
|
|
@@ -286,6 +288,8 @@ export function createFakeTruApiClient(options?: CreateFakeTruApiClientOptions):
|
|
|
286
288
|
notifications: notModeled("notifications"),
|
|
287
289
|
payment: notModeled("payment"),
|
|
288
290
|
permissions: notModeled("permissions"),
|
|
291
|
+
pocket: notModeled("pocket"),
|
|
292
|
+
renderer: notModeled("renderer"),
|
|
289
293
|
resourceAllocation: notModeled("resourceAllocation"),
|
|
290
294
|
theme: notModeled("theme"),
|
|
291
295
|
};
|
package/src/truapi.ts
CHANGED
|
@@ -347,6 +347,40 @@ export interface ResultAsync<T, E> {
|
|
|
347
347
|
if (import.meta.vitest) {
|
|
348
348
|
const { test, expect, describe } = import.meta.vitest;
|
|
349
349
|
|
|
350
|
+
// Re-exported verbatim, so a truapi bump reshapes public API with nothing
|
|
351
|
+
// here to fail.
|
|
352
|
+
type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2
|
|
353
|
+
? true
|
|
354
|
+
: false;
|
|
355
|
+
type Expect<T extends true> = T;
|
|
356
|
+
|
|
357
|
+
type PinnedAllocatableResource =
|
|
358
|
+
| { tag: "StatementStoreAllowance"; value?: undefined }
|
|
359
|
+
| { tag: "BulletinAllowance"; value?: undefined }
|
|
360
|
+
| {
|
|
361
|
+
tag: "SmartContractAllowance";
|
|
362
|
+
value: { tag: "Index"; value: number } | { tag: "Raw"; value: HexString };
|
|
363
|
+
}
|
|
364
|
+
| { tag: "AutoSigning"; value?: undefined };
|
|
365
|
+
|
|
366
|
+
type _AllocatableResourceIsPinned = Expect<
|
|
367
|
+
Equal<AllocatableResource, PinnedAllocatableResource>
|
|
368
|
+
>;
|
|
369
|
+
type _AllocationOutcomeIsPinned = Expect<
|
|
370
|
+
Equal<AllocationOutcome, "Allocated" | "Rejected" | "NotAvailable">
|
|
371
|
+
>;
|
|
372
|
+
|
|
373
|
+
type _RemotePermissionIsPinned = Expect<
|
|
374
|
+
Equal<
|
|
375
|
+
RemotePermission,
|
|
376
|
+
| { tag: "Remote"; value: { domains: string[] } }
|
|
377
|
+
| { tag: "WebRtc"; value?: undefined }
|
|
378
|
+
| { tag: "ChainSubmit"; value?: undefined }
|
|
379
|
+
| { tag: "PreimageSubmit"; value?: undefined }
|
|
380
|
+
| { tag: "StatementSubmit"; value?: undefined }
|
|
381
|
+
>
|
|
382
|
+
>;
|
|
383
|
+
|
|
350
384
|
test("getTruApi returns null outside a container", async () => {
|
|
351
385
|
const api = await getTruApi();
|
|
352
386
|
expect(api === null || typeof api === "object").toBe(true);
|