@parity/product-sdk-terminal 0.0.0-dev.312.0 → 0.0.0-dev.421.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.
@@ -1,1508 +0,0 @@
1
- // src/host.ts
2
- import { createLogger as createLogger2 } from "@parity/product-sdk-logger";
3
-
4
- // src/host-cache.ts
5
- import { createLogger } from "@parity/product-sdk-logger";
6
- import { toHex } from "@polkadot-api/utils";
7
- import { mkdir, readFile, rename, writeFile } from "fs/promises";
8
- import { homedir } from "os";
9
- import { dirname, join } from "path";
10
- var log = createLogger("terminal");
11
- var DEFAULT_STORAGE_DIR = join(homedir(), ".polkadot-apps");
12
- var CACHE_FILE_MODE = 384;
13
- function sanitizeAppId(appId) {
14
- return appId.replace(/[^a-zA-Z0-9_.-]/g, "_");
15
- }
16
- function cachePath(appId, storageDir) {
17
- return join(storageDir ?? DEFAULT_STORAGE_DIR, `${sanitizeAppId(appId)}_AllowanceKeys.json`);
18
- }
19
- function emptyCache() {
20
- return { version: 1, entries: {} };
21
- }
22
- async function loadCache(appId, storageDir) {
23
- const path = cachePath(appId, storageDir);
24
- let raw;
25
- try {
26
- raw = await readFile(path, "utf-8");
27
- } catch (e) {
28
- if (e?.code === "ENOENT") return emptyCache();
29
- throw e;
30
- }
31
- try {
32
- const parsed = JSON.parse(raw);
33
- if (parsed?.version !== 1 || typeof parsed.entries !== "object" || parsed.entries === null) {
34
- log.warn("allowance cache schema mismatch; starting fresh", { appId, path });
35
- return emptyCache();
36
- }
37
- return parsed;
38
- } catch (e) {
39
- log.warn("allowance cache parse failed; starting fresh", { appId, path, error: String(e) });
40
- return emptyCache();
41
- }
42
- }
43
- async function saveCache(appId, cache, storageDir) {
44
- const path = cachePath(appId, storageDir);
45
- await mkdir(dirname(path), { recursive: true });
46
- const tmp = `${path}.tmp`;
47
- await writeFile(tmp, JSON.stringify(cache, null, 2), { mode: CACHE_FILE_MODE });
48
- await rename(tmp, path);
49
- }
50
- function cacheKey(resource) {
51
- if (resource.tag === "SmartContractAllowance") {
52
- return `${resource.tag}::${resource.value}`;
53
- }
54
- return resource.tag;
55
- }
56
- function pickOnExistingPolicy(cache, resources) {
57
- if (resources.length === 0) return "Ignore";
58
- const allSlotTableAndCached = resources.every((r) => {
59
- if (r.tag !== "BulletInAllowance" && r.tag !== "StatementStoreAllowance") return false;
60
- return Object.hasOwn(cache.entries, cacheKey(r));
61
- });
62
- return allSlotTableAndCached ? "Increase" : "Ignore";
63
- }
64
- function mergeOutcomes(cache, requested, outcomes) {
65
- if (requested.length !== outcomes.length) {
66
- throw new Error(
67
- `mergeOutcomes: length mismatch \u2014 requested ${requested.length}, got ${outcomes.length}`
68
- );
69
- }
70
- const entries = { ...cache.entries };
71
- let mutated = false;
72
- for (let i = 0; i < outcomes.length; i++) {
73
- const req = requested[i];
74
- const out = outcomes[i];
75
- if (out.tag !== "Allocated") continue;
76
- const inner = out.value;
77
- if (inner.tag !== req.tag) {
78
- throw new Error(
79
- `mergeOutcomes: variant mismatch at index ${i} \u2014 requested ${req.tag}, got Allocated<${inner.tag}>`
80
- );
81
- }
82
- const key = cacheKey(req);
83
- switch (inner.tag) {
84
- case "BulletInAllowance":
85
- case "StatementStoreAllowance":
86
- entries[key] = {
87
- tag: inner.tag,
88
- slotAccountKey: toHex(inner.value.slotAccountKey)
89
- };
90
- mutated = true;
91
- break;
92
- case "SmartContractAllowance":
93
- if (req.tag === "SmartContractAllowance") {
94
- entries[key] = { tag: "SmartContractAllowance", dest: req.value };
95
- mutated = true;
96
- }
97
- break;
98
- case "AutoSigning":
99
- entries[key] = {
100
- tag: "AutoSigning",
101
- productDerivationSecret: inner.value.productDerivationSecret,
102
- productRootPrivateKey: toHex(inner.value.productRootPrivateKey)
103
- };
104
- mutated = true;
105
- break;
106
- }
107
- }
108
- return mutated ? { version: 1, entries } : cache;
109
- }
110
- function readCacheEntry(cache, resource) {
111
- return cache.entries[cacheKey(resource)] ?? null;
112
- }
113
- var cacheLocks = /* @__PURE__ */ new Map();
114
- function withCacheLock(appId, storageDir, fn) {
115
- const key = cachePath(appId, storageDir);
116
- const prev = cacheLocks.get(key) ?? Promise.resolve();
117
- const next = prev.catch(() => {
118
- }).then(fn);
119
- cacheLocks.set(
120
- key,
121
- next.catch(() => {
122
- })
123
- );
124
- return next;
125
- }
126
- if (void 0) {
127
- const { describe, test, expect, beforeEach } = void 0;
128
- const { mkdtempSync, rmSync } = await null;
129
- const { tmpdir } = await null;
130
- let storageDir;
131
- beforeEach(() => {
132
- storageDir = mkdtempSync(join(tmpdir(), "host-cache-test-"));
133
- return () => rmSync(storageDir, { recursive: true, force: true });
134
- });
135
- describe("cacheKey", () => {
136
- test("returns the variant tag for slot-table resources and AutoSigning", () => {
137
- expect(cacheKey({ tag: "BulletInAllowance", value: void 0 })).toBe(
138
- "BulletInAllowance"
139
- );
140
- expect(cacheKey({ tag: "StatementStoreAllowance", value: void 0 })).toBe(
141
- "StatementStoreAllowance"
142
- );
143
- expect(cacheKey({ tag: "AutoSigning", value: void 0 })).toBe("AutoSigning");
144
- });
145
- test("disambiguates SmartContractAllowance by dest", () => {
146
- expect(cacheKey({ tag: "SmartContractAllowance", value: 0 })).toBe(
147
- "SmartContractAllowance::0"
148
- );
149
- expect(cacheKey({ tag: "SmartContractAllowance", value: 7 })).toBe(
150
- "SmartContractAllowance::7"
151
- );
152
- });
153
- });
154
- describe("loadCache / saveCache round-trip", () => {
155
- test("loadCache on missing file returns empty cache, not an error", async () => {
156
- const cache = await loadCache("my-app", storageDir);
157
- expect(cache).toEqual({ version: 1, entries: {} });
158
- });
159
- test("saveCache then loadCache returns the same content", async () => {
160
- const original = {
161
- version: 1,
162
- entries: {
163
- BulletInAllowance: { tag: "BulletInAllowance", slotAccountKey: "0xdeadbeef" }
164
- }
165
- };
166
- await saveCache("my-app", original, storageDir);
167
- const reloaded = await loadCache("my-app", storageDir);
168
- expect(reloaded).toEqual(original);
169
- });
170
- test("two appIds in the same storageDir don't collide", async () => {
171
- const a = {
172
- version: 1,
173
- entries: {
174
- BulletInAllowance: { tag: "BulletInAllowance", slotAccountKey: "0x01" }
175
- }
176
- };
177
- const b = {
178
- version: 1,
179
- entries: {
180
- BulletInAllowance: { tag: "BulletInAllowance", slotAccountKey: "0x02" }
181
- }
182
- };
183
- await saveCache("app-a", a, storageDir);
184
- await saveCache("app-b", b, storageDir);
185
- expect((await loadCache("app-a", storageDir)).entries.BulletInAllowance).toEqual(
186
- a.entries.BulletInAllowance
187
- );
188
- expect((await loadCache("app-b", storageDir)).entries.BulletInAllowance).toEqual(
189
- b.entries.BulletInAllowance
190
- );
191
- });
192
- test("appId with non-alphanumeric characters sanitizes to a safe path", async () => {
193
- const cache = { version: 1, entries: {} };
194
- await expect(saveCache("../escape", cache, storageDir)).resolves.toBeUndefined();
195
- const reloaded = await loadCache("../escape", storageDir);
196
- expect(reloaded).toEqual(cache);
197
- });
198
- test("malformed cache file is treated as empty, not thrown", async () => {
199
- await writeFile(cachePath("my-app", storageDir), "{ not valid json", "utf-8");
200
- const cache = await loadCache("my-app", storageDir);
201
- expect(cache).toEqual({ version: 1, entries: {} });
202
- });
203
- test("version mismatch is treated as empty, not thrown", async () => {
204
- await writeFile(
205
- cachePath("my-app", storageDir),
206
- JSON.stringify({ version: 99, entries: {} }),
207
- "utf-8"
208
- );
209
- const cache = await loadCache("my-app", storageDir);
210
- expect(cache).toEqual({ version: 1, entries: {} });
211
- });
212
- test("file is written with 0o600 permissions (defense-in-depth)", async () => {
213
- const { stat } = await null;
214
- const cache = { version: 1, entries: {} };
215
- await saveCache("my-app", cache, storageDir);
216
- const s = await stat(cachePath("my-app", storageDir));
217
- expect(s.mode & 511).toBe(384);
218
- });
219
- });
220
- describe("pickOnExistingPolicy", () => {
221
- test("empty request resolves to Ignore", () => {
222
- const cache = emptyCache();
223
- expect(pickOnExistingPolicy(cache, [])).toBe("Ignore");
224
- });
225
- test("no cached entries \u2192 Ignore", () => {
226
- const cache = emptyCache();
227
- expect(
228
- pickOnExistingPolicy(cache, [{ tag: "BulletInAllowance", value: void 0 }])
229
- ).toBe("Ignore");
230
- });
231
- test("all slot-table resources cached \u2192 Increase", () => {
232
- const cache = {
233
- version: 1,
234
- entries: {
235
- BulletInAllowance: { tag: "BulletInAllowance", slotAccountKey: "0x01" },
236
- StatementStoreAllowance: {
237
- tag: "StatementStoreAllowance",
238
- slotAccountKey: "0x02"
239
- }
240
- }
241
- };
242
- expect(
243
- pickOnExistingPolicy(cache, [
244
- { tag: "BulletInAllowance", value: void 0 },
245
- { tag: "StatementStoreAllowance", value: void 0 }
246
- ])
247
- ).toBe("Increase");
248
- });
249
- test("mixed cached + uncached \u2192 Ignore (conservative)", () => {
250
- const cache = {
251
- version: 1,
252
- entries: {
253
- BulletInAllowance: { tag: "BulletInAllowance", slotAccountKey: "0x01" }
254
- }
255
- };
256
- expect(
257
- pickOnExistingPolicy(cache, [
258
- { tag: "BulletInAllowance", value: void 0 },
259
- { tag: "StatementStoreAllowance", value: void 0 }
260
- ])
261
- ).toBe("Ignore");
262
- });
263
- test("AutoSigning in the request \u2192 Ignore even if cached (not slot-additive)", () => {
264
- const cache = {
265
- version: 1,
266
- entries: {
267
- AutoSigning: {
268
- tag: "AutoSigning",
269
- productDerivationSecret: "x",
270
- productRootPrivateKey: "0xaa"
271
- }
272
- }
273
- };
274
- expect(pickOnExistingPolicy(cache, [{ tag: "AutoSigning", value: void 0 }])).toBe(
275
- "Ignore"
276
- );
277
- });
278
- test("SC in the request \u2192 Ignore (not slot-additive; PGAS claim is per-call)", () => {
279
- const cache = {
280
- version: 1,
281
- entries: {
282
- "SmartContractAllowance::5": { tag: "SmartContractAllowance", dest: 5 }
283
- }
284
- };
285
- expect(pickOnExistingPolicy(cache, [{ tag: "SmartContractAllowance", value: 5 }])).toBe(
286
- "Ignore"
287
- );
288
- });
289
- });
290
- describe("mergeOutcomes", () => {
291
- test("ignores Rejected and NotAvailable", () => {
292
- const cache = emptyCache();
293
- const requested = [
294
- { tag: "BulletInAllowance", value: void 0 },
295
- { tag: "StatementStoreAllowance", value: void 0 }
296
- ];
297
- const outcomes = [
298
- { tag: "Rejected", value: void 0 },
299
- { tag: "NotAvailable", value: void 0 }
300
- ];
301
- const merged = mergeOutcomes(cache, requested, outcomes);
302
- expect(merged.entries).toEqual({});
303
- });
304
- test("stores Allocated Bulletin/SSS keys with hex encoding", () => {
305
- const cache = emptyCache();
306
- const requested = [
307
- { tag: "BulletInAllowance", value: void 0 }
308
- ];
309
- const outcomes = [
310
- {
311
- tag: "Allocated",
312
- value: {
313
- tag: "BulletInAllowance",
314
- value: { slotAccountKey: new Uint8Array([222, 173, 190, 239]) }
315
- }
316
- }
317
- ];
318
- const merged = mergeOutcomes(cache, requested, outcomes);
319
- expect(merged.entries.BulletInAllowance).toEqual({
320
- tag: "BulletInAllowance",
321
- slotAccountKey: "0xdeadbeef"
322
- });
323
- });
324
- test("stores SmartContractAllowance with dest from the request", () => {
325
- const cache = emptyCache();
326
- const requested = [{ tag: "SmartContractAllowance", value: 7 }];
327
- const outcomes = [
328
- {
329
- tag: "Allocated",
330
- value: { tag: "SmartContractAllowance", value: void 0 }
331
- }
332
- ];
333
- const merged = mergeOutcomes(cache, requested, outcomes);
334
- expect(merged.entries["SmartContractAllowance::7"]).toEqual({
335
- tag: "SmartContractAllowance",
336
- dest: 7
337
- });
338
- });
339
- test("stores AutoSigning subtree key + secret", () => {
340
- const cache = emptyCache();
341
- const requested = [{ tag: "AutoSigning", value: void 0 }];
342
- const outcomes = [
343
- {
344
- tag: "Allocated",
345
- value: {
346
- tag: "AutoSigning",
347
- value: {
348
- productDerivationSecret: "secret-hex",
349
- productRootPrivateKey: new Uint8Array([171, 205])
350
- }
351
- }
352
- }
353
- ];
354
- const merged = mergeOutcomes(cache, requested, outcomes);
355
- expect(merged.entries.AutoSigning).toEqual({
356
- tag: "AutoSigning",
357
- productDerivationSecret: "secret-hex",
358
- productRootPrivateKey: "0xabcd"
359
- });
360
- });
361
- test("returns the same cache reference when no Allocated outcomes (no-op)", () => {
362
- const cache = {
363
- version: 1,
364
- entries: {
365
- BulletInAllowance: { tag: "BulletInAllowance", slotAccountKey: "0xaa" }
366
- }
367
- };
368
- const requested = [
369
- { tag: "BulletInAllowance", value: void 0 },
370
- { tag: "StatementStoreAllowance", value: void 0 }
371
- ];
372
- const outcomes = [
373
- { tag: "Rejected", value: void 0 },
374
- { tag: "NotAvailable", value: void 0 }
375
- ];
376
- expect(mergeOutcomes(cache, requested, outcomes)).toBe(cache);
377
- });
378
- test("empty inputs return the same cache reference", () => {
379
- const cache = emptyCache();
380
- expect(mergeOutcomes(cache, [], [])).toBe(cache);
381
- });
382
- test("returns a new object reference when at least one entry is added", () => {
383
- const cache = emptyCache();
384
- const requested = [
385
- { tag: "BulletInAllowance", value: void 0 }
386
- ];
387
- const outcomes = [
388
- {
389
- tag: "Allocated",
390
- value: {
391
- tag: "BulletInAllowance",
392
- value: { slotAccountKey: new Uint8Array([1]) }
393
- }
394
- }
395
- ];
396
- expect(mergeOutcomes(cache, requested, outcomes)).not.toBe(cache);
397
- });
398
- test("throws when outcomes do not length-match requested", () => {
399
- const cache = emptyCache();
400
- const requested = [
401
- { tag: "BulletInAllowance", value: void 0 },
402
- { tag: "StatementStoreAllowance", value: void 0 }
403
- ];
404
- const outcomes = [{ tag: "Rejected", value: void 0 }];
405
- expect(() => mergeOutcomes(cache, requested, outcomes)).toThrow(/length mismatch/);
406
- });
407
- test("throws when an Allocated outcome's variant does not match the requested variant", () => {
408
- const cache = emptyCache();
409
- const requested = [
410
- { tag: "StatementStoreAllowance", value: void 0 }
411
- ];
412
- const outcomes = [
413
- {
414
- tag: "Allocated",
415
- value: {
416
- tag: "BulletInAllowance",
417
- value: { slotAccountKey: new Uint8Array([170]) }
418
- }
419
- }
420
- ];
421
- expect(() => mergeOutcomes(cache, requested, outcomes)).toThrow(
422
- /variant mismatch at index 0 — requested StatementStoreAllowance, got Allocated<BulletInAllowance>/
423
- );
424
- });
425
- test("preserves existing entries when merging new ones", () => {
426
- const cache = {
427
- version: 1,
428
- entries: {
429
- StatementStoreAllowance: {
430
- tag: "StatementStoreAllowance",
431
- slotAccountKey: "0xaa"
432
- }
433
- }
434
- };
435
- const requested = [
436
- { tag: "BulletInAllowance", value: void 0 }
437
- ];
438
- const outcomes = [
439
- {
440
- tag: "Allocated",
441
- value: {
442
- tag: "BulletInAllowance",
443
- value: { slotAccountKey: new Uint8Array([187]) }
444
- }
445
- }
446
- ];
447
- const merged = mergeOutcomes(cache, requested, outcomes);
448
- expect(Object.keys(merged.entries).sort()).toEqual([
449
- "BulletInAllowance",
450
- "StatementStoreAllowance"
451
- ]);
452
- expect(merged.entries.StatementStoreAllowance).toEqual({
453
- tag: "StatementStoreAllowance",
454
- slotAccountKey: "0xaa"
455
- });
456
- });
457
- });
458
- describe("readCacheEntry", () => {
459
- test("returns the cached entry by resource discriminator", () => {
460
- const cache = {
461
- version: 1,
462
- entries: {
463
- "SmartContractAllowance::5": {
464
- tag: "SmartContractAllowance",
465
- dest: 5
466
- }
467
- }
468
- };
469
- expect(readCacheEntry(cache, { tag: "SmartContractAllowance", value: 5 })).toEqual({
470
- tag: "SmartContractAllowance",
471
- dest: 5
472
- });
473
- expect(readCacheEntry(cache, { tag: "SmartContractAllowance", value: 6 })).toBeNull();
474
- });
475
- test("returns null when nothing cached", () => {
476
- const cache = emptyCache();
477
- expect(
478
- readCacheEntry(cache, { tag: "BulletInAllowance", value: void 0 })
479
- ).toBeNull();
480
- });
481
- });
482
- describe("withCacheLock", () => {
483
- test("serializes calls for the same (appId, storageDir)", async () => {
484
- const order = [];
485
- const a = withCacheLock("app", storageDir, async () => {
486
- order.push("a-start");
487
- await new Promise((r) => setTimeout(r, 10));
488
- order.push("a-end");
489
- });
490
- const b = withCacheLock("app", storageDir, async () => {
491
- order.push("b-start");
492
- order.push("b-end");
493
- });
494
- await Promise.all([a, b]);
495
- expect(order).toEqual(["a-start", "a-end", "b-start", "b-end"]);
496
- });
497
- test("different (appId, storageDir) keys do not block each other", async () => {
498
- let bRan = false;
499
- const a = withCacheLock("app-a", storageDir, async () => {
500
- await new Promise((r) => setTimeout(r, 20));
501
- expect(bRan).toBe(true);
502
- });
503
- const b = withCacheLock("app-b", storageDir, async () => {
504
- bRan = true;
505
- });
506
- await Promise.all([a, b]);
507
- });
508
- test("a rejection in one call does not block subsequent calls on the same key", async () => {
509
- const a = withCacheLock("app", storageDir, async () => {
510
- throw new Error("boom");
511
- });
512
- await expect(a).rejects.toThrow("boom");
513
- const b = await withCacheLock("app", storageDir, async () => "ok");
514
- expect(b).toBe("ok");
515
- });
516
- });
517
- }
518
-
519
- // src/host-signer.ts
520
- import { bytesToNumberLE, numberToBytesLE } from "@noble/curves/utils.js";
521
- import { fromHex } from "@polkadot-api/utils";
522
- import { createDerive, sr25519, sr25519Derive } from "@polkadot-labs/hdkd-helpers";
523
- import { getPolkadotSigner } from "polkadot-api/signer";
524
- function canonicalSr25519SecretToEd25519Bytes(secret) {
525
- const ed25519Scalar = numberToBytesLE(bytesToNumberLE(secret.subarray(0, 32)) << 3n, 32);
526
- const out = new Uint8Array(64);
527
- out.set(ed25519Scalar, 0);
528
- out.set(secret.subarray(32, 64), 32);
529
- return out;
530
- }
531
- function buildKeypair(secret) {
532
- if (secret.length === 32) {
533
- const derive = createDerive({ seed: secret, curve: sr25519, derive: sr25519Derive });
534
- const keypair = derive("");
535
- return { publicKey: keypair.publicKey, sign: (data) => keypair.sign(data) };
536
- }
537
- if (secret.length === 64) {
538
- const ed25519Secret = canonicalSr25519SecretToEd25519Bytes(secret);
539
- return {
540
- publicKey: sr25519.getPublicKey(ed25519Secret),
541
- sign: (data) => sr25519.sign(data, ed25519Secret)
542
- };
543
- }
544
- throw new Error(
545
- `createSlotAccountSigner: unexpected slotAccountKey length ${secret.length}. Expected 32 (mini-secret) or 64 (expanded secret).`
546
- );
547
- }
548
- function buildSignerFromEntry(entry) {
549
- if (entry.tag !== "BulletInAllowance" && entry.tag !== "StatementStoreAllowance") {
550
- throw new Error(
551
- `createSlotAccountSigner: ${entry.tag} does not carry a slot account key. Slot-table signing is defined for BulletInAllowance and StatementStoreAllowance only.`
552
- );
553
- }
554
- const secret = fromHex(entry.slotAccountKey);
555
- const { publicKey, sign } = buildKeypair(secret);
556
- return getPolkadotSigner(publicKey, "Sr25519", async (data) => sign(data));
557
- }
558
- async function createSlotAccountSigner(adapter, resource, productId) {
559
- const cache = await loadCache(productId ?? adapter.appId, adapter.storageDir);
560
- const entry = readCacheEntry(cache, resource);
561
- if (!entry) return null;
562
- return buildSignerFromEntry(entry);
563
- }
564
- if (void 0) {
565
- let fakeAdapter = function(appId) {
566
- return { appId, storageDir };
567
- }, knownSecret = function() {
568
- const secret = mnemonicToMiniSecret(DEV_PHRASE);
569
- const derive = createDerive({ seed: secret, curve: sr25519, derive: sr25519Derive });
570
- const keypair = derive("");
571
- return {
572
- secret,
573
- publicKey: keypair.publicKey,
574
- hex: toHex2(secret),
575
- sign: (msg) => keypair.sign(msg)
576
- };
577
- };
578
- fakeAdapter2 = fakeAdapter, knownSecret2 = knownSecret;
579
- const { describe, test, expect, beforeEach } = void 0;
580
- const { mkdtempSync, rmSync } = await null;
581
- const { tmpdir } = await null;
582
- const { join: pathJoin } = await null;
583
- const { saveCache: saveCache2 } = await null;
584
- const { DEV_PHRASE, mnemonicToMiniSecret } = await null;
585
- const { toHex: toHex2 } = await null;
586
- let storageDir;
587
- beforeEach(() => {
588
- storageDir = mkdtempSync(pathJoin(tmpdir(), "host-signer-test-"));
589
- return () => rmSync(storageDir, { recursive: true, force: true });
590
- });
591
- describe("createSlotAccountSigner", () => {
592
- test("returns null when no allocation is cached", async () => {
593
- const adapter = fakeAdapter("p");
594
- const signer = await createSlotAccountSigner(adapter, {
595
- tag: "BulletInAllowance",
596
- value: void 0
597
- });
598
- expect(signer).toBeNull();
599
- });
600
- test("returns a signer with the sr25519 public key derived from the cached secret", async () => {
601
- const { hex, publicKey } = knownSecret();
602
- await saveCache2(
603
- "p",
604
- {
605
- version: 1,
606
- entries: {
607
- BulletInAllowance: { tag: "BulletInAllowance", slotAccountKey: hex }
608
- }
609
- },
610
- storageDir
611
- );
612
- const signer = await createSlotAccountSigner(fakeAdapter("p"), {
613
- tag: "BulletInAllowance",
614
- value: void 0
615
- });
616
- expect(signer).not.toBeNull();
617
- expect(signer?.publicKey).toEqual(publicKey);
618
- });
619
- test("signs locally and the signature verifies against the cached account", async () => {
620
- const { hex, publicKey } = knownSecret();
621
- await saveCache2(
622
- "p",
623
- {
624
- version: 1,
625
- entries: {
626
- BulletInAllowance: { tag: "BulletInAllowance", slotAccountKey: hex }
627
- }
628
- },
629
- storageDir
630
- );
631
- const signer = await createSlotAccountSigner(fakeAdapter("p"), {
632
- tag: "BulletInAllowance",
633
- value: void 0
634
- });
635
- const payload = new TextEncoder().encode("hello slot");
636
- const wrapped = new Uint8Array([
637
- ...new TextEncoder().encode("<Bytes>"),
638
- ...payload,
639
- ...new TextEncoder().encode("</Bytes>")
640
- ]);
641
- const signature = await signer.signBytes(payload);
642
- expect(sr25519.verify(signature, wrapped, publicKey)).toBe(true);
643
- });
644
- test("64-byte expanded-secret form produces a valid signer", async () => {
645
- const SECRET_64_HEX = "0x05d65584630d16cd4af6d0bec10f34bb504a5dcb62dba2122d49f5a663763d0afd190cce74df356432b410bd64682309d6dedb27c76845daf388557cbac3ca34";
646
- await saveCache2(
647
- "p",
648
- {
649
- version: 1,
650
- entries: {
651
- BulletInAllowance: {
652
- tag: "BulletInAllowance",
653
- slotAccountKey: SECRET_64_HEX
654
- }
655
- }
656
- },
657
- storageDir
658
- );
659
- const signer = await createSlotAccountSigner(fakeAdapter("p"), {
660
- tag: "BulletInAllowance",
661
- value: void 0
662
- });
663
- expect(signer).not.toBeNull();
664
- const { publicKey: pubFrom32Bytes } = knownSecret();
665
- expect(signer?.publicKey).toEqual(pubFrom32Bytes);
666
- const payload = new TextEncoder().encode("hello 64-byte path");
667
- const wrapped = new Uint8Array([
668
- ...new TextEncoder().encode("<Bytes>"),
669
- ...payload,
670
- ...new TextEncoder().encode("</Bytes>")
671
- ]);
672
- const sig = await signer.signBytes(payload);
673
- expect(sr25519.verify(sig, wrapped, pubFrom32Bytes)).toBe(true);
674
- });
675
- test("works for StatementStoreAllowance the same way", async () => {
676
- const { hex, publicKey } = knownSecret();
677
- await saveCache2(
678
- "p",
679
- {
680
- version: 1,
681
- entries: {
682
- StatementStoreAllowance: {
683
- tag: "StatementStoreAllowance",
684
- slotAccountKey: hex
685
- }
686
- }
687
- },
688
- storageDir
689
- );
690
- const signer = await createSlotAccountSigner(fakeAdapter("p"), {
691
- tag: "StatementStoreAllowance",
692
- value: void 0
693
- });
694
- expect(signer?.publicKey).toEqual(publicKey);
695
- });
696
- test("throws for SmartContractAllowance (no slot account key)", async () => {
697
- await saveCache2(
698
- "p",
699
- {
700
- version: 1,
701
- entries: {
702
- "SmartContractAllowance::5": { tag: "SmartContractAllowance", dest: 5 }
703
- }
704
- },
705
- storageDir
706
- );
707
- await expect(
708
- createSlotAccountSigner(fakeAdapter("p"), {
709
- tag: "SmartContractAllowance",
710
- value: 5
711
- })
712
- ).rejects.toThrow(/SmartContractAllowance does not carry a slot account key/);
713
- });
714
- test("throws for AutoSigning (subtree key, not slot account)", async () => {
715
- await saveCache2(
716
- "p",
717
- {
718
- version: 1,
719
- entries: {
720
- AutoSigning: {
721
- tag: "AutoSigning",
722
- productDerivationSecret: "secret",
723
- productRootPrivateKey: "0xabcd"
724
- }
725
- }
726
- },
727
- storageDir
728
- );
729
- await expect(
730
- createSlotAccountSigner(fakeAdapter("p"), {
731
- tag: "AutoSigning",
732
- value: void 0
733
- })
734
- ).rejects.toThrow(/AutoSigning does not carry a slot account key/);
735
- });
736
- test("explicit productId reads that cache namespace instead of adapter.appId", async () => {
737
- const { hex, publicKey } = knownSecret();
738
- await saveCache2(
739
- "my-product.dot",
740
- {
741
- version: 1,
742
- entries: {
743
- BulletInAllowance: { tag: "BulletInAllowance", slotAccountKey: hex }
744
- }
745
- },
746
- storageDir
747
- );
748
- const adapter = fakeAdapter("my-cli");
749
- expect(
750
- await createSlotAccountSigner(adapter, {
751
- tag: "BulletInAllowance",
752
- value: void 0
753
- })
754
- ).toBeNull();
755
- const signer = await createSlotAccountSigner(
756
- adapter,
757
- { tag: "BulletInAllowance", value: void 0 },
758
- "my-product.dot"
759
- );
760
- expect(signer?.publicKey).toEqual(publicKey);
761
- });
762
- test("different appIds yield different signers from disjoint caches", async () => {
763
- const a = knownSecret();
764
- const otherMiniSecret = new Uint8Array(32).fill(7);
765
- const otherKeypair = createDerive({
766
- seed: otherMiniSecret,
767
- curve: sr25519,
768
- derive: sr25519Derive
769
- })("");
770
- await saveCache2(
771
- "app-a",
772
- {
773
- version: 1,
774
- entries: {
775
- BulletInAllowance: { tag: "BulletInAllowance", slotAccountKey: a.hex }
776
- }
777
- },
778
- storageDir
779
- );
780
- await saveCache2(
781
- "app-b",
782
- {
783
- version: 1,
784
- entries: {
785
- BulletInAllowance: {
786
- tag: "BulletInAllowance",
787
- slotAccountKey: toHex2(otherMiniSecret)
788
- }
789
- }
790
- },
791
- storageDir
792
- );
793
- const signerA = await createSlotAccountSigner(fakeAdapter("app-a"), {
794
- tag: "BulletInAllowance",
795
- value: void 0
796
- });
797
- const signerB = await createSlotAccountSigner(fakeAdapter("app-b"), {
798
- tag: "BulletInAllowance",
799
- value: void 0
800
- });
801
- expect(signerA?.publicKey).toEqual(a.publicKey);
802
- expect(signerB?.publicKey).toEqual(otherKeypair.publicKey);
803
- expect(signerA?.publicKey).not.toEqual(signerB?.publicKey);
804
- });
805
- test("rejects an unexpected slotAccountKey length", async () => {
806
- await saveCache2(
807
- "p",
808
- {
809
- version: 1,
810
- entries: {
811
- BulletInAllowance: {
812
- tag: "BulletInAllowance",
813
- slotAccountKey: toHex2(new Uint8Array(48).fill(1))
814
- }
815
- }
816
- },
817
- storageDir
818
- );
819
- await expect(
820
- createSlotAccountSigner(fakeAdapter("p"), {
821
- tag: "BulletInAllowance",
822
- value: void 0
823
- })
824
- ).rejects.toThrow(/unexpected slotAccountKey length 48/);
825
- });
826
- });
827
- }
828
- var fakeAdapter2;
829
- var knownSecret2;
830
-
831
- // src/host.ts
832
- var log2 = createLogger2("terminal");
833
- async function requestResourceAllocation(session, adapter, resources, options = {}) {
834
- const productId = options.productId ?? adapter.appId;
835
- return withCacheLock(productId, adapter.storageDir, async () => {
836
- const cache = await loadCache(productId, adapter.storageDir);
837
- const onExisting = options.onExisting ?? pickOnExistingPolicy(cache, resources);
838
- log2.debug("requestResourceAllocation", {
839
- productId,
840
- resources: resources.map((r) => r.tag),
841
- onExisting,
842
- autoPolicy: options.onExisting === void 0
843
- });
844
- const outcomes = await sendResourceAllocation(session, productId, resources, onExisting);
845
- const next = mergeOutcomes(cache, resources, outcomes);
846
- if (next !== cache) {
847
- await saveCache(productId, next, adapter.storageDir);
848
- }
849
- return outcomes;
850
- });
851
- }
852
- async function sendResourceAllocation(session, productId, resources, onExisting) {
853
- const result = await session.requestResourceAllocation({
854
- callingProductId: productId,
855
- resources,
856
- onExisting
857
- });
858
- if (result.isErr()) {
859
- throw new Error(`requestResourceAllocation failed: ${result.error.message}`);
860
- }
861
- return result.value;
862
- }
863
- async function getCachedAllocation(adapter, resource, productId) {
864
- const cache = await loadCache(productId ?? adapter.appId, adapter.storageDir);
865
- return readCacheEntry(cache, resource);
866
- }
867
- async function ensureSlotAccountSigner(session, adapter, resource, productId) {
868
- const resolvedProductId = productId ?? adapter.appId;
869
- return withCacheLock(resolvedProductId, adapter.storageDir, async () => {
870
- const cache = await loadCache(resolvedProductId, adapter.storageDir);
871
- const hit = readCacheEntry(cache, resource);
872
- if (hit) return buildSignerFromEntry(hit);
873
- log2.debug("ensureSlotAccountSigner: cache miss, allocating", { resource: resource.tag });
874
- const onExisting = pickOnExistingPolicy(cache, [resource]);
875
- const outcomes = await sendResourceAllocation(
876
- session,
877
- resolvedProductId,
878
- [resource],
879
- onExisting
880
- );
881
- const next = mergeOutcomes(cache, [resource], outcomes);
882
- if (next !== cache) {
883
- await saveCache(resolvedProductId, next, adapter.storageDir);
884
- }
885
- const outcome = outcomes[0];
886
- if (!outcome) {
887
- throw new Error(
888
- `ensureSlotAccountSigner: protocol violation \u2014 empty outcomes for ${resource.tag}`
889
- );
890
- }
891
- if (outcome.tag !== "Allocated") {
892
- throw new Error(
893
- `ensureSlotAccountSigner: allocation ${outcome.tag} for ${resource.tag}`
894
- );
895
- }
896
- const freshEntry = readCacheEntry(next, resource);
897
- if (!freshEntry) {
898
- throw new Error(
899
- `ensureSlotAccountSigner: allocation succeeded but cache lookup returned null for ${resource.tag}`
900
- );
901
- }
902
- return buildSignerFromEntry(freshEntry);
903
- });
904
- }
905
- if (void 0) {
906
- let fakeAdapter = function(appId) {
907
- return { appId, storageDir: testStorageDir };
908
- }, makeSession = function(opts) {
909
- return {
910
- requestResourceAllocation: vi.fn(
911
- opts.requestResourceAllocation ?? (async () => {
912
- throw new Error("requestResourceAllocation not stubbed in this test");
913
- })
914
- )
915
- };
916
- };
917
- fakeAdapter2 = fakeAdapter, makeSession2 = makeSession;
918
- const { describe, test, expect, vi, beforeEach } = void 0;
919
- const { ok, err } = await null;
920
- const { mkdtempSync, rmSync } = await null;
921
- const { tmpdir } = await null;
922
- const { join: pathJoin } = await null;
923
- const { mnemonicToMiniSecret, DEV_PHRASE } = await null;
924
- const { toHex: toHex2 } = await null;
925
- let testStorageDir;
926
- beforeEach(() => {
927
- testStorageDir = mkdtempSync(pathJoin(tmpdir(), "host-test-"));
928
- return () => rmSync(testStorageDir, { recursive: true, force: true });
929
- });
930
- describe("requestResourceAllocation", () => {
931
- test("forwards resources verbatim and returns outcomes in order", async () => {
932
- const captured = [];
933
- const stubbed = [
934
- {
935
- tag: "Allocated",
936
- value: {
937
- tag: "BulletInAllowance",
938
- value: { slotAccountKey: new Uint8Array([1, 2, 3]) }
939
- }
940
- },
941
- { tag: "Rejected", value: void 0 }
942
- ];
943
- const session = makeSession({
944
- requestResourceAllocation: async (req) => {
945
- captured.push(req);
946
- return ok(stubbed);
947
- }
948
- });
949
- const outcomes = await requestResourceAllocation(session, fakeAdapter("my-app"), [
950
- { tag: "BulletInAllowance", value: void 0 },
951
- { tag: "StatementStoreAllowance", value: void 0 }
952
- ]);
953
- expect(captured).toHaveLength(1);
954
- expect(captured[0].callingProductId).toBe("my-app");
955
- expect(captured[0].resources.map((r) => r.tag)).toEqual([
956
- "BulletInAllowance",
957
- "StatementStoreAllowance"
958
- ]);
959
- expect(outcomes).toEqual(stubbed);
960
- });
961
- test("uses adapter.appId as callingProductId by default", async () => {
962
- const captured = [];
963
- const session = makeSession({
964
- requestResourceAllocation: async (req) => {
965
- captured.push(req);
966
- return ok([]);
967
- }
968
- });
969
- await requestResourceAllocation(session, fakeAdapter("alt-product.dot"), []);
970
- expect(captured[0].callingProductId).toBe("alt-product.dot");
971
- });
972
- test("options.productId overrides adapter.appId as callingProductId", async () => {
973
- const captured = [];
974
- const session = makeSession({
975
- requestResourceAllocation: async (req) => {
976
- captured.push(req);
977
- return ok([]);
978
- }
979
- });
980
- await requestResourceAllocation(session, fakeAdapter("my-cli"), [], {
981
- productId: "my-product.dot"
982
- });
983
- expect(captured[0].callingProductId).toBe("my-product.dot");
984
- });
985
- test("auto-picks Ignore for an empty resources array", async () => {
986
- const captured = [];
987
- const session = makeSession({
988
- requestResourceAllocation: async (req) => {
989
- captured.push(req);
990
- return ok([]);
991
- }
992
- });
993
- await requestResourceAllocation(session, fakeAdapter("p"), []);
994
- expect(captured[0].onExisting).toBe("Ignore");
995
- });
996
- test("forwards onExisting when explicitly set to 'Increase'", async () => {
997
- const captured = [];
998
- const session = makeSession({
999
- requestResourceAllocation: async (req) => {
1000
- captured.push(req);
1001
- return ok([{ tag: "Rejected", value: void 0 }]);
1002
- }
1003
- });
1004
- await requestResourceAllocation(
1005
- session,
1006
- fakeAdapter("p"),
1007
- [{ tag: "BulletInAllowance", value: void 0 }],
1008
- { onExisting: "Increase" }
1009
- );
1010
- expect(captured[0].onExisting).toBe("Increase");
1011
- });
1012
- test("forwards SmartContractAllowance derivation index", async () => {
1013
- const captured = [];
1014
- const session = makeSession({
1015
- requestResourceAllocation: async (req) => {
1016
- captured.push(req);
1017
- return ok([{ tag: "Rejected", value: void 0 }]);
1018
- }
1019
- });
1020
- await requestResourceAllocation(session, fakeAdapter("p"), [
1021
- { tag: "SmartContractAllowance", value: 7 }
1022
- ]);
1023
- const sent = captured[0].resources[0];
1024
- expect(sent.tag).toBe("SmartContractAllowance");
1025
- if (sent.tag === "SmartContractAllowance") {
1026
- expect(sent.value).toBe(7);
1027
- }
1028
- });
1029
- test("throws a clear error when the session call fails", async () => {
1030
- const session = makeSession({
1031
- requestResourceAllocation: async () => err(new Error("mobile disconnected"))
1032
- });
1033
- await expect(
1034
- requestResourceAllocation(session, fakeAdapter("p"), [
1035
- { tag: "BulletInAllowance", value: void 0 }
1036
- ])
1037
- ).rejects.toThrow("requestResourceAllocation failed: mobile disconnected");
1038
- });
1039
- test("forwards AutoSigning and returns NotAvailable verbatim (mobile-default path)", async () => {
1040
- const captured = [];
1041
- const stubbed = [{ tag: "NotAvailable", value: void 0 }];
1042
- const session = makeSession({
1043
- requestResourceAllocation: async (req) => {
1044
- captured.push(req);
1045
- return ok(stubbed);
1046
- }
1047
- });
1048
- const outcomes = await requestResourceAllocation(session, fakeAdapter("p"), [
1049
- { tag: "AutoSigning", value: void 0 }
1050
- ]);
1051
- const sent = captured[0].resources[0];
1052
- expect(sent.tag).toBe("AutoSigning");
1053
- expect(outcomes).toEqual(stubbed);
1054
- });
1055
- test("passes empty resources through (no-op request still hits the wire)", async () => {
1056
- const captured = [];
1057
- const session = makeSession({
1058
- requestResourceAllocation: async (req) => {
1059
- captured.push(req);
1060
- return ok([]);
1061
- }
1062
- });
1063
- const outcomes = await requestResourceAllocation(session, fakeAdapter("p"), []);
1064
- expect(captured).toHaveLength(1);
1065
- expect(captured[0].resources).toEqual([]);
1066
- expect(outcomes).toEqual([]);
1067
- });
1068
- });
1069
- describe("requestResourceAllocation \u2014 cache integration", () => {
1070
- function bulletinAllocated(slotKey) {
1071
- return ok([
1072
- {
1073
- tag: "Allocated",
1074
- value: {
1075
- tag: "BulletInAllowance",
1076
- value: { slotAccountKey: slotKey }
1077
- }
1078
- }
1079
- ]);
1080
- }
1081
- test("first call sends Ignore when cache is empty", async () => {
1082
- const captured = [];
1083
- const session = makeSession({
1084
- requestResourceAllocation: async (req) => {
1085
- captured.push(req);
1086
- return bulletinAllocated(new Uint8Array([1, 2, 3]));
1087
- }
1088
- });
1089
- await requestResourceAllocation(session, fakeAdapter("p"), [
1090
- { tag: "BulletInAllowance", value: void 0 }
1091
- ]);
1092
- expect(captured[0].onExisting).toBe("Ignore");
1093
- });
1094
- test("second call for same slot-table resource auto-picks Increase", async () => {
1095
- const captured = [];
1096
- const session = makeSession({
1097
- requestResourceAllocation: async (req2) => {
1098
- captured.push(req2);
1099
- return bulletinAllocated(new Uint8Array([1, 2, 3]));
1100
- }
1101
- });
1102
- const adapter = fakeAdapter("p");
1103
- const req = [{ tag: "BulletInAllowance", value: void 0 }];
1104
- await requestResourceAllocation(session, adapter, req);
1105
- await requestResourceAllocation(session, adapter, req);
1106
- expect(captured.map((c) => c.onExisting)).toEqual(["Ignore", "Increase"]);
1107
- });
1108
- test("explicit options.onExisting overrides the auto-pick", async () => {
1109
- const captured = [];
1110
- const session = makeSession({
1111
- requestResourceAllocation: async (req2) => {
1112
- captured.push(req2);
1113
- return bulletinAllocated(new Uint8Array([1, 2, 3]));
1114
- }
1115
- });
1116
- const adapter = fakeAdapter("p");
1117
- const req = [{ tag: "BulletInAllowance", value: void 0 }];
1118
- await requestResourceAllocation(session, adapter, req);
1119
- await requestResourceAllocation(session, adapter, req, { onExisting: "Ignore" });
1120
- expect(captured.map((c) => c.onExisting)).toEqual(["Ignore", "Ignore"]);
1121
- });
1122
- test("populates cache on Allocated and survives across separate calls", async () => {
1123
- const slotKey = new Uint8Array([222, 173, 190, 239]);
1124
- const session = makeSession({
1125
- requestResourceAllocation: async () => bulletinAllocated(slotKey)
1126
- });
1127
- const adapter = fakeAdapter("p");
1128
- await requestResourceAllocation(session, adapter, [
1129
- { tag: "BulletInAllowance", value: void 0 }
1130
- ]);
1131
- const cached = await getCachedAllocation(adapter, {
1132
- tag: "BulletInAllowance",
1133
- value: void 0
1134
- });
1135
- expect(cached).toEqual({
1136
- tag: "BulletInAllowance",
1137
- slotAccountKey: "0xdeadbeef"
1138
- });
1139
- });
1140
- test("does not cache Rejected or NotAvailable outcomes", async () => {
1141
- const session = makeSession({
1142
- requestResourceAllocation: async () => ok([
1143
- { tag: "Rejected", value: void 0 },
1144
- { tag: "NotAvailable", value: void 0 }
1145
- ])
1146
- });
1147
- const adapter = fakeAdapter("p");
1148
- await requestResourceAllocation(session, adapter, [
1149
- { tag: "BulletInAllowance", value: void 0 },
1150
- { tag: "StatementStoreAllowance", value: void 0 }
1151
- ]);
1152
- expect(
1153
- await getCachedAllocation(adapter, { tag: "BulletInAllowance", value: void 0 })
1154
- ).toBeNull();
1155
- expect(
1156
- await getCachedAllocation(adapter, {
1157
- tag: "StatementStoreAllowance",
1158
- value: void 0
1159
- })
1160
- ).toBeNull();
1161
- });
1162
- test("mixed cached + uncached request stays on Ignore (conservative)", async () => {
1163
- const captured = [];
1164
- const session = makeSession({
1165
- requestResourceAllocation: async (req) => {
1166
- captured.push(req);
1167
- return ok(
1168
- req.resources.map(
1169
- (_r, i) => i === 0 ? {
1170
- tag: "Allocated",
1171
- value: {
1172
- tag: "BulletInAllowance",
1173
- value: { slotAccountKey: new Uint8Array([1]) }
1174
- }
1175
- } : { tag: "Rejected", value: void 0 }
1176
- )
1177
- );
1178
- }
1179
- });
1180
- const adapter = fakeAdapter("p");
1181
- await requestResourceAllocation(session, adapter, [
1182
- { tag: "BulletInAllowance", value: void 0 }
1183
- ]);
1184
- await requestResourceAllocation(session, adapter, [
1185
- { tag: "BulletInAllowance", value: void 0 },
1186
- { tag: "StatementStoreAllowance", value: void 0 }
1187
- ]);
1188
- expect(captured[captured.length - 1].onExisting).toBe("Ignore");
1189
- });
1190
- test("AutoSigning never auto-Increases even when cached", async () => {
1191
- const captured = [];
1192
- const session = makeSession({
1193
- requestResourceAllocation: async (req2) => {
1194
- captured.push(req2);
1195
- return ok([
1196
- {
1197
- tag: "Allocated",
1198
- value: {
1199
- tag: "AutoSigning",
1200
- value: {
1201
- productDerivationSecret: "secret",
1202
- productRootPrivateKey: new Uint8Array([171])
1203
- }
1204
- }
1205
- }
1206
- ]);
1207
- }
1208
- });
1209
- const adapter = fakeAdapter("p");
1210
- const req = [{ tag: "AutoSigning", value: void 0 }];
1211
- await requestResourceAllocation(session, adapter, req);
1212
- await requestResourceAllocation(session, adapter, req);
1213
- expect(captured.map((c) => c.onExisting)).toEqual(["Ignore", "Ignore"]);
1214
- });
1215
- test("parallel calls for different resources both persist their keys (no last-writer-wins)", async () => {
1216
- const session = makeSession({
1217
- requestResourceAllocation: async (req) => {
1218
- await new Promise((r) => setTimeout(r, 5));
1219
- const tag = req.resources[0]?.tag;
1220
- return ok([
1221
- {
1222
- tag: "Allocated",
1223
- value: tag === "BulletInAllowance" ? {
1224
- tag: "BulletInAllowance",
1225
- value: { slotAccountKey: new Uint8Array([170]) }
1226
- } : {
1227
- tag: "StatementStoreAllowance",
1228
- value: { slotAccountKey: new Uint8Array([187]) }
1229
- }
1230
- }
1231
- ]);
1232
- }
1233
- });
1234
- const adapter = fakeAdapter("p");
1235
- await Promise.all([
1236
- requestResourceAllocation(session, adapter, [
1237
- { tag: "BulletInAllowance", value: void 0 }
1238
- ]),
1239
- requestResourceAllocation(session, adapter, [
1240
- { tag: "StatementStoreAllowance", value: void 0 }
1241
- ])
1242
- ]);
1243
- expect(
1244
- await getCachedAllocation(adapter, {
1245
- tag: "BulletInAllowance",
1246
- value: void 0
1247
- })
1248
- ).not.toBeNull();
1249
- expect(
1250
- await getCachedAllocation(adapter, {
1251
- tag: "StatementStoreAllowance",
1252
- value: void 0
1253
- })
1254
- ).not.toBeNull();
1255
- });
1256
- test("options.productId scopes the cache namespace, not adapter.appId", async () => {
1257
- const session = makeSession({
1258
- requestResourceAllocation: async () => bulletinAllocated(new Uint8Array([1, 2, 3]))
1259
- });
1260
- const adapter = fakeAdapter("my-cli");
1261
- await requestResourceAllocation(
1262
- session,
1263
- adapter,
1264
- [{ tag: "BulletInAllowance", value: void 0 }],
1265
- { productId: "my-product.dot" }
1266
- );
1267
- expect(
1268
- await getCachedAllocation(
1269
- adapter,
1270
- { tag: "BulletInAllowance", value: void 0 },
1271
- "my-product.dot"
1272
- )
1273
- ).not.toBeNull();
1274
- expect(
1275
- await getCachedAllocation(adapter, { tag: "BulletInAllowance", value: void 0 })
1276
- ).toBeNull();
1277
- });
1278
- test("two appIds maintain independent caches in the same storageDir", async () => {
1279
- const session = makeSession({
1280
- requestResourceAllocation: async () => bulletinAllocated(new Uint8Array([1, 2, 3]))
1281
- });
1282
- const adapterA = fakeAdapter("app-a");
1283
- const adapterB = fakeAdapter("app-b");
1284
- await requestResourceAllocation(session, adapterA, [
1285
- { tag: "BulletInAllowance", value: void 0 }
1286
- ]);
1287
- expect(
1288
- await getCachedAllocation(adapterB, {
1289
- tag: "BulletInAllowance",
1290
- value: void 0
1291
- })
1292
- ).toBeNull();
1293
- expect(
1294
- await getCachedAllocation(adapterA, {
1295
- tag: "BulletInAllowance",
1296
- value: void 0
1297
- })
1298
- ).not.toBeNull();
1299
- });
1300
- });
1301
- describe("getCachedAllocation", () => {
1302
- test("returns null when nothing is cached", async () => {
1303
- const adapter = fakeAdapter("p");
1304
- const cached = await getCachedAllocation(adapter, {
1305
- tag: "BulletInAllowance",
1306
- value: void 0
1307
- });
1308
- expect(cached).toBeNull();
1309
- });
1310
- test("disambiguates SmartContractAllowance entries by dest", async () => {
1311
- const session = makeSession({
1312
- requestResourceAllocation: async () => ok([
1313
- {
1314
- tag: "Allocated",
1315
- value: {
1316
- tag: "SmartContractAllowance",
1317
- value: void 0
1318
- }
1319
- }
1320
- ])
1321
- });
1322
- const adapter = fakeAdapter("p");
1323
- await requestResourceAllocation(session, adapter, [
1324
- { tag: "SmartContractAllowance", value: 5 }
1325
- ]);
1326
- expect(
1327
- await getCachedAllocation(adapter, { tag: "SmartContractAllowance", value: 5 })
1328
- ).toEqual({ tag: "SmartContractAllowance", dest: 5 });
1329
- expect(
1330
- await getCachedAllocation(adapter, { tag: "SmartContractAllowance", value: 6 })
1331
- ).toBeNull();
1332
- });
1333
- });
1334
- describe("ensureSlotAccountSigner", () => {
1335
- const slotKey = mnemonicToMiniSecret(DEV_PHRASE);
1336
- function bulletinAllocatedResponse() {
1337
- return ok([
1338
- {
1339
- tag: "Allocated",
1340
- value: {
1341
- tag: "BulletInAllowance",
1342
- value: { slotAccountKey: slotKey }
1343
- }
1344
- }
1345
- ]);
1346
- }
1347
- test("cache hit returns a signer without invoking the AP wire", async () => {
1348
- const captured = [];
1349
- const session = makeSession({
1350
- requestResourceAllocation: async (req) => {
1351
- captured.push(req);
1352
- return bulletinAllocatedResponse();
1353
- }
1354
- });
1355
- const adapter = fakeAdapter("p");
1356
- await requestResourceAllocation(session, adapter, [
1357
- { tag: "BulletInAllowance", value: void 0 }
1358
- ]);
1359
- expect(captured).toHaveLength(1);
1360
- const signer = await ensureSlotAccountSigner(session, adapter, {
1361
- tag: "BulletInAllowance",
1362
- value: void 0
1363
- });
1364
- expect(signer.publicKey).toBeInstanceOf(Uint8Array);
1365
- expect(captured).toHaveLength(1);
1366
- });
1367
- test("cache miss triggers allocation and returns a fresh signer", async () => {
1368
- const captured = [];
1369
- const session = makeSession({
1370
- requestResourceAllocation: async (req) => {
1371
- captured.push(req);
1372
- return bulletinAllocatedResponse();
1373
- }
1374
- });
1375
- const adapter = fakeAdapter("p");
1376
- const signer = await ensureSlotAccountSigner(session, adapter, {
1377
- tag: "BulletInAllowance",
1378
- value: void 0
1379
- });
1380
- expect(signer.publicKey).toBeInstanceOf(Uint8Array);
1381
- expect(captured).toHaveLength(1);
1382
- expect(captured[0].resources.map((r) => r.tag)).toEqual(["BulletInAllowance"]);
1383
- });
1384
- test("Rejected outcome surfaces as a throw \u2014 no signer to return", async () => {
1385
- const session = makeSession({
1386
- requestResourceAllocation: async () => ok([{ tag: "Rejected", value: void 0 }])
1387
- });
1388
- await expect(
1389
- ensureSlotAccountSigner(session, fakeAdapter("p"), {
1390
- tag: "BulletInAllowance",
1391
- value: void 0
1392
- })
1393
- ).rejects.toThrow(/allocation Rejected for BulletInAllowance/);
1394
- });
1395
- test("empty outcomes array from the wire surfaces as a length-mismatch throw", async () => {
1396
- const session = makeSession({
1397
- requestResourceAllocation: async () => ok([])
1398
- });
1399
- await expect(
1400
- ensureSlotAccountSigner(session, fakeAdapter("p"), {
1401
- tag: "BulletInAllowance",
1402
- value: void 0
1403
- })
1404
- ).rejects.toThrow(/length mismatch — requested 1, got 0/);
1405
- });
1406
- test("NotAvailable outcome surfaces as a throw", async () => {
1407
- const session = makeSession({
1408
- requestResourceAllocation: async () => ok([{ tag: "NotAvailable", value: void 0 }])
1409
- });
1410
- await expect(
1411
- ensureSlotAccountSigner(session, fakeAdapter("p"), {
1412
- tag: "StatementStoreAllowance",
1413
- value: void 0
1414
- })
1415
- ).rejects.toThrow(/allocation NotAvailable for StatementStoreAllowance/);
1416
- });
1417
- test("transport error propagates from the underlying allocation call", async () => {
1418
- const session = makeSession({
1419
- requestResourceAllocation: async () => err(new Error("mobile timed out"))
1420
- });
1421
- await expect(
1422
- ensureSlotAccountSigner(session, fakeAdapter("p"), {
1423
- tag: "BulletInAllowance",
1424
- value: void 0
1425
- })
1426
- ).rejects.toThrow(/mobile timed out/);
1427
- });
1428
- test("explicit productId scopes the wire id and the cache namespace", async () => {
1429
- const captured = [];
1430
- const session = makeSession({
1431
- requestResourceAllocation: async (req) => {
1432
- captured.push(req);
1433
- return bulletinAllocatedResponse();
1434
- }
1435
- });
1436
- const adapter = fakeAdapter("my-cli");
1437
- const signer = await ensureSlotAccountSigner(
1438
- session,
1439
- adapter,
1440
- { tag: "BulletInAllowance", value: void 0 },
1441
- "my-product.dot"
1442
- );
1443
- expect(signer.publicKey).toBeInstanceOf(Uint8Array);
1444
- expect(captured[0].callingProductId).toBe("my-product.dot");
1445
- await ensureSlotAccountSigner(
1446
- session,
1447
- adapter,
1448
- { tag: "BulletInAllowance", value: void 0 },
1449
- "my-product.dot"
1450
- );
1451
- expect(captured).toHaveLength(1);
1452
- expect(
1453
- await getCachedAllocation(adapter, { tag: "BulletInAllowance", value: void 0 })
1454
- ).toBeNull();
1455
- });
1456
- test("populates the cache so a later call is a hit", async () => {
1457
- const session = makeSession({
1458
- requestResourceAllocation: async () => bulletinAllocatedResponse()
1459
- });
1460
- const adapter = fakeAdapter("p");
1461
- await ensureSlotAccountSigner(session, adapter, {
1462
- tag: "BulletInAllowance",
1463
- value: void 0
1464
- });
1465
- const cached = await getCachedAllocation(adapter, {
1466
- tag: "BulletInAllowance",
1467
- value: void 0
1468
- });
1469
- expect(cached?.tag).toBe("BulletInAllowance");
1470
- });
1471
- test("parallel calls for the SAME resource share one allocation (no double-prompt)", async () => {
1472
- const captured = [];
1473
- const session = makeSession({
1474
- requestResourceAllocation: async (req) => {
1475
- captured.push(req);
1476
- await new Promise((r) => setTimeout(r, 5));
1477
- return bulletinAllocatedResponse();
1478
- }
1479
- });
1480
- const adapter = fakeAdapter("p");
1481
- const [signerA, signerB] = await Promise.all([
1482
- ensureSlotAccountSigner(session, adapter, {
1483
- tag: "BulletInAllowance",
1484
- value: void 0
1485
- }),
1486
- ensureSlotAccountSigner(session, adapter, {
1487
- tag: "BulletInAllowance",
1488
- value: void 0
1489
- })
1490
- ]);
1491
- expect(captured).toHaveLength(1);
1492
- expect(signerA.publicKey).toBeInstanceOf(Uint8Array);
1493
- expect(signerB.publicKey).toBeInstanceOf(Uint8Array);
1494
- expect(toHex2(signerA.publicKey)).toBe(toHex2(signerB.publicKey));
1495
- });
1496
- });
1497
- }
1498
- var fakeAdapter2;
1499
- var makeSession2;
1500
-
1501
- export {
1502
- createSlotAccountSigner,
1503
- requestResourceAllocation,
1504
- sendResourceAllocation,
1505
- getCachedAllocation,
1506
- ensureSlotAccountSigner
1507
- };
1508
- //# sourceMappingURL=chunk-YS3GW2XW.js.map