@parity/product-sdk-host 0.10.3 → 0.12.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 +681 -462
- package/dist/index.js +890 -219
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
- package/src/accounts.ts +544 -0
- package/src/chain-spec.ts +272 -0
- package/src/chain-transaction.ts +241 -0
- package/src/chat.ts +81 -85
- package/src/container.ts +211 -246
- package/src/entropy.ts +63 -25
- package/src/errors.ts +198 -0
- package/src/features.ts +172 -0
- package/src/index.ts +47 -22
- package/src/navigation.ts +128 -0
- package/src/notifications.ts +59 -69
- package/src/papi-provider.ts +673 -0
- package/src/payments.ts +77 -61
- package/src/permissions.ts +107 -105
- package/src/result.ts +56 -0
- package/src/theme.ts +35 -63
- package/src/transport.ts +71 -0
- package/src/truapi.ts +166 -409
- package/src/types.ts +69 -61
package/src/truapi.ts
CHANGED
|
@@ -3,310 +3,190 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* TruAPI - the protocol for communicating between apps and the Polkadot host container.
|
|
5
5
|
*
|
|
6
|
-
* This module centralizes access to
|
|
7
|
-
* allowing other
|
|
8
|
-
* directly on
|
|
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).
|
|
9
13
|
*
|
|
10
14
|
* @module
|
|
11
15
|
*/
|
|
12
16
|
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
import { enumValue } from "@novasamatech/host-api";
|
|
17
|
+
import { scale } from "@parity/truapi";
|
|
16
18
|
import type {
|
|
17
|
-
AllocatableResource
|
|
18
|
-
AllocationOutcome
|
|
19
|
-
|
|
20
|
-
RemotePermission
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
AllocatableResource,
|
|
20
|
+
AllocationOutcome,
|
|
21
|
+
HexString,
|
|
22
|
+
RemotePermission,
|
|
23
|
+
TrUApiClient,
|
|
24
|
+
} from "@parity/truapi";
|
|
25
|
+
import { createLogger } from "@parity/product-sdk-logger";
|
|
23
26
|
|
|
24
|
-
import {
|
|
25
|
-
|
|
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";
|
|
26
37
|
|
|
27
38
|
const log = createLogger("host");
|
|
28
39
|
|
|
29
40
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
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.
|
|
38
50
|
*/
|
|
39
|
-
export function
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
inner != null &&
|
|
47
|
-
typeof inner === "object" &&
|
|
48
|
-
"message" in inner &&
|
|
49
|
-
typeof (inner as { message: unknown }).message === "string"
|
|
50
|
-
) {
|
|
51
|
-
const named = inner as { name?: unknown; message: string };
|
|
52
|
-
return typeof named.name === "string" ? `${named.name}: ${named.message}` : named.message;
|
|
53
|
-
}
|
|
54
|
-
try {
|
|
55
|
-
return JSON.stringify(inner);
|
|
56
|
-
} catch {
|
|
57
|
-
return String(inner);
|
|
58
|
-
}
|
|
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
|
+
);
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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)),
|
|
68
75
|
);
|
|
69
76
|
}
|
|
70
77
|
|
|
71
78
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
72
|
-
//
|
|
79
|
+
// Hex helpers
|
|
73
80
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
* ```
|
|
88
|
-
*/
|
|
89
|
-
enumValue,
|
|
90
|
-
/**
|
|
91
|
-
* Check if a value is a specific enum variant.
|
|
92
|
-
*/
|
|
93
|
-
isEnumVariant,
|
|
94
|
-
/**
|
|
95
|
-
* Assert that a value is a specific enum variant, throwing if not.
|
|
96
|
-
*/
|
|
97
|
-
assertEnumVariant,
|
|
98
|
-
/**
|
|
99
|
-
* Unwrap a Result, throwing on error.
|
|
100
|
-
*/
|
|
101
|
-
unwrapResultOrThrow,
|
|
102
|
-
/**
|
|
103
|
-
* Create an Ok result.
|
|
104
|
-
*/
|
|
105
|
-
resultOk,
|
|
106
|
-
/**
|
|
107
|
-
* Create an Err result.
|
|
108
|
-
*/
|
|
109
|
-
resultErr,
|
|
110
|
-
/**
|
|
111
|
-
* Convert bytes to hex string.
|
|
112
|
-
*/
|
|
113
|
-
toHex,
|
|
114
|
-
/**
|
|
115
|
-
* Convert hex string to bytes.
|
|
116
|
-
*/
|
|
117
|
-
fromHex,
|
|
118
|
-
} from "@novasamatech/host-api";
|
|
119
|
-
|
|
120
|
-
/** A `0x`-prefixed hex string (the template literal type ``\`0x${string}\``) used by the host API surface for raw byte payloads. Re-exported from `@novasamatech/host-api` so consumers bridging between host APIs and SDK code can reach the host-side type without an additional dependency. */
|
|
121
|
-
export type { HexString } from "@novasamatech/host-api";
|
|
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 };
|
|
122
94
|
|
|
123
95
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
124
96
|
// TruAPI accessor
|
|
125
97
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
126
98
|
|
|
127
99
|
/**
|
|
128
|
-
* The TruApi
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* - `navigateTo(url)` - Navigate to a URL within the host
|
|
132
|
-
* - `permission(permissions)` - Request permissions from the host
|
|
133
|
-
* - `localStorageRead/Write/Clear` - Host-backed storage
|
|
134
|
-
* - `sign(payload)` - Request transaction signing
|
|
135
|
-
* - `deriveEntropy(context)` - Derive deterministic entropy
|
|
136
|
-
* - `themeSubscribe()` - Subscribe to host theme changes
|
|
137
|
-
* - And many more...
|
|
138
|
-
*
|
|
139
|
-
* Type identical to `hostApi` from `@novasamatech/host-api-wrapper` so that
|
|
140
|
-
* `truApi.X(...)` calls keep their full inference (return types, method
|
|
141
|
-
* names, parameter shapes) instead of decaying to `any`.
|
|
142
|
-
*/
|
|
143
|
-
export type TruApi = typeof import("@novasamatech/host-api-wrapper").hostApi;
|
|
144
|
-
|
|
145
|
-
/** Cached TruApi instance */
|
|
146
|
-
let cachedTruApi: TruApi | null = null;
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Get the TruAPI instance for direct low-level access.
|
|
150
|
-
*
|
|
151
|
-
* Returns the `hostApi` object from `@novasamatech/host-api-wrapper` which provides
|
|
152
|
-
* methods for communicating directly with the host container. Returns `null`
|
|
153
|
-
* when running outside a container or when the SDK is unavailable.
|
|
154
|
-
*
|
|
155
|
-
* For most use cases, prefer the higher-level functions like `getHostLocalStorage()`,
|
|
156
|
-
* `getHostProvider()`, etc. Use this when you need direct access to host methods
|
|
157
|
-
* like `navigateTo()`, `permission()`, or `deriveEntropy()`.
|
|
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`.
|
|
158
103
|
*
|
|
159
104
|
* @example
|
|
160
105
|
* ```ts
|
|
161
|
-
* import { getTruApi, enumValue } from "@parity/product-sdk-host";
|
|
162
|
-
*
|
|
163
106
|
* const truApi = await getTruApi();
|
|
164
107
|
* if (truApi) {
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
* // Navigate to a URL
|
|
169
|
-
* await truApi.navigateTo("polkadot://settings");
|
|
170
|
-
*
|
|
171
|
-
* // Subscribe to theme changes
|
|
172
|
-
* const sub = truApi.themeSubscribe(undefined, (theme) => {
|
|
173
|
-
* console.log("Theme changed:", theme);
|
|
108
|
+
* await truApi.permissions.requestRemotePermission({
|
|
109
|
+
* permission: { tag: "ChainSubmit", value: undefined },
|
|
174
110
|
* });
|
|
111
|
+
* await truApi.system.navigateTo({ url: "polkadot://settings" });
|
|
175
112
|
* }
|
|
176
113
|
* ```
|
|
177
|
-
*
|
|
178
|
-
* @returns The TruAPI instance, or `null` if unavailable.
|
|
179
114
|
*/
|
|
180
|
-
export
|
|
181
|
-
if (cachedTruApi) return cachedTruApi;
|
|
182
|
-
|
|
183
|
-
try {
|
|
184
|
-
const sdk = await import("@novasamatech/host-api-wrapper");
|
|
185
|
-
cachedTruApi = sdk.hostApi;
|
|
186
|
-
log.debug("TruAPI loaded");
|
|
187
|
-
return cachedTruApi;
|
|
188
|
-
} catch {
|
|
189
|
-
log.debug("TruAPI unavailable (not in container or SDK not installed)");
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
115
|
+
export type TruApi = TrUApiClient;
|
|
193
116
|
|
|
194
117
|
/**
|
|
195
|
-
* Get the
|
|
118
|
+
* Get the TruAPI client for direct low-level access to host protocol domains.
|
|
196
119
|
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
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.
|
|
199
122
|
*
|
|
200
|
-
*
|
|
123
|
+
* For most use cases, prefer the higher-level functions like
|
|
124
|
+
* {@link requestPermission}, {@link deriveEntropy}, or `getHostLocalStorage()`.
|
|
201
125
|
*
|
|
202
|
-
* @
|
|
203
|
-
* ```ts
|
|
204
|
-
* import { getPreimageManager } from "@parity/product-sdk-host";
|
|
205
|
-
*
|
|
206
|
-
* const manager = await getPreimageManager();
|
|
207
|
-
* if (manager) {
|
|
208
|
-
* // Submit a preimage
|
|
209
|
-
* const key = await manager.submit(new Uint8Array([1, 2, 3]));
|
|
210
|
-
*
|
|
211
|
-
* // Look up a preimage
|
|
212
|
-
* const sub = manager.lookup(key, (data) => {
|
|
213
|
-
* if (data) console.log("Found:", data);
|
|
214
|
-
* });
|
|
215
|
-
* }
|
|
216
|
-
* ```
|
|
126
|
+
* @returns The TruAPI client, or `null` if unavailable.
|
|
217
127
|
*/
|
|
218
|
-
export async function
|
|
219
|
-
|
|
220
|
-
const sdk = await import("@novasamatech/host-api-wrapper");
|
|
221
|
-
return sdk.preimageManager;
|
|
222
|
-
} catch (err) {
|
|
223
|
-
log.debug("getPreimageManager unavailable", err);
|
|
224
|
-
return null;
|
|
225
|
-
}
|
|
128
|
+
export async function getTruApi(): Promise<TruApi | null> {
|
|
129
|
+
return getClient();
|
|
226
130
|
}
|
|
227
131
|
|
|
228
132
|
/**
|
|
229
|
-
* Preimage manager handle for bulletin chain operations
|
|
230
|
-
* `
|
|
231
|
-
* `
|
|
232
|
-
*
|
|
233
|
-
*
|
|
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.
|
|
234
138
|
*/
|
|
235
|
-
export
|
|
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
|
+
}
|
|
236
158
|
|
|
237
159
|
/**
|
|
238
|
-
*
|
|
239
|
-
* transport. Use this when you need a non-default transport; otherwise
|
|
240
|
-
* prefer {@link getPreimageManager}, which returns the shared singleton.
|
|
241
|
-
*
|
|
242
|
-
* Mirrors `createPreimageManager` from `@novasamatech/host-api-wrapper`.
|
|
160
|
+
* Get the preimage manager for bulletin chain operations.
|
|
243
161
|
*
|
|
244
|
-
* @
|
|
245
|
-
* @returns A new `PreimageManager` instance, or `null` if unavailable.
|
|
162
|
+
* @returns The preimage manager, or `null` if unavailable (outside a container).
|
|
246
163
|
*/
|
|
247
|
-
export async function
|
|
248
|
-
|
|
249
|
-
):
|
|
250
|
-
if (!(await isInsideContainer())) return null;
|
|
251
|
-
try {
|
|
252
|
-
const sdk = await import("@novasamatech/host-api-wrapper");
|
|
253
|
-
return sdk.createPreimageManager(transport);
|
|
254
|
-
} catch (err) {
|
|
255
|
-
log.debug("createHostPreimageManager unavailable", err);
|
|
256
|
-
return null;
|
|
257
|
-
}
|
|
164
|
+
export async function getPreimageManager(): Promise<PreimageManager | null> {
|
|
165
|
+
const client = await getClient();
|
|
166
|
+
return client ? adaptPreimageManager(client) : null;
|
|
258
167
|
}
|
|
259
168
|
|
|
260
169
|
/**
|
|
261
|
-
*
|
|
170
|
+
* Construct a `PreimageManager`. Retained for API compatibility; with the single
|
|
171
|
+
* cached TruAPI client this is equivalent to {@link getPreimageManager}.
|
|
262
172
|
*
|
|
263
|
-
* @returns
|
|
173
|
+
* @returns A `PreimageManager` instance, or `null` if unavailable.
|
|
264
174
|
*/
|
|
265
|
-
export async function
|
|
266
|
-
|
|
267
|
-
const sdk = await import("@novasamatech/host-api-wrapper");
|
|
268
|
-
return sdk.createAccountsProvider();
|
|
269
|
-
} catch (err) {
|
|
270
|
-
log.debug("getAccountsProvider unavailable", err);
|
|
271
|
-
return null;
|
|
272
|
-
}
|
|
175
|
+
export async function createHostPreimageManager(): Promise<PreimageManager | null> {
|
|
176
|
+
return getPreimageManager();
|
|
273
177
|
}
|
|
274
178
|
|
|
275
179
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
276
180
|
// Resource allocation
|
|
277
181
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
278
182
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
/** Tag-only view of {@link AllocatableResource} for places that just need the variant name. */
|
|
287
|
-
export type AllocatableResourceTag = AllocatableResource["tag"];
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Per-resource outcome from {@link requestResourceAllocation}.
|
|
291
|
-
* The host strips secret payloads from `Allocated` before returning, so
|
|
292
|
-
* `value` is always `undefined` on the product side.
|
|
293
|
-
*/
|
|
294
|
-
export type AllocationOutcome = CodecType<typeof AllocationOutcomeCodec>;
|
|
295
|
-
|
|
296
|
-
/** Tag-only view of {@link AllocationOutcome} (`"Allocated" | "Rejected" | "NotAvailable"`). */
|
|
297
|
-
export type AllocationOutcomeTag = AllocationOutcome["tag"];
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Remote permission the dapp can ask the host to grant via
|
|
301
|
-
* {@link requestPermission}.
|
|
302
|
-
*
|
|
303
|
-
* Derived from the upstream codec so variant renames surface as compile
|
|
304
|
-
* errors, not runtime failures.
|
|
305
|
-
*/
|
|
306
|
-
export type RemotePermission = CodecType<typeof RemotePermissionCodec>;
|
|
307
|
-
|
|
308
|
-
/** Tag-only view of {@link RemotePermission}. */
|
|
309
|
-
export type RemotePermissionTag = RemotePermission["tag"];
|
|
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
|
+
// - `AllocationOutcome` — per-resource outcome, the string union
|
|
187
|
+
// `"Allocated" | "Rejected" | "NotAvailable"` (RFC-10).
|
|
188
|
+
// - `RemotePermission` — permission the dapp asks the host to grant via `requestPermission`.
|
|
189
|
+
export type { AllocatableResource, AllocationOutcome, RemotePermission };
|
|
310
190
|
|
|
311
191
|
/**
|
|
312
192
|
* Request the host to pre-allocate one or more resource allowances.
|
|
@@ -315,34 +195,30 @@ export type RemotePermissionTag = RemotePermission["tag"];
|
|
|
315
195
|
* granted allowance don't re-prompt.
|
|
316
196
|
*
|
|
317
197
|
* @param resources - Resources to request.
|
|
318
|
-
* @returns
|
|
319
|
-
*
|
|
198
|
+
* @returns `ok` with per-resource outcomes in the same order as `resources`, or
|
|
199
|
+
* `err(HostUnavailableError | HostCallFailedError)`.
|
|
320
200
|
*
|
|
321
201
|
* @example
|
|
322
202
|
* ```ts
|
|
323
|
-
* const
|
|
203
|
+
* const r = await requestResourceAllocation([
|
|
324
204
|
* { tag: "BulletinAllowance", value: undefined },
|
|
325
205
|
* ]);
|
|
326
|
-
* if (
|
|
206
|
+
* if (r.ok && r.value[0] === "Allocated") { ... }
|
|
327
207
|
* ```
|
|
328
208
|
*/
|
|
329
209
|
export async function requestResourceAllocation(
|
|
330
210
|
resources: AllocatableResource[],
|
|
331
|
-
): Promise<AllocationOutcome[]
|
|
211
|
+
): Promise<Result<AllocationOutcome[], HostError>> {
|
|
332
212
|
const truApi = await getTruApi();
|
|
333
213
|
if (!truApi) {
|
|
334
|
-
|
|
214
|
+
return err(new HostUnavailableError("requestResourceAllocation: TruAPI unavailable"));
|
|
335
215
|
}
|
|
336
216
|
log.debug("requestResourceAllocation", { resources: resources.map((r) => r.tag) });
|
|
337
217
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
(
|
|
341
|
-
|
|
342
|
-
throw new Error(`requestResourceAllocation failed: ${formatHostError(err)}`, {
|
|
343
|
-
cause: err,
|
|
344
|
-
});
|
|
345
|
-
},
|
|
218
|
+
return mapHostResult(
|
|
219
|
+
truApi.resourceAllocation.request({ resources }),
|
|
220
|
+
(response) => response.outcomes,
|
|
221
|
+
"requestResourceAllocation failed",
|
|
346
222
|
);
|
|
347
223
|
}
|
|
348
224
|
|
|
@@ -351,105 +227,33 @@ export async function requestResourceAllocation(
|
|
|
351
227
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
352
228
|
|
|
353
229
|
/**
|
|
354
|
-
* Have the host sign a Statement using
|
|
355
|
-
* picks internally — RFC-10 §"Statement Store allowance".
|
|
356
|
-
*
|
|
357
|
-
* The product passes only the Statement payload; the host chooses the
|
|
358
|
-
* `//allowance//statement-store//{productId}` account that holds SSS
|
|
359
|
-
* allowance and signs with it. Allowance is provisioned implicitly on
|
|
360
|
-
* first use if the host hasn't already pre-allocated via
|
|
361
|
-
* {@link requestResourceAllocation}; products never see the signing
|
|
362
|
-
* account or its key material.
|
|
230
|
+
* Have the host sign a Statement using the product's allowance-bearing account,
|
|
231
|
+
* which it picks internally — RFC-10 §"Statement Store allowance". No per-call
|
|
232
|
+
* account id is needed (this is the sponsored-submission path).
|
|
363
233
|
*
|
|
364
|
-
* Pairs with {@link getStatementStore}'s `submit`: call this to obtain
|
|
365
|
-
*
|
|
234
|
+
* Pairs with {@link getStatementStore}'s `submit`: call this to obtain a proof,
|
|
235
|
+
* attach it to the Statement, and submit the result.
|
|
366
236
|
*
|
|
367
237
|
* @param statement - The Statement to be signed.
|
|
368
|
-
* @returns
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
* @example
|
|
372
|
-
* ```ts
|
|
373
|
-
* import { createProofAuthorized, getStatementStore } from "@parity/product-sdk-host";
|
|
374
|
-
*
|
|
375
|
-
* const statement = {
|
|
376
|
-
* proof: undefined,
|
|
377
|
-
* decryptionKey: undefined,
|
|
378
|
-
* expiry: undefined,
|
|
379
|
-
* channel: undefined,
|
|
380
|
-
* topics: [],
|
|
381
|
-
* data: payload,
|
|
382
|
-
* };
|
|
383
|
-
* const proof = await createProofAuthorized(statement);
|
|
384
|
-
* const store = await getStatementStore();
|
|
385
|
-
* await store?.submit({ ...statement, proof });
|
|
386
|
-
* ```
|
|
387
|
-
*
|
|
388
|
-
* @remarks
|
|
389
|
-
* RFC-10 introduces this as a new, strictly additive TruAPI call. The
|
|
390
|
-
* pre-existing `HostStatementStore.createProof(accountId, statement)`
|
|
391
|
-
* surface stays available for products that own a non-allowance signing
|
|
392
|
-
* account; this wrapper is the sponsored-submission path.
|
|
238
|
+
* @returns `ok` with the proof to attach before submitting, or
|
|
239
|
+
* `err(HostUnavailableError | HostCallFailedError)`.
|
|
393
240
|
*/
|
|
394
|
-
export async function createProofAuthorized(
|
|
241
|
+
export async function createProofAuthorized(
|
|
242
|
+
statement: Statement,
|
|
243
|
+
): Promise<Result<StatementProof, HostError>> {
|
|
395
244
|
const truApi = await getTruApi();
|
|
396
245
|
if (!truApi) {
|
|
397
|
-
|
|
246
|
+
return err(new HostUnavailableError("createProofAuthorized: TruAPI unavailable"));
|
|
398
247
|
}
|
|
399
|
-
log.debug("createProofAuthorized", {
|
|
400
|
-
topics: statement.topics.length,
|
|
401
|
-
dataLen: statement.data?.length ?? 0,
|
|
402
|
-
});
|
|
248
|
+
log.debug("createProofAuthorized", { topics: statement.topics.length });
|
|
403
249
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
(
|
|
407
|
-
|
|
408
|
-
throw new Error(`createProofAuthorized failed: ${formatHostError(err)}`, {
|
|
409
|
-
cause: err,
|
|
410
|
-
});
|
|
411
|
-
},
|
|
250
|
+
return mapHostResult(
|
|
251
|
+
truApi.statementStore.createProofAuthorized(statement),
|
|
252
|
+
(response) => response.proof,
|
|
253
|
+
"createProofAuthorized failed",
|
|
412
254
|
);
|
|
413
255
|
}
|
|
414
256
|
|
|
415
|
-
/**
|
|
416
|
-
* One of the user's existing wallet accounts, surfaced through the host and
|
|
417
|
-
* identified by its public key and an optional name. Contrast with
|
|
418
|
-
* {@link ProductAccount}, which is also user-controlled but derived by the
|
|
419
|
-
* host for a specific app rather than picked from the user's existing keys.
|
|
420
|
-
*/
|
|
421
|
-
export interface HostAccount {
|
|
422
|
-
publicKey: Uint8Array;
|
|
423
|
-
name?: string;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* A product account — an app-scoped derived account managed by the host wallet.
|
|
428
|
-
*
|
|
429
|
-
* The host derives a unique keypair for each app (identified by `dotNsIdentifier`)
|
|
430
|
-
* so apps get their own account that the user controls but is scoped to the app.
|
|
431
|
-
*/
|
|
432
|
-
export interface ProductAccount {
|
|
433
|
-
/** App identifier (e.g., "mark3t.dot"). */
|
|
434
|
-
dotNsIdentifier: string;
|
|
435
|
-
/** Derivation index within the app scope. Default: 0 */
|
|
436
|
-
derivationIndex: number;
|
|
437
|
-
/** Raw public key (32 bytes). */
|
|
438
|
-
publicKey: Uint8Array;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* A contextual alias obtained from Ring VRF.
|
|
443
|
-
*
|
|
444
|
-
* Proves account membership in a ring without revealing which account.
|
|
445
|
-
*/
|
|
446
|
-
export interface ContextualAlias {
|
|
447
|
-
/** Ring context (32 bytes). */
|
|
448
|
-
context: Uint8Array;
|
|
449
|
-
/** The Ring VRF alias bytes. */
|
|
450
|
-
alias: Uint8Array;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
257
|
/**
|
|
454
258
|
* Neverthrow-style ResultAsync returned by product-sdk methods.
|
|
455
259
|
*
|
|
@@ -459,18 +263,6 @@ export interface ResultAsync<T, E> {
|
|
|
459
263
|
match: <A, B = A>(ok: (t: T) => A, err: (e: E) => B) => Promise<A | B>;
|
|
460
264
|
}
|
|
461
265
|
|
|
462
|
-
/**
|
|
463
|
-
* Accounts provider handle from `@novasamatech/host-api-wrapper`. Surfaces the
|
|
464
|
-
* full upstream API - host wallet accounts, app-scoped product accounts,
|
|
465
|
-
* Ring VRF, user identity (`getUserId`, `requestLogin`), and connection
|
|
466
|
-
* status subscription.
|
|
467
|
-
*
|
|
468
|
-
* Type identical to `createAccountsProvider()` from
|
|
469
|
-
* `@novasamatech/host-api-wrapper`; methods return neverthrow `ResultAsync`
|
|
470
|
-
* values with typed `CodecError` variants in the error channel.
|
|
471
|
-
*/
|
|
472
|
-
export type AccountsProvider = ReturnType<typeof createAccountsProvider>;
|
|
473
|
-
|
|
474
266
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
475
267
|
// Tests
|
|
476
268
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -478,17 +270,13 @@ export type AccountsProvider = ReturnType<typeof createAccountsProvider>;
|
|
|
478
270
|
if (import.meta.vitest) {
|
|
479
271
|
const { test, expect } = import.meta.vitest;
|
|
480
272
|
|
|
481
|
-
test("getTruApi returns
|
|
482
|
-
// Reset cache for test
|
|
483
|
-
cachedTruApi = null;
|
|
273
|
+
test("getTruApi returns null outside a container", async () => {
|
|
484
274
|
const api = await getTruApi();
|
|
485
|
-
// In dev/test mode, product-sdk is installed
|
|
486
275
|
expect(api === null || typeof api === "object").toBe(true);
|
|
487
276
|
});
|
|
488
277
|
|
|
489
|
-
test("getPreimageManager returns manager
|
|
278
|
+
test("getPreimageManager returns manager or null", async () => {
|
|
490
279
|
const manager = await getPreimageManager();
|
|
491
|
-
// In dev/test mode, product-sdk is installed
|
|
492
280
|
expect(manager === null || typeof manager === "object").toBe(true);
|
|
493
281
|
});
|
|
494
282
|
|
|
@@ -496,58 +284,27 @@ if (import.meta.vitest) {
|
|
|
496
284
|
expect(await createHostPreimageManager()).toBeNull();
|
|
497
285
|
});
|
|
498
286
|
|
|
499
|
-
test("
|
|
500
|
-
expect(
|
|
501
|
-
|
|
502
|
-
tag: "v1",
|
|
503
|
-
value: { name: "GenericError", message: "boom" },
|
|
504
|
-
}),
|
|
505
|
-
).toBe("GenericError: boom");
|
|
506
|
-
expect(formatHostError(new Error("plain"))).toBe("plain");
|
|
507
|
-
expect(formatHostError("string err")).toBe("string err");
|
|
508
|
-
expect(formatHostError({ tag: "v1", value: { message: "no-name" } })).toBe("no-name");
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
test("getAccountsProvider returns provider when SDK is available", async () => {
|
|
512
|
-
// In dev/test mode, product-sdk is installed, so this returns a provider
|
|
513
|
-
const provider = await getAccountsProvider();
|
|
514
|
-
// Just verify it returns something (null when SDK unavailable, provider when available)
|
|
515
|
-
expect(provider === null || typeof provider === "object").toBe(true);
|
|
287
|
+
test("hex helpers", () => {
|
|
288
|
+
expect(toHex(new Uint8Array([0xde, 0xad]))).toBe("0xdead");
|
|
289
|
+
expect(Array.from(fromHex("0xdead"))).toEqual([0xde, 0xad]);
|
|
516
290
|
});
|
|
517
291
|
|
|
518
|
-
test("
|
|
519
|
-
const { enumValue } = await import("./truapi.js");
|
|
520
|
-
expect(typeof enumValue).toBe("function");
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
test("requestResourceAllocation throws when TruAPI is unavailable", async () => {
|
|
524
|
-
cachedTruApi = null;
|
|
292
|
+
test("requestResourceAllocation returns err when TruAPI is unavailable", async () => {
|
|
525
293
|
const api = await getTruApi();
|
|
526
294
|
if (api === null) {
|
|
527
|
-
await
|
|
528
|
-
|
|
529
|
-
)
|
|
295
|
+
const result = await requestResourceAllocation([
|
|
296
|
+
{ tag: "BulletinAllowance", value: undefined },
|
|
297
|
+
]);
|
|
298
|
+
expect(result.ok).toBe(false);
|
|
299
|
+
if (!result.ok) {
|
|
300
|
+
expect(result.error).toBeInstanceOf(HostUnavailableError);
|
|
301
|
+
}
|
|
530
302
|
} else {
|
|
531
303
|
expect(typeof requestResourceAllocation).toBe("function");
|
|
532
304
|
}
|
|
533
305
|
});
|
|
534
306
|
|
|
535
|
-
test("createProofAuthorized
|
|
536
|
-
|
|
537
|
-
const api = await getTruApi();
|
|
538
|
-
if (api === null) {
|
|
539
|
-
await expect(
|
|
540
|
-
createProofAuthorized({
|
|
541
|
-
proof: undefined,
|
|
542
|
-
decryptionKey: undefined,
|
|
543
|
-
expiry: undefined,
|
|
544
|
-
channel: undefined,
|
|
545
|
-
topics: [],
|
|
546
|
-
data: undefined,
|
|
547
|
-
}),
|
|
548
|
-
).rejects.toThrow(/TruAPI unavailable/);
|
|
549
|
-
} else {
|
|
550
|
-
expect(typeof createProofAuthorized).toBe("function");
|
|
551
|
-
}
|
|
307
|
+
test("createProofAuthorized is callable", () => {
|
|
308
|
+
expect(typeof createProofAuthorized).toBe("function");
|
|
552
309
|
});
|
|
553
310
|
}
|