@parity/product-sdk-host 0.0.0-dev.312.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/LICENSE +201 -0
- package/dist/chain-discovery-nLrPzb3d.d.ts +70 -0
- package/dist/chunk-GDXSV7JV.js +50 -0
- package/dist/chunk-GDXSV7JV.js.map +1 -0
- package/dist/index.d.ts +1210 -0
- package/dist/index.js +1163 -0
- package/dist/index.js.map +1 -0
- package/dist/testing.d.ts +105 -0
- package/dist/testing.js +177 -0
- package/dist/testing.js.map +1 -0
- package/package.json +46 -0
- package/src/accounts.ts +1052 -0
- package/src/chain-discovery.ts +287 -0
- package/src/chain-spec.ts +272 -0
- package/src/chain-transaction.ts +241 -0
- package/src/chains.ts +46 -0
- package/src/chat.ts +122 -0
- package/src/container.ts +339 -0
- package/src/entropy.ts +105 -0
- package/src/errors.ts +239 -0
- package/src/features.ts +172 -0
- package/src/index.ts +149 -0
- package/src/navigation.ts +128 -0
- package/src/notifications.ts +113 -0
- package/src/papi-provider.ts +741 -0
- package/src/payments.ts +117 -0
- package/src/permissions.ts +236 -0
- package/src/result.ts +13 -0
- package/src/testing.ts +493 -0
- package/src/theme.ts +78 -0
- package/src/transport.ts +181 -0
- package/src/truapi.ts +313 -0
- package/src/types.ts +101 -0
- package/src/worker.ts +261 -0
package/src/transport.ts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// Copyright 2026 Parity Technologies (UK) Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Access to the in-house TruAPI client (`@parity/truapi`) for the host package.
|
|
5
|
+
*
|
|
6
|
+
* Environment detection and the lazily-built, cached client come from
|
|
7
|
+
* `@parity/truapi/sandbox`; this module layers the product-sdk-specific glue on
|
|
8
|
+
* top — an async {@link getClient} accessor and {@link subscribeWithInterrupt},
|
|
9
|
+
* which adapts a truapi stream into the host's {@link HostSubscription} shape.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { ObservableLike, TrUApiClient } from "@parity/truapi";
|
|
15
|
+
import {
|
|
16
|
+
getClientSync as sandboxGetClientSync,
|
|
17
|
+
isCorrectEnvironment as sandboxIsCorrectEnvironment,
|
|
18
|
+
} from "@parity/truapi/sandbox";
|
|
19
|
+
|
|
20
|
+
import type { HostSubscription } from "./types.js";
|
|
21
|
+
|
|
22
|
+
/** A {@link HostSubscription} carrying the transport-assigned subscription id. */
|
|
23
|
+
export interface TransportSubscription extends HostSubscription {
|
|
24
|
+
readonly subscriptionId: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Test-only override. When set — via `setTruApiClient`, exposed through
|
|
28
|
+
// `@parity/product-sdk-host/testing` — every host accessor resolves this client
|
|
29
|
+
// instead of the sandbox one. `null` in production, so the branches below are
|
|
30
|
+
// no-ops there.
|
|
31
|
+
let clientOverride: TrUApiClient | null = null;
|
|
32
|
+
|
|
33
|
+
function isProductionBuild(): boolean {
|
|
34
|
+
try {
|
|
35
|
+
// Must stay a plain `process.env.NODE_ENV` member expression: bundlers
|
|
36
|
+
// (Vite, esbuild, webpack) substitute it textually, which is how this
|
|
37
|
+
// check works in browser builds where `process` doesn't exist.
|
|
38
|
+
return process.env.NODE_ENV === "production";
|
|
39
|
+
} catch {
|
|
40
|
+
// No `process` and no bundler define — can't tell, stay quiet.
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Test-only seam: force {@link getClient} / {@link getClientSync} to return
|
|
47
|
+
* `client`, and {@link isCorrectEnvironment} to report `true`. Pass `null` to
|
|
48
|
+
* restore normal detection. Exposed through `@parity/product-sdk-host/testing`,
|
|
49
|
+
* not the package's main entry.
|
|
50
|
+
*
|
|
51
|
+
* Calling this in a production build silently reroutes every host accessor to
|
|
52
|
+
* the injected client, so we warn — it almost always means a `/testing` import
|
|
53
|
+
* leaked into a production path.
|
|
54
|
+
*/
|
|
55
|
+
export function setTruApiClient(client: TrUApiClient | null): void {
|
|
56
|
+
if (client !== null && isProductionBuild()) {
|
|
57
|
+
console.warn(
|
|
58
|
+
"[product-sdk] setTruApiClient() was called in a production build. This is a test-only seam from @parity/product-sdk-host/testing; a leaked import will silently reroute all host access to the injected client.",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
clientOverride = client;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Synchronous TruAPI client accessor. Returns the injected test client when one
|
|
66
|
+
* is set, otherwise the sandbox client (`null` outside a host container).
|
|
67
|
+
*/
|
|
68
|
+
export function getClientSync(): TrUApiClient | null {
|
|
69
|
+
return clientOverride ?? sandboxGetClientSync();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Host-container detection. `true` when a test client is injected, otherwise the
|
|
74
|
+
* sandbox heuristic (iframe / webview marker / injected message port).
|
|
75
|
+
*/
|
|
76
|
+
export function isCorrectEnvironment(): boolean {
|
|
77
|
+
return clientOverride !== null || sandboxIsCorrectEnvironment();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get the TruAPI client. Returns `null` outside a host container. Async wrapper
|
|
82
|
+
* over {@link getClientSync} for the host wrappers that already `await` it.
|
|
83
|
+
*/
|
|
84
|
+
export async function getClient(): Promise<TrUApiClient | null> {
|
|
85
|
+
return getClientSync();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Adapt a truapi `ObservableLike` stream into the host's callback-style
|
|
90
|
+
* {@link HostSubscription} (`unsubscribe` + `onInterrupt`). `onNext` fires for
|
|
91
|
+
* each item; the registered `onInterrupt` callback fires when the host ends the
|
|
92
|
+
* subscription server-side — which the generated client surfaces as either
|
|
93
|
+
* `complete` (a host interrupt frame) or `error` (transport close). Shared by
|
|
94
|
+
* the statement-store and preimage adapters, which both expose this shape.
|
|
95
|
+
*/
|
|
96
|
+
export function subscribeWithInterrupt<Item, Reason = never>(
|
|
97
|
+
observable: ObservableLike<Item, Reason>,
|
|
98
|
+
onNext: (item: Item) => void,
|
|
99
|
+
): TransportSubscription {
|
|
100
|
+
let interruptCallback: ((reason?: unknown) => void) | undefined;
|
|
101
|
+
const sub = observable.subscribe({
|
|
102
|
+
next: onNext,
|
|
103
|
+
error: (reason) => interruptCallback?.(reason),
|
|
104
|
+
complete: () => interruptCallback?.(),
|
|
105
|
+
});
|
|
106
|
+
return {
|
|
107
|
+
subscriptionId: sub.subscriptionId,
|
|
108
|
+
unsubscribe: () => sub.unsubscribe(),
|
|
109
|
+
onInterrupt: (callback) => {
|
|
110
|
+
interruptCallback = callback;
|
|
111
|
+
return () => {
|
|
112
|
+
if (interruptCallback === callback) interruptCallback = undefined;
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (import.meta.vitest) {
|
|
119
|
+
const { test, expect, afterEach } = import.meta.vitest;
|
|
120
|
+
|
|
121
|
+
afterEach(() => setTruApiClient(null));
|
|
122
|
+
|
|
123
|
+
// Environment detection and client building are covered by `@parity/truapi`'s
|
|
124
|
+
// own sandbox tests; here we only assert the local glue degrades outside a
|
|
125
|
+
// host container.
|
|
126
|
+
test("getClientSync returns null outside a container", () => {
|
|
127
|
+
expect(getClientSync()).toBeNull();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("getClient resolves null outside a container", async () => {
|
|
131
|
+
expect(await getClient()).toBeNull();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("setTruApiClient overrides the client and container detection", async () => {
|
|
135
|
+
const fake = {} as TrUApiClient;
|
|
136
|
+
setTruApiClient(fake);
|
|
137
|
+
expect(getClientSync()).toBe(fake);
|
|
138
|
+
expect(await getClient()).toBe(fake);
|
|
139
|
+
expect(isCorrectEnvironment()).toBe(true);
|
|
140
|
+
|
|
141
|
+
setTruApiClient(null);
|
|
142
|
+
expect(getClientSync()).toBeNull();
|
|
143
|
+
expect(isCorrectEnvironment()).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("setTruApiClient warns when injecting in a production build", () => {
|
|
147
|
+
const original = process.env.NODE_ENV;
|
|
148
|
+
const warnings: string[] = [];
|
|
149
|
+
const realWarn = console.warn;
|
|
150
|
+
console.warn = (...args: unknown[]) => void warnings.push(String(args[0]));
|
|
151
|
+
try {
|
|
152
|
+
process.env.NODE_ENV = "production";
|
|
153
|
+
setTruApiClient({} as TrUApiClient);
|
|
154
|
+
expect(warnings).toHaveLength(1);
|
|
155
|
+
expect(warnings[0]).toContain("production build");
|
|
156
|
+
|
|
157
|
+
// Clearing the override must not warn.
|
|
158
|
+
setTruApiClient(null);
|
|
159
|
+
expect(warnings).toHaveLength(1);
|
|
160
|
+
} finally {
|
|
161
|
+
console.warn = realWarn;
|
|
162
|
+
process.env.NODE_ENV = original;
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("subscribeWithInterrupt preserves the transport subscription id", () => {
|
|
167
|
+
const observable = {
|
|
168
|
+
subscribe: () => ({
|
|
169
|
+
subscriptionId: "p:17",
|
|
170
|
+
unsubscribe: () => {},
|
|
171
|
+
}),
|
|
172
|
+
[Symbol.observable]() {
|
|
173
|
+
return this;
|
|
174
|
+
},
|
|
175
|
+
} as ObservableLike<never>;
|
|
176
|
+
|
|
177
|
+
const subscription = subscribeWithInterrupt(observable, () => {});
|
|
178
|
+
|
|
179
|
+
expect(subscription.subscriptionId).toBe("p:17");
|
|
180
|
+
});
|
|
181
|
+
}
|
package/src/truapi.ts
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
// Copyright 2026 Parity Technologies (UK) Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* TruAPI - the protocol for communicating between apps and the Polkadot host container.
|
|
5
|
+
*
|
|
6
|
+
* This module centralizes access to the in-house `@parity/truapi` client,
|
|
7
|
+
* allowing other `@parity/product-sdk-*` packages to import from here rather
|
|
8
|
+
* than depending directly on the protocol package. The client is built and
|
|
9
|
+
* cached by {@link module:transport}; this module adds the accessor plus the
|
|
10
|
+
* two helpers the convenience wrappers fold truapi's `ResultAsync` through —
|
|
11
|
+
* {@link mapHostResult} (returns a `Result`, used by the public operations) and
|
|
12
|
+
* {@link unwrapHostResult} (throws, used by the adapter-object methods).
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { scale } from "@parity/truapi";
|
|
18
|
+
import type {
|
|
19
|
+
AllocatableResource,
|
|
20
|
+
AllocationOutcome,
|
|
21
|
+
HexString,
|
|
22
|
+
RemotePermission,
|
|
23
|
+
TrUApiClient,
|
|
24
|
+
} from "@parity/truapi";
|
|
25
|
+
import { createLogger } from "@parity/product-sdk-logger";
|
|
26
|
+
|
|
27
|
+
import {
|
|
28
|
+
type HostError,
|
|
29
|
+
type HostErrorPayload,
|
|
30
|
+
HostCallFailedError,
|
|
31
|
+
HostUnavailableError,
|
|
32
|
+
formatHostError,
|
|
33
|
+
} from "./errors.js";
|
|
34
|
+
import { type Result, err, ok } from "./result.js";
|
|
35
|
+
import { getClient, subscribeWithInterrupt } from "./transport.js";
|
|
36
|
+
import type { HostSubscription, Statement, StatementProof } from "./types.js";
|
|
37
|
+
|
|
38
|
+
const log = createLogger("host");
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Await a host `ResultAsync`, returning its Ok value or throwing a diagnostic
|
|
42
|
+
* `Error` built from the host's error payload (preserved as `cause`).
|
|
43
|
+
*
|
|
44
|
+
* This is the *throwing* helper, retained for the methods of the adapter objects
|
|
45
|
+
* returned by the feature-detection getters (`PreimageManager.submit`,
|
|
46
|
+
* `HostLocalStorage.read`, `AccountsProvider` signing, …). Those objects often
|
|
47
|
+
* implement external interfaces (e.g. polkadot-api's `JsonRpcProvider`) whose
|
|
48
|
+
* method signatures can't carry a {@link Result}, so they keep the
|
|
49
|
+
* throw convention. The flat public operations use {@link mapHostResult} instead.
|
|
50
|
+
*/
|
|
51
|
+
export function unwrapHostResult<T, E>(result: ResultAsync<T, E>, label: string): Promise<T> {
|
|
52
|
+
return result.match(
|
|
53
|
+
(value) => value,
|
|
54
|
+
(error: E) => {
|
|
55
|
+
throw new Error(`${label}: ${formatHostError(error)}`, { cause: error });
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Await a host `ResultAsync` and fold it into a tagged {@link Result}: maps the
|
|
62
|
+
* Ok value through `map`, or wraps the host error payload in a
|
|
63
|
+
* {@link HostCallFailedError} on the `err` channel. This is the non-throwing
|
|
64
|
+
* boundary the flat public host operations (`requestPermission`, `deriveEntropy`,
|
|
65
|
+
* `requestResourceAllocation`, …) return through.
|
|
66
|
+
*/
|
|
67
|
+
export function mapHostResult<T, U>(
|
|
68
|
+
result: ResultAsync<T, HostErrorPayload>,
|
|
69
|
+
map: (value: T) => U,
|
|
70
|
+
label: string,
|
|
71
|
+
): Promise<Result<U, HostError>> {
|
|
72
|
+
return result.match(
|
|
73
|
+
(value) => ok(map(value)),
|
|
74
|
+
(error) => err(new HostCallFailedError(label, error)),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
79
|
+
// Hex helpers
|
|
80
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
/** Convert bytes to a `0x`-prefixed lower-case hex string. */
|
|
83
|
+
export function toHex(bytes: Uint8Array): HexString {
|
|
84
|
+
return scale.bytesToHex(bytes);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Convert a hex string (with or without `0x`) to bytes. */
|
|
88
|
+
export function fromHex(hex: string): Uint8Array {
|
|
89
|
+
return scale.hexToBytes(hex);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** A `0x`-prefixed hex string used by the host API surface for raw byte payloads. */
|
|
93
|
+
export type { HexString };
|
|
94
|
+
|
|
95
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
96
|
+
// TruAPI accessor
|
|
97
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The TruApi client — namespaced access to every host protocol domain
|
|
101
|
+
* (`permissions`, `entropy`, `signing`, `statementStore`, `system`,
|
|
102
|
+
* `localStorage`, …). Identical to `TrUApiClient` from `@parity/truapi`.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* const truApi = await getTruApi();
|
|
107
|
+
* if (truApi) {
|
|
108
|
+
* await truApi.permissions.requestRemotePermission({
|
|
109
|
+
* permission: { tag: "ChainSubmit", value: undefined },
|
|
110
|
+
* });
|
|
111
|
+
* await truApi.system.navigateTo({ url: "polkadot://settings" });
|
|
112
|
+
* }
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export type TruApi = TrUApiClient;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get the TruAPI client for direct low-level access to host protocol domains.
|
|
119
|
+
*
|
|
120
|
+
* Returns the cached `@parity/truapi` client once the host transport is built
|
|
121
|
+
* and the handshake has run, or `null` when running outside a container.
|
|
122
|
+
*
|
|
123
|
+
* For most use cases, prefer the higher-level functions like
|
|
124
|
+
* {@link requestPermission}, {@link deriveEntropy}, or `getHostLocalStorage()`.
|
|
125
|
+
*
|
|
126
|
+
* @returns The TruAPI client, or `null` if unavailable.
|
|
127
|
+
*/
|
|
128
|
+
export async function getTruApi(): Promise<TruApi | null> {
|
|
129
|
+
return getClient();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Preimage manager handle for bulletin chain operations, backed by
|
|
134
|
+
* `truApi.preimage.*`. `lookup` opens a {@link HostSubscription} (`unsubscribe`
|
|
135
|
+
* + `onInterrupt`) that delivers the preimage bytes — or `null` until the host
|
|
136
|
+
* finds them; `submit` uploads a preimage and resolves to its `0x`-prefixed hex
|
|
137
|
+
* key.
|
|
138
|
+
*/
|
|
139
|
+
export interface PreimageManager {
|
|
140
|
+
lookup(key: HexString, callback: (preimage: Uint8Array | null) => void): HostSubscription;
|
|
141
|
+
submit(value: Uint8Array): Promise<HexString>;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Build a {@link PreimageManager} over a TruAPI client's `preimage` domain. */
|
|
145
|
+
function adaptPreimageManager(client: TrUApiClient): PreimageManager {
|
|
146
|
+
const preimage = client.preimage;
|
|
147
|
+
return {
|
|
148
|
+
lookup(key, callback) {
|
|
149
|
+
return subscribeWithInterrupt(preimage.lookupSubscribe({ request: { key } }), (item) =>
|
|
150
|
+
callback(item.value !== undefined ? fromHex(item.value) : null),
|
|
151
|
+
);
|
|
152
|
+
},
|
|
153
|
+
submit(value) {
|
|
154
|
+
return unwrapHostResult(preimage.submit(toHex(value)), "preimage submit failed");
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Get the preimage manager for bulletin chain operations.
|
|
161
|
+
*
|
|
162
|
+
* @returns The preimage manager, or `null` if unavailable (outside a container).
|
|
163
|
+
*/
|
|
164
|
+
export async function getPreimageManager(): Promise<PreimageManager | null> {
|
|
165
|
+
const client = await getClient();
|
|
166
|
+
return client ? adaptPreimageManager(client) : null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Construct a `PreimageManager`. Retained for API compatibility; with the single
|
|
171
|
+
* cached TruAPI client this is equivalent to {@link getPreimageManager}.
|
|
172
|
+
*
|
|
173
|
+
* @returns A `PreimageManager` instance, or `null` if unavailable.
|
|
174
|
+
*/
|
|
175
|
+
export async function createHostPreimageManager(): Promise<PreimageManager | null> {
|
|
176
|
+
return getPreimageManager();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
180
|
+
// Resource allocation
|
|
181
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
// Resource-allocation / permission types, re-exported verbatim from
|
|
184
|
+
// `@parity/truapi` (imported above for the local signatures):
|
|
185
|
+
// - `AllocatableResource` — resource types requestable via `requestResourceAllocation`.
|
|
186
|
+
// Its `SmartContractAllowance` variant carries the tagged `DerivationIndex`
|
|
187
|
+
// selector (`{ tag: "Index", value: number }` for a plain index, `{ tag:
|
|
188
|
+
// "Raw", value: HexString }` for a raw 32-byte index).
|
|
189
|
+
// - `AllocationOutcome` — per-resource outcome, the string union
|
|
190
|
+
// `"Allocated" | "Rejected" | "NotAvailable"` (RFC-10).
|
|
191
|
+
// - `RemotePermission` — permission the dapp asks the host to grant via `requestPermission`.
|
|
192
|
+
export type { AllocatableResource, AllocationOutcome, RemotePermission };
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Request the host to pre-allocate one or more resource allowances.
|
|
196
|
+
*
|
|
197
|
+
* The host prompts the user once; subsequent operations covered by the
|
|
198
|
+
* granted allowance don't re-prompt.
|
|
199
|
+
*
|
|
200
|
+
* @param resources - Resources to request.
|
|
201
|
+
* @returns `ok` with per-resource outcomes in the same order as `resources`, or
|
|
202
|
+
* `err(HostUnavailableError | HostCallFailedError)`.
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```ts
|
|
206
|
+
* const r = await requestResourceAllocation([
|
|
207
|
+
* { tag: "BulletinAllowance", value: undefined },
|
|
208
|
+
* ]);
|
|
209
|
+
* if (r.ok && r.value[0] === "Allocated") { ... }
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
export async function requestResourceAllocation(
|
|
213
|
+
resources: AllocatableResource[],
|
|
214
|
+
): Promise<Result<AllocationOutcome[], HostError>> {
|
|
215
|
+
const truApi = await getTruApi();
|
|
216
|
+
if (!truApi) {
|
|
217
|
+
return err(new HostUnavailableError("requestResourceAllocation: TruAPI unavailable"));
|
|
218
|
+
}
|
|
219
|
+
log.debug("requestResourceAllocation", { resources: resources.map((r) => r.tag) });
|
|
220
|
+
|
|
221
|
+
return mapHostResult(
|
|
222
|
+
truApi.resourceAllocation.request({ resources }),
|
|
223
|
+
(response) => response.outcomes,
|
|
224
|
+
"requestResourceAllocation failed",
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
229
|
+
// Authorized Statement Store proof creation (RFC-10 §"Statement Store allowance")
|
|
230
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Have the host sign a Statement using the product's allowance-bearing account,
|
|
234
|
+
* which it picks internally — RFC-10 §"Statement Store allowance". No per-call
|
|
235
|
+
* account id is needed (this is the sponsored-submission path).
|
|
236
|
+
*
|
|
237
|
+
* Pairs with {@link getStatementStore}'s `submit`: call this to obtain a proof,
|
|
238
|
+
* attach it to the Statement, and submit the result.
|
|
239
|
+
*
|
|
240
|
+
* @param statement - The Statement to be signed.
|
|
241
|
+
* @returns `ok` with the proof to attach before submitting, or
|
|
242
|
+
* `err(HostUnavailableError | HostCallFailedError)`.
|
|
243
|
+
*/
|
|
244
|
+
export async function createProofAuthorized(
|
|
245
|
+
statement: Statement,
|
|
246
|
+
): Promise<Result<StatementProof, HostError>> {
|
|
247
|
+
const truApi = await getTruApi();
|
|
248
|
+
if (!truApi) {
|
|
249
|
+
return err(new HostUnavailableError("createProofAuthorized: TruAPI unavailable"));
|
|
250
|
+
}
|
|
251
|
+
log.debug("createProofAuthorized", { topics: statement.topics.length });
|
|
252
|
+
|
|
253
|
+
return mapHostResult(
|
|
254
|
+
truApi.statementStore.createProofAuthorized(statement),
|
|
255
|
+
(response) => response.proof,
|
|
256
|
+
"createProofAuthorized failed",
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Neverthrow-style ResultAsync returned by product-sdk methods.
|
|
262
|
+
*
|
|
263
|
+
* Use `.match(onOk, onErr)` to handle success/error cases.
|
|
264
|
+
*/
|
|
265
|
+
export interface ResultAsync<T, E> {
|
|
266
|
+
match: <A, B = A>(ok: (t: T) => A, err: (e: E) => B) => Promise<A | B>;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
270
|
+
// Tests
|
|
271
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
272
|
+
|
|
273
|
+
if (import.meta.vitest) {
|
|
274
|
+
const { test, expect } = import.meta.vitest;
|
|
275
|
+
|
|
276
|
+
test("getTruApi returns null outside a container", async () => {
|
|
277
|
+
const api = await getTruApi();
|
|
278
|
+
expect(api === null || typeof api === "object").toBe(true);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test("getPreimageManager returns manager or null", async () => {
|
|
282
|
+
const manager = await getPreimageManager();
|
|
283
|
+
expect(manager === null || typeof manager === "object").toBe(true);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("createHostPreimageManager returns null outside container", async () => {
|
|
287
|
+
expect(await createHostPreimageManager()).toBeNull();
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("hex helpers", () => {
|
|
291
|
+
expect(toHex(new Uint8Array([0xde, 0xad]))).toBe("0xdead");
|
|
292
|
+
expect(Array.from(fromHex("0xdead"))).toEqual([0xde, 0xad]);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test("requestResourceAllocation returns err when TruAPI is unavailable", async () => {
|
|
296
|
+
const api = await getTruApi();
|
|
297
|
+
if (api === null) {
|
|
298
|
+
const result = await requestResourceAllocation([
|
|
299
|
+
{ tag: "BulletinAllowance", value: undefined },
|
|
300
|
+
]);
|
|
301
|
+
expect(result.ok).toBe(false);
|
|
302
|
+
if (!result.ok) {
|
|
303
|
+
expect(result.error).toBeInstanceOf(HostUnavailableError);
|
|
304
|
+
}
|
|
305
|
+
} else {
|
|
306
|
+
expect(typeof requestResourceAllocation).toBe("function");
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test("createProofAuthorized is callable", () => {
|
|
311
|
+
expect(typeof createProofAuthorized).toBe("function");
|
|
312
|
+
});
|
|
313
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Copyright 2026 Parity Technologies (UK) Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Public types for the host wrappers.
|
|
5
|
+
*
|
|
6
|
+
* The statement-store types are re-exported from `@parity/truapi` so the Parity
|
|
7
|
+
* surface stays in lockstep with the in-house protocol codec types. Their fields
|
|
8
|
+
* are `0x`-prefixed hex strings (`HexString`) and their enums are `{ tag }` unions.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
ProductAccountId,
|
|
13
|
+
RemoteStatementStoreSubscribeItem,
|
|
14
|
+
SignedStatement,
|
|
15
|
+
Statement,
|
|
16
|
+
StatementProof,
|
|
17
|
+
Topic,
|
|
18
|
+
} from "@parity/truapi";
|
|
19
|
+
|
|
20
|
+
// Statement-store types, re-exported verbatim from `@parity/truapi` (imported
|
|
21
|
+
// above for the local signatures): `Statement` / `SignedStatement` /
|
|
22
|
+
// `StatementProof` / `Topic` / `ProductAccountId`. Their fields are
|
|
23
|
+
// `0x`-prefixed `HexString`s and their enums are `{ tag }` unions; the proof
|
|
24
|
+
// variants cover `Sr25519` / `Ed25519` / `Ecdsa` / `OnChain`.
|
|
25
|
+
export type { ProductAccountId, SignedStatement, Statement, StatementProof, Topic };
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Persistent storage exposed by the host container, including string, JSON
|
|
29
|
+
* and raw byte (`readBytes`/`writeBytes`) accessors. Most apps reach it
|
|
30
|
+
* indirectly through the Storage package's `KvStore`; reach for it directly
|
|
31
|
+
* via {@link getHostLocalStorage} when you need raw host storage without the
|
|
32
|
+
* KV abstraction.
|
|
33
|
+
*
|
|
34
|
+
* Backed by `truApi.localStorage.*` (raw `read`/`write`/`clear` over hex bytes);
|
|
35
|
+
* {@link getHostLocalStorage} adapts that into this richer surface. `readString`
|
|
36
|
+
* resolves to `""` for a missing key and `readJSON`/`readBytes` to
|
|
37
|
+
* `null`/`undefined`.
|
|
38
|
+
*/
|
|
39
|
+
export interface HostLocalStorage {
|
|
40
|
+
/** Read a UTF-8 string value; `""` when the key is absent. */
|
|
41
|
+
readString(key: string): Promise<string>;
|
|
42
|
+
/** Write a UTF-8 string value. */
|
|
43
|
+
writeString(key: string, value: string): Promise<void>;
|
|
44
|
+
/** Read and JSON-parse a value; `null` when the key is absent. */
|
|
45
|
+
readJSON(key: string): Promise<unknown>;
|
|
46
|
+
/** JSON-stringify and write a value. */
|
|
47
|
+
writeJSON(key: string, value: unknown): Promise<void>;
|
|
48
|
+
/** Read raw bytes; `undefined` when the key is absent. */
|
|
49
|
+
readBytes(key: string): Promise<Uint8Array | undefined>;
|
|
50
|
+
/** Write raw bytes. */
|
|
51
|
+
writeBytes(key: string, value: Uint8Array): Promise<void>;
|
|
52
|
+
/** Remove a key. */
|
|
53
|
+
clear(key: string): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Topic-based subscription filter. The host delivers statements that match
|
|
58
|
+
* either *all* of the listed topics (`matchAll`) or *any* of them (`matchAny`).
|
|
59
|
+
*
|
|
60
|
+
* This is a field-discriminated form of truapi's `RemoteStatementStoreSubscribeRequest`,
|
|
61
|
+
* which is a tagged union (`{ tag: "MatchAll"; value: Topic[] } | { tag: "MatchAny"; value: Topic[] }`).
|
|
62
|
+
* The transport maps between the two.
|
|
63
|
+
*/
|
|
64
|
+
export type StatementTopicFilter = { matchAll: Topic[] } | { matchAny: Topic[] };
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A page of signed statements delivered by {@link HostStatementStore.subscribe}.
|
|
68
|
+
*
|
|
69
|
+
* truapi's `RemoteStatementStoreSubscribeItem`, re-exported under a friendlier
|
|
70
|
+
* name. Pages arrive sequentially; `isComplete` is `false` while the host
|
|
71
|
+
* streams the historical backfill and `true` once it's done (and on every
|
|
72
|
+
* subsequent live-update page).
|
|
73
|
+
*/
|
|
74
|
+
export type StatementsPage = RemoteStatementStoreSubscribeItem;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Subscription handle returned by the host. Exposes `unsubscribe()` plus an
|
|
78
|
+
* `onInterrupt` hook that fires if the host interrupts the subscription
|
|
79
|
+
* server-side; `onInterrupt` returns a function that cancels the hook.
|
|
80
|
+
*/
|
|
81
|
+
export interface HostSubscription {
|
|
82
|
+
unsubscribe(): void;
|
|
83
|
+
onInterrupt(callback: (reason?: unknown) => void): () => void;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Statement Store handle exposed by the host container, backed by
|
|
88
|
+
* `truApi.statementStore.*`. `subscribe` streams matching statements;
|
|
89
|
+
* `createProofAuthorized` signs a statement with the product's RFC-10 allowance
|
|
90
|
+
* account (the sponsored path — no per-call account id); `submit` publishes a
|
|
91
|
+
* signed statement. The `statement-store` package layers a higher-level client
|
|
92
|
+
* on top.
|
|
93
|
+
*/
|
|
94
|
+
export interface HostStatementStore {
|
|
95
|
+
subscribe(
|
|
96
|
+
filter: StatementTopicFilter,
|
|
97
|
+
callback: (page: StatementsPage) => void,
|
|
98
|
+
): HostSubscription;
|
|
99
|
+
createProofAuthorized(statement: Statement): Promise<StatementProof>;
|
|
100
|
+
submit(signedStatement: SignedStatement): Promise<void>;
|
|
101
|
+
}
|