@parity/product-sdk-terminal 0.2.1 → 0.3.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/README.md CHANGED
@@ -16,22 +16,7 @@ pnpm add @parity/product-sdk-terminal
16
16
 
17
17
  **Requires Node ≥21.** The package relies on the global `WebSocket` exposed by Node 21+ (via `@polkadot-api/ws-provider@0.9`). On older Node versions the WebSocket connection fails at runtime with `WebSocket is not defined`.
18
18
 
19
- **Register the WASM loader** — the host-papp SDK depends on `verifiablejs` which uses inline WASM (browser-only). The register hook redirects it to the Node.js WASM build. Pass it via `--import`:
20
-
21
- ```bash
22
- node --import @parity/product-sdk-terminal/register app.js
23
- tsx --import @parity/product-sdk-terminal/register app.ts
24
- ```
25
-
26
- Or in your `package.json` scripts:
27
-
28
- ```json
29
- {
30
- "scripts": {
31
- "start": "tsx --import @parity/product-sdk-terminal/register index.ts"
32
- }
33
- }
34
- ```
19
+ > **Note (≥0.3):** The `@parity/product-sdk-terminal/register` WASM loader hook has been removed. It existed only to patch `verifiablejs` (a browser-only inline-WASM dependency of `@novasamatech/host-papp`). host-papp ≥0.7.9 no longer uses `verifiablejs` — its sr25519 primitives are pure JS — so no loader hook is needed. Drop any `--import @parity/product-sdk-terminal/register` flags from your `node`/`tsx` invocations.
35
20
 
36
21
  ## Quick Start
37
22
 
@@ -41,7 +26,6 @@ import { createTerminalAdapter, renderQrCode, waitForSessions } from "@parity/pr
41
26
  // 1. Create the adapter
42
27
  const adapter = createTerminalAdapter({
43
28
  appId: "my-terminal-app",
44
- metadataUrl: "https://example.com/metadata.json",
45
29
  });
46
30
 
47
31
  // 2. Subscribe to pairing status to show the QR code
@@ -79,7 +63,6 @@ Creates a terminal adapter backed by the host-papp SDK.
79
63
 
80
64
  **Options:**
81
65
  - `appId` -- unique app identifier (used as storage namespace)
82
- - `metadataUrl` -- URL to metadata JSON shown during pairing
83
66
  - `endpoints?` -- statement store WebSocket endpoints (defaults to Paseo)
84
67
  - `hostMetadata?` -- optional host environment info
85
68
  - `storageDir?` -- override the on-disk session directory (defaults to `~/.polkadot-apps/`). Useful in tests and containerised environments.
@@ -129,6 +112,44 @@ File-based storage adapter for Node.js. Data persists in `storageDir` (defaults
129
112
 
130
113
  Waits for the session list to emit at least one entry, or resolves with `[]` after `timeoutMs`.
131
114
 
115
+ ## Allowance management (`./host` subpath)
116
+
117
+ For CLIs that need to write to Bulletin / Statement Store / Asset Hub smart contracts: the QR-paired mobile wallet is the Account Holder, the CLI plays the Host role, and `@parity/product-sdk-terminal/host` exposes that Host-runner surface (the [RFC-10](https://github.com/paritytech/triangle-js-sdks/blob/valentunn-rfc-0010/docs/rfcs/0010-allowance.md) Accounts Protocol companion, an on-disk allowance-key cache, and a local sr25519 signer over the cached keys).
118
+
119
+ ```ts
120
+ import {
121
+ ensureSlotAccountSigner,
122
+ requestResourceAllocation,
123
+ getCachedAllocation,
124
+ createSlotAccountSigner,
125
+ } from "@parity/product-sdk-terminal/host";
126
+
127
+ // First call prompts the wallet; subsequent calls are wire-free.
128
+ const signer = await ensureSlotAccountSigner(session, adapter, {
129
+ tag: "BulletInAllowance",
130
+ value: undefined,
131
+ });
132
+ await bulletinTx.submitAndWatch(signer);
133
+ ```
134
+
135
+ ### `ensureSlotAccountSigner(session, adapter, resource): Promise<PolkadotSigner>`
136
+
137
+ The one call most CLIs want. Cache hit → returns signer; cache miss → allocates via the wallet, then returns signer. Throws on `Rejected` / `NotAvailable`. Slot-table resources only (`BulletInAllowance`, `StatementStoreAllowance`).
138
+
139
+ ### `requestResourceAllocation(session, adapter, resources, options?): Promise<ApAllocationOutcome[]>`
140
+
141
+ Lower-level: pre-allocate any combination of resources. Granted keys cached at `{storageDir}/{appId}_AllowanceKeys.json` (`0o600`, atomic write). `onExisting` auto-picks `Ignore`/`Increase` based on cache state; override via `options.onExisting`.
142
+
143
+ ### `getCachedAllocation(adapter, resource): Promise<CachedAllocation | null>`
144
+
145
+ Read-only cache lookup; no wire round-trip.
146
+
147
+ ### `createSlotAccountSigner(adapter, resource): Promise<PolkadotSigner | null>`
148
+
149
+ Build a `PolkadotSigner` from the cached slot key without round-tripping. Returns `null` if nothing's cached (use `ensureSlotAccountSigner` for the round-trip-if-missing flow).
150
+
151
+ > **Caveats.** `AutoSigning` currently returns `NotAvailable` on both Android and iOS wallets. Slot renewal at expiry depends on [paritytech/individuality#931](https://github.com/paritytech/individuality/pull/931) (open) — first-time allocation works end-to-end today.
152
+
132
153
  ## Migration from `@polkadot-apps/terminal`
133
154
 
134
155
  For consumers moving from `@polkadot-apps/terminal` v0.2.0 / v0.3.0. Existing sessions on disk (`~/.polkadot-apps/`) carry over — same `appId`, same path. No re-pairing required for the migration itself.
@@ -136,7 +157,7 @@ For consumers moving from `@polkadot-apps/terminal` v0.2.0 / v0.3.0. Existing se
136
157
  | Concern | `@polkadot-apps/terminal` | `@parity/product-sdk-terminal` |
137
158
  | --- | --- | --- |
138
159
  | Package name | `@polkadot-apps/terminal` | `@parity/product-sdk-terminal` |
139
- | Register import path | `--import @polkadot-apps/terminal/register` | `--import @parity/product-sdk-terminal/register` |
160
+ | WASM loader hook | `--import @polkadot-apps/terminal/register` (required) | **removed** — no `--import` needed (host-papp ≥0.7.9 dropped `verifiablejs`) |
140
161
  | `createTerminalAdapter` | `async` — returned `Promise<TerminalAdapter>` | **sync** — returns `TerminalAdapter` directly. Drop the `await`. |
141
162
  | Default account signer | `createSessionSigner(session)` | `createSessionSigner(session, adapter)` — pass the adapter as second arg |
142
163
  | Non-default sub-account signer | not exposed | `createSessionSignerForAccount(session, { productId, derivationIndex })` |
@@ -157,7 +178,7 @@ The single-argument `createSessionSigner(session)` from `@polkadot-apps/terminal
157
178
  ### Migration steps
158
179
 
159
180
  1. **Replace the dep**: `pnpm remove @polkadot-apps/terminal && pnpm add @parity/product-sdk-terminal`
160
- 2. **Update the `--import` flag** in your `node` / `tsx` invocations or `package.json` scripts.
181
+ 2. **Remove the `--import @.../register` flag** from your `node` / `tsx` invocations or `package.json` scripts — the WASM loader hook no longer exists (and is no longer needed).
161
182
  3. **Drop `await`** in front of `createTerminalAdapter(...)` calls.
162
183
  4. **Update each `createSessionSigner` call site**: change `createSessionSigner(session)` → `createSessionSigner(session, adapter)`.
163
184
  5. **Verify Node version** is ≥21 (`node --version`).
@@ -183,7 +204,6 @@ const { sessionId } = await createTestSession({
183
204
 
184
205
  const adapter = createTerminalAdapter({
185
206
  appId: "my-terminal-app",
186
- metadataUrl: "https://example.com/metadata.json",
187
207
  storageDir,
188
208
  });
189
209
  const sessions = await waitForSessions(adapter);
@@ -214,16 +234,6 @@ The adapter uses `@polkadot-api/ws-provider@0.9`, which relies on the global `We
214
234
 
215
235
  The default WebSocket is constructed without `followRedirects: true`, so endpoints behind an HTTP redirect will fail to connect. If you must point at an endpoint that does, supply the resolved URL directly via the `endpoints` option.
216
236
 
217
- ### `ExperimentalWarning: Importing WebAssembly module instances`
218
-
219
- You'll see this warning at startup:
220
-
221
- ```
222
- (node:NNNNN) ExperimentalWarning: Importing WebAssembly module instances is an experimental feature and might change at any time
223
- ```
224
-
225
- It's emitted by Node when the loader hook imports the `verifiablejs` WASM. Harmless. To silence it, run with `--no-warnings=ExperimentalWarning`.
226
-
227
237
  ## How It Works
228
238
 
229
239
  1. **QR Pairing** -- generates Sr25519 + P256 keypairs, encodes a `polkadotapp://pair?handshake=0x...` deep link, subscribes to the statement store
@@ -243,20 +253,6 @@ Sessions are persisted to `~/.polkadot-apps/` and survive across restarts. The S
243
253
 
244
254
  ## Future Work
245
255
 
246
- - **`KvStore`↔`StorageAdapter` bridge.** This package implements its own file-backed `StorageAdapter` for Node.js (`createNodeStorageAdapter`). Once `@parity/product-sdk-storage` grows a file backend with the same `read/write/clear/subscribe` `ResultAsync` shape, replace `node-storage.ts` with a thin adapter over it.
256
+ - **`KvStore`↔`StorageAdapter` bridge.** This package implements its own file-backed `StorageAdapter` for Node.js (`createNodeStorageAdapter`). Once `@parity/product-sdk-local-storage` grows a file backend with the same `read/write/clear/subscribe` `ResultAsync` shape, replace `node-storage.ts` with a thin adapter over it.
247
257
  - **Codec re-exports from `@parity/product-sdk-statement-store`.** `testing.ts` imports session-account codec helpers (`AccountIdCodec`, `LocalSessionAccountCodec`, etc.) directly from `@novasamatech/statement-store`. Re-exporting them through the in-monorepo wrapper would let this package depend only on workspace siblings.
248
- - **Embedded host runner for allowance / attestation refresh.** Today this package consumes a paired session and signs against it, but cannot renew the on-chain attestation that gates allowance writes — once it expires the user has to re-do the full QR pairing. The proposed fix is a new `./host` sub-export (in addition to `.`, `./register`, `./testing`) exposing roughly:
249
- ```ts
250
- // proposed shape — not yet implemented
251
- export interface AllowanceManager {
252
- isExpired(): boolean;
253
- refresh(): ResultAsync<void, Error>;
254
- currentAttestation(): ResultAsync<Attestation, Error>;
255
- }
256
- export function createAllowanceManager(
257
- adapter: TerminalAdapter,
258
- options?: { hostEndpoint?: string },
259
- ): AllowanceManager;
260
- ```
261
- Implementation should sit on top of `@parity/product-sdk-host`'s container/storage primitives so the host runner is shared with browser/desktop hosts rather than being CLI-specific. This is the gap Tarik flagged as "the CLI might also need to run some kind of host to get allowances".
262
258
  - **`@noble/*` major version drift.** This package pins `@noble/{ciphers,curves,hashes}: ^2.x` because upstream `@polkadot-apps/terminal` did, and the `testing.ts` codec helpers use the v2 import paths (`@noble/hashes/blake2.js`, `@noble/curves/nist.js`). The rest of the monorepo is on `^1.x`. Both majors coexist in the lockfile; not a runtime problem today but worth a coordinated bump. Either move the whole monorepo to v2, or rewrite `testing.ts` against v1 paths (`@noble/hashes/blake2b.js` etc.).
@@ -0,0 +1,72 @@
1
+ import { PappAdapter, HostMetadata } from '@novasamatech/host-papp';
2
+
3
+ /**
4
+ * Node.js adapter for the Polkadot host-papp SDK.
5
+ *
6
+ * Provides Node.js-compatible implementations of the SDK's storage and
7
+ * transport layers, enabling QR login, attestation, and signing in
8
+ * terminal/CLI environments.
9
+ */
10
+
11
+ /** Options for creating a terminal adapter. */
12
+ interface TerminalAdapterOptions {
13
+ /** Unique app identifier. Used as the storage namespace. */
14
+ appId: string;
15
+ /** Statement store WebSocket endpoints. Defaults to Paseo stable endpoints. */
16
+ endpoints?: string[];
17
+ /** Optional host metadata for the Sign-In screen. */
18
+ hostMetadata?: HostMetadata;
19
+ /**
20
+ * Directory where session files are persisted. Defaults to
21
+ * `~/.polkadot-apps/`. Override in tests to point at a temporary
22
+ * directory populated with `createTestSession` from
23
+ * `@parity/product-sdk-terminal/testing`.
24
+ */
25
+ storageDir?: string;
26
+ }
27
+ /**
28
+ * Create a terminal adapter backed by the host-papp SDK.
29
+ *
30
+ * This sets up:
31
+ * - File-based storage in `~/.polkadot-apps/` (since Node.js has no localStorage)
32
+ * - WebSocket connection to the statement store
33
+ * - The full SSO flow: QR pairing + on-chain attestation
34
+ * - Session manager for signing requests
35
+ */
36
+ /** A PappAdapter with the `appId` it was created with and a `destroy` method for cleanup. */
37
+ type TerminalAdapter = PappAdapter & {
38
+ /** The `appId` passed to {@link createTerminalAdapter}. Useful for {@link createSessionSigner}. */
39
+ readonly appId: string;
40
+ /**
41
+ * The on-disk storage directory used for sessions and (when the
42
+ * host-runner facet is in use) for the allowance-key cache the
43
+ * `./host` subpath maintains. `undefined` when the default
44
+ * is in use; set explicitly when the caller passed `storageDir` to
45
+ * {@link createTerminalAdapter}.
46
+ */
47
+ readonly storageDir?: string;
48
+ /**
49
+ * Disconnect the WebSocket and release resources.
50
+ *
51
+ * @remarks
52
+ * Idempotent. Returns a Promise that resolves once all in-flight
53
+ * statement-subscription teardowns have settled and the underlying
54
+ * substrate client has been disconnected. **Awaiting is recommended
55
+ * but not required** — callers that don't await get the same
56
+ * fire-and-forget shape the previous version had, but they may see
57
+ * the destroy-time RPC traffic finish after the function returns.
58
+ *
59
+ * The implementation tracks the server-side `statement_unsubscribe…`
60
+ * RPCs `sessions.dispose()` fires, then awaits them via
61
+ * `Promise.allSettled` before destroying the substrate-client request
62
+ * queue. No timing-based guesses; no global-state mutations of
63
+ * `console.error` or `process.on('unhandledRejection')`. Pending
64
+ * subscribes (where `onSuccess` hasn't fired yet) are cancelled by
65
+ * the underlying `getSubscribeFn` teardown via `cancelRequest()`,
66
+ * which is the in-band fast-path and doesn't surface as a rejection.
67
+ */
68
+ destroy(): Promise<void>;
69
+ };
70
+ declare function createTerminalAdapter(options: TerminalAdapterOptions): TerminalAdapter;
71
+
72
+ export { type TerminalAdapter as T, type TerminalAdapterOptions as a, createTerminalAdapter as c };
@@ -207,4 +207,4 @@ export {
207
207
  sanitizeKey,
208
208
  createNodeStorageAdapter
209
209
  };
210
- //# sourceMappingURL=chunk-5MCLSFKQ.js.map
210
+ //# sourceMappingURL=chunk-GHOFKVUG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/node-storage.ts"],"sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * File-based StorageAdapter for Node.js environments.\n *\n * Implements the @novasamatech/storage-adapter interface using JSON files\n * in ~/.polkadot-apps/. Node.js doesn't have localStorage, so this\n * provides persistent storage for the SDK's session and secret data.\n */\nimport type { StorageAdapter } from \"@novasamatech/storage-adapter\";\nimport { createLogger } from \"@parity/product-sdk-logger\";\nimport { fromPromise } from \"neverthrow\";\nimport { join } from \"node:path\";\nimport { readFile, writeFile, mkdir, unlink } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\n\nconst log = createLogger(\"terminal\");\nconst DEFAULT_STORAGE_DIR = join(homedir(), \".polkadot-apps\");\n\n/**\n * Compute the storage filename for a given appId+key pair.\n *\n * Exposed (rather than kept private) so the test-session helper in\n * `./testing.ts` can target the same file the live adapter reads from\n * without having to duplicate the sanitization rule.\n */\nexport function sanitizeKey(appId: string, key: string): string {\n return `${appId}_${key}`.replace(/[^a-zA-Z0-9_.-]/g, \"_\");\n}\n\nfunction toError(e: unknown): Error {\n return e instanceof Error ? e : new Error(String(e));\n}\n\n/**\n * Create a file-based StorageAdapter for use with the host-papp SDK in Node.js.\n *\n * Data is stored as individual JSON files in the given directory\n * (defaults to `~/.polkadot-apps/`).\n */\nexport function createNodeStorageAdapter(appId: string, storageDir?: string): StorageAdapter {\n const dir = storageDir ?? DEFAULT_STORAGE_DIR;\n let dirCreated = false;\n const subscribers = new Map<string, Set<(value: string | null) => unknown>>();\n\n function fp(key: string): string {\n return join(dir, `${sanitizeKey(appId, key)}.json`);\n }\n\n async function ensureDir(): Promise<void> {\n if (dirCreated) return;\n await mkdir(dir, { recursive: true });\n dirCreated = true;\n }\n\n function notifySubscribers(key: string, value: string | null) {\n const subs = subscribers.get(key);\n if (subs) {\n for (const cb of subs) {\n try {\n cb(value);\n } catch (e) {\n log.warn(\"storage subscriber callback threw\", { key, error: e });\n }\n }\n }\n }\n\n return {\n read(key: string) {\n return fromPromise(\n readFile(fp(key), \"utf-8\").catch((e) => {\n // Missing files are expected (a missing key reads as null);\n // log at debug so consumers can opt in to seeing reads when\n // diagnosing \"why isn't my session loading\".\n if ((e as NodeJS.ErrnoException).code !== \"ENOENT\") {\n log.warn(\"storage read failed\", { key, error: e });\n } else {\n log.debug(\"storage read miss\", { key });\n }\n return null;\n }),\n toError,\n );\n },\n\n write(key: string, value: string) {\n return fromPromise(\n ensureDir()\n .then(() => writeFile(fp(key), value, \"utf-8\"))\n .then(() => {\n notifySubscribers(key, value);\n }),\n toError,\n ).map(() => undefined as undefined);\n },\n\n clear(key: string) {\n return fromPromise(\n unlink(fp(key))\n .catch(() => {})\n .then(() => {\n notifySubscribers(key, null);\n }),\n toError,\n ).map(() => undefined as undefined);\n },\n\n subscribe(key: string, callback: (value: string | null) => unknown) {\n if (!subscribers.has(key)) {\n subscribers.set(key, new Set());\n }\n subscribers.get(key)!.add(callback);\n return () => {\n subscribers.get(key)?.delete(callback);\n };\n },\n };\n}\n\nif (import.meta.vitest) {\n const { describe, test, expect, beforeEach, afterAll } = import.meta.vitest;\n const { mkdtemp, rm } = await import(\"node:fs/promises\");\n const { tmpdir } = await import(\"node:os\");\n const { configure } = await import(\"@parity/product-sdk-logger\");\n\n let testDir: string;\n\n beforeEach(async () => {\n // Silence the logger so tests that exercise the warn paths don't\n // pollute stderr with expected log output.\n configure({ handler: () => {} });\n testDir = await mkdtemp(join(tmpdir(), \"terminal-storage-test-\"));\n });\n\n afterAll(async () => {\n // Clean up any remaining test dirs\n try {\n await rm(testDir, { recursive: true });\n } catch {\n /* ignore */\n }\n });\n\n describe(\"createNodeStorageAdapter\", () => {\n test(\"read returns null for missing key\", async () => {\n const store = createNodeStorageAdapter(\"test\", testDir);\n const result = await store.read(\"nonexistent\");\n expect(result.isOk()).toBe(true);\n expect(result._unsafeUnwrap()).toBeNull();\n });\n\n test(\"write and read round-trip\", async () => {\n const store = createNodeStorageAdapter(\"test\", testDir);\n await store.write(\"key1\", \"hello\");\n const result = await store.read(\"key1\");\n expect(result._unsafeUnwrap()).toBe(\"hello\");\n });\n\n test(\"write overwrites existing value\", async () => {\n const store = createNodeStorageAdapter(\"test\", testDir);\n await store.write(\"key1\", \"first\");\n await store.write(\"key1\", \"second\");\n const result = await store.read(\"key1\");\n expect(result._unsafeUnwrap()).toBe(\"second\");\n });\n\n test(\"clear removes key\", async () => {\n const store = createNodeStorageAdapter(\"test\", testDir);\n await store.write(\"key1\", \"value\");\n await store.clear(\"key1\");\n const result = await store.read(\"key1\");\n expect(result._unsafeUnwrap()).toBeNull();\n });\n\n test(\"clear is safe for missing key\", async () => {\n const store = createNodeStorageAdapter(\"test\", testDir);\n const result = await store.clear(\"nonexistent\");\n expect(result.isOk()).toBe(true);\n });\n\n test(\"different appIds are isolated\", async () => {\n const storeA = createNodeStorageAdapter(\"app-a\", testDir);\n const storeB = createNodeStorageAdapter(\"app-b\", testDir);\n await storeA.write(\"key\", \"from-a\");\n await storeB.write(\"key\", \"from-b\");\n expect((await storeA.read(\"key\"))._unsafeUnwrap()).toBe(\"from-a\");\n expect((await storeB.read(\"key\"))._unsafeUnwrap()).toBe(\"from-b\");\n });\n\n test(\"subscribe notifies on write\", async () => {\n const store = createNodeStorageAdapter(\"test\", testDir);\n const values: (string | null)[] = [];\n store.subscribe(\"key1\", (v) => values.push(v));\n\n await store.write(\"key1\", \"hello\");\n expect(values).toEqual([\"hello\"]);\n });\n\n test(\"subscribe notifies on clear\", async () => {\n const store = createNodeStorageAdapter(\"test\", testDir);\n const values: (string | null)[] = [];\n await store.write(\"key1\", \"hello\");\n store.subscribe(\"key1\", (v) => values.push(v));\n\n await store.clear(\"key1\");\n expect(values).toEqual([null]);\n });\n\n test(\"unsubscribe stops notifications\", async () => {\n const store = createNodeStorageAdapter(\"test\", testDir);\n const values: (string | null)[] = [];\n const unsub = store.subscribe(\"key1\", (v) => values.push(v));\n\n await store.write(\"key1\", \"first\");\n unsub();\n await store.write(\"key1\", \"second\");\n\n expect(values).toEqual([\"first\"]);\n });\n\n test(\"subscriber errors do not break other subscribers\", async () => {\n const store = createNodeStorageAdapter(\"test\", testDir);\n const values: string[] = [];\n store.subscribe(\"key1\", () => {\n throw new Error(\"boom\");\n });\n store.subscribe(\"key1\", (v) => {\n if (v) values.push(v);\n });\n\n await store.write(\"key1\", \"hello\");\n expect(values).toEqual([\"hello\"]);\n });\n\n test(\"sanitizes special characters in keys\", async () => {\n const store = createNodeStorageAdapter(\"test\", testDir);\n await store.write(\"key/with:special chars!\", \"value\");\n const result = await store.read(\"key/with:special chars!\");\n expect(result._unsafeUnwrap()).toBe(\"value\");\n });\n\n test(\"handles JSON values\", async () => {\n const store = createNodeStorageAdapter(\"test\", testDir);\n const obj = { name: \"test\", count: 42, nested: { ok: true } };\n await store.write(\"json\", JSON.stringify(obj));\n const raw = (await store.read(\"json\"))._unsafeUnwrap();\n expect(JSON.parse(raw!)).toEqual(obj);\n });\n });\n\n describe(\"toError\", () => {\n test(\"returns Error instances unchanged\", () => {\n const original = new TypeError(\"boom\");\n expect(toError(original)).toBe(original);\n });\n\n test(\"wraps non-Error string values\", () => {\n const result = toError(\"primitive failure\");\n expect(result).toBeInstanceOf(Error);\n expect(result.message).toBe(\"primitive failure\");\n });\n\n test(\"wraps non-Error nullish values\", () => {\n const result = toError(null);\n expect(result).toBeInstanceOf(Error);\n expect(result.message).toBe(\"null\");\n });\n });\n}\n"],"mappings":"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","names":[]}
package/dist/host.d.ts ADDED
@@ -0,0 +1,113 @@
1
+ import { UserSession } from '@novasamatech/host-papp';
2
+ import { ResultAsync } from 'neverthrow';
3
+ import { PolkadotSigner } from 'polkadot-api';
4
+ import { T as TerminalAdapter } from './adapter-Dt3ryKnw.js';
5
+
6
+ /** One cached allowance entry. Hex strings are 0x-prefixed. */
7
+ type CachedAllocation = {
8
+ tag: "BulletInAllowance";
9
+ slotAccountKey: string;
10
+ } | {
11
+ tag: "StatementStoreAllowance";
12
+ slotAccountKey: string;
13
+ } | {
14
+ tag: "SmartContractAllowance";
15
+ dest: number;
16
+ } | {
17
+ tag: "AutoSigning";
18
+ productDerivationSecret: string;
19
+ productRootPrivateKey: string;
20
+ };
21
+
22
+ /**
23
+ * `PolkadotSigner` backed by the cached slot account key. Returns `null`
24
+ * when nothing's cached for `(adapter.appId, resource)`. Throws for SC
25
+ * and AutoSigning (no slot account key — see module docstring).
26
+ *
27
+ * Signs locally with sr25519 — no wallet round-trip on the hot path.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const signer = await createSlotAccountSigner(adapter, {
32
+ * tag: "BulletInAllowance", value: undefined,
33
+ * });
34
+ * if (signer) await tx.submitAndWatch(signer);
35
+ * ```
36
+ */
37
+ declare function createSlotAccountSigner(adapter: TerminalAdapter, resource: AllocatableResource): Promise<PolkadotSigner | null>;
38
+
39
+ /**
40
+ * Host-runner facet of `@parity/product-sdk-terminal`. A CLI using this
41
+ * package plays the Host role per RFC-10; this module implements the
42
+ * three §Stakeholders Host responsibilities (AP client, cache, signer)
43
+ * over `@novasamatech/host-papp@0.7.7`'s `UserSession`.
44
+ *
45
+ * @module
46
+ */
47
+
48
+ type ResourceAllocationRequest = Parameters<UserSession["requestResourceAllocation"]>[0];
49
+ /**
50
+ * One resource a Host can request from the Account Holder. AutoSigning
51
+ * currently returns `NotAvailable` on both Android and iOS wallets.
52
+ */
53
+ type AllocatableResource = ResourceAllocationRequest["resources"][number];
54
+ /**
55
+ * `"Ignore"`: return existing keys if any, else allocate one slot.
56
+ * `"Increase"`: add one slot to an existing allowance account.
57
+ */
58
+ type OnExistingAllowancePolicy = ResourceAllocationRequest["onExisting"];
59
+ type ExtractOk<R> = R extends ResultAsync<infer T, unknown> ? T : never;
60
+ /**
61
+ * Per-resource outcome. `Allocated.value` carries the materialized payload
62
+ * (slot account key for Bulletin/SSS; subtree key + secret for AutoSigning;
63
+ * undefined for SC). Each entry is independent — no rollback on partial
64
+ * success.
65
+ */
66
+ type ApAllocationOutcome = ExtractOk<ReturnType<UserSession["requestResourceAllocation"]>>[number];
67
+ interface RequestResourceAllocationOptions {
68
+ /**
69
+ * Override the auto-picked `onExisting`. Default: `Ignore` unless every
70
+ * requested resource is a cached slot-table variant, then `Increase`.
71
+ */
72
+ onExisting?: OnExistingAllowancePolicy;
73
+ }
74
+ /**
75
+ * Send the AP `request_resource_allocation` message over the paired
76
+ * session; block on the user's mobile dialog; return outcomes in request
77
+ * order. Granted key material is cached on disk so subsequent calls skip
78
+ * the wallet prompt.
79
+ *
80
+ * @throws If the session call fails (transport, timeout, AH protocol error).
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * const [session] = adapter.sessions.sessions.read();
85
+ * const outcomes = await requestResourceAllocation(session, adapter, [
86
+ * { tag: "BulletInAllowance", value: undefined },
87
+ * ]);
88
+ * ```
89
+ */
90
+ declare function requestResourceAllocation(session: UserSession, adapter: TerminalAdapter, resources: AllocatableResource[], options?: RequestResourceAllocationOptions): Promise<ApAllocationOutcome[]>;
91
+ /**
92
+ * Read a cached allowance entry without going over the wire. Returns
93
+ * `null` if nothing's cached for `(adapter.appId, resource)`. For
94
+ * `SmartContractAllowance`, the same `dest` must be passed — entries
95
+ * are keyed per-dest.
96
+ */
97
+ declare function getCachedAllocation(adapter: TerminalAdapter, resource: AllocatableResource): Promise<CachedAllocation | null>;
98
+ /**
99
+ * Cache hit → return signer. Cache miss → call
100
+ * {@link requestResourceAllocation} for `[resource]`, then build the signer.
101
+ * Throws on `Rejected` / `NotAvailable`.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * const signer = await ensureSlotAccountSigner(session, adapter, {
106
+ * tag: "BulletInAllowance", value: undefined,
107
+ * });
108
+ * await someBulletinTx.submitAndWatch(signer);
109
+ * ```
110
+ */
111
+ declare function ensureSlotAccountSigner(session: UserSession, adapter: TerminalAdapter, resource: AllocatableResource): Promise<PolkadotSigner>;
112
+
113
+ export { type AllocatableResource, type ApAllocationOutcome, type CachedAllocation, type OnExistingAllowancePolicy, type RequestResourceAllocationOptions, createSlotAccountSigner, ensureSlotAccountSigner, getCachedAllocation, requestResourceAllocation };