@parity/product-sdk-terminal 0.3.1 → 0.4.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
@@ -112,9 +112,88 @@ File-based storage adapter for Node.js. Data persists in `storageDir` (defaults
112
112
 
113
113
  Waits for the session list to emit at least one entry, or resolves with `[]` after `timeoutMs`.
114
114
 
115
- ## Allowance management (`./host` subpath)
115
+ ## Allowance signers — the canonical path
116
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).
117
+ For CLIs that need to write to Bulletin or publish to the Statement Store: ask the paired wallet for an allowance slot, get a `PolkadotSigner` back, sign extrinsics with it. This is what most consumers want.
118
+
119
+ ```ts
120
+ import {
121
+ createTerminalAdapter,
122
+ getBulletinSigner,
123
+ getStatementStoreProver,
124
+ waitForSessions,
125
+ } from "@parity/product-sdk-terminal";
126
+
127
+ const adapter = createTerminalAdapter({ appId: "my-cli" });
128
+
129
+ // ...QR pair the phone, wait for the session...
130
+ await waitForSessions(adapter);
131
+
132
+ // First call prompts the wallet for an allowance slot; subsequent calls
133
+ // return the cached slot key — no wire round-trip.
134
+ const bulletinSigner = await getBulletinSigner(adapter, "my-cli.dot");
135
+ await bulletinClient.tx.TransactionStorage.store({ data }).signAndSubmit(bulletinSigner);
136
+
137
+ // Same idea, returns a StatementProver for `@novasamatech/statement-store` writes.
138
+ const prover = await getStatementStoreProver(adapter, "my-cli.dot");
139
+ ```
140
+
141
+ ### `getBulletinSigner(adapter, productId, sessionId?): Promise<PolkadotSigner>`
142
+
143
+ ### `getStatementStoreProver(adapter, productId, sessionId?): Promise<StatementProver>`
144
+
145
+ Both default `sessionId` to the only paired session. With zero or more than one paired sessions and no explicit id, both throw `AllowanceError` with `reason: 'NoSession'`. Pass an explicit `sessionId` to disambiguate.
146
+
147
+ Failures from the host (`Rejected`, `NotAvailable`, codec drift) surface as `AllowanceError`:
148
+
149
+ ```ts
150
+ import { AllowanceError } from "@parity/product-sdk-terminal";
151
+
152
+ try {
153
+ const signer = await getBulletinSigner(adapter, "my-cli.dot");
154
+ } catch (err) {
155
+ if (err instanceof AllowanceError && err.reason === "Rejected") {
156
+ // user denied the allowance request on the phone
157
+ }
158
+ throw err;
159
+ }
160
+ ```
161
+
162
+ Behind the scenes both helpers wrap `adapter.allowance` (inherited from host-papp's `PappAdapter`) and unwrap its neverthrow `ResultAsync` into a throwing `Promise`. If you want to keep neverthrow's `.match()` / `.mapErr()` ergonomics or need explicit multi-session handling, call `adapter.allowance.getBulletinSigner(sessionId, productId)` / `getStatementStoreProver(...)` directly.
163
+
164
+ ### Cache-only probes — `hasBulletinAllowance` / `hasStatementStoreAllowance`
165
+
166
+ For paths that must run silently — login health checks, "would calling `getBulletinSigner` prompt the phone?" decisions, readiness probes — use the cache-only variants. They read host-papp's on-disk allowance file directly and **never** trigger a wallet prompt.
167
+
168
+ ```ts
169
+ import {
170
+ getBulletinSigner,
171
+ hasBulletinAllowance,
172
+ } from "@parity/product-sdk-terminal";
173
+
174
+ if (await hasBulletinAllowance(adapter, "my-cli.dot")) {
175
+ // happy path — fetch the signer without risking a wallet prompt
176
+ const signer = await getBulletinSigner(adapter, "my-cli.dot");
177
+ } else {
178
+ // tell the user a wallet prompt will fire, then call getBulletinSigner
179
+ console.log("Approve the allowance request on your phone…");
180
+ const signer = await getBulletinSigner(adapter, "my-cli.dot");
181
+ }
182
+ ```
183
+
184
+ #### `hasBulletinAllowance(adapter, productId, sessionId?): Promise<boolean>`
185
+
186
+ #### `hasStatementStoreAllowance(adapter, productId, sessionId?): Promise<boolean>`
187
+
188
+ Resolves `true` when a slot key for `(sessionId, productId, resource)` is already cached on disk; `false` when it is not (file absent, or present with no matching entry). Same `sessionId` defaulting as the fetching helpers: defaults to the only paired session, throws `AllowanceError('NoSession')` for zero or multiple paired sessions without an explicit id.
189
+
190
+ Rejects only on decrypt or decode failures — a corrupted cache file is a real failure, not a "no allowance" signal.
191
+
192
+ > The cache-only check uses an internal mirror of host-papp's `AllowanceRepository` codec because host-papp doesn't expose a public probe today. We'll switch to the upstream API when one ships; the public surface (`hasBulletinAllowance` / `hasStatementStoreAllowance`) won't change.
193
+
194
+ ## Allowance management — manual cache + AP control (`./host` subpath)
195
+
196
+ For CLIs that need finer control — pre-allocating multiple resources in one wallet prompt, inspecting the cache before round-tripping, or building a signer over a cached key without consulting the wallet — the `@parity/product-sdk-terminal/host` subpath exposes the lower-level 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). Most CLIs don't need this — start with `getBulletinSigner` / `getStatementStoreProver` above.
118
197
 
119
198
  ```ts
120
199
  import {
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { T as TerminalAdapter } from './adapter-Dt3ryKnw.js';
2
2
  export { a as TerminalAdapterOptions, c as createTerminalAdapter } from './adapter-Dt3ryKnw.js';
3
3
  import { UserSession } from '@novasamatech/host-papp';
4
- export { HostMetadata, PairingStatus, PappAdapter, SS_PASEO_STABLE_STAGE_ENDPOINTS, SS_STABLE_STAGE_ENDPOINTS, SigningPayloadRequest, SigningPayloadResponse, SigningRawRequest, StoredUserSession, UserSession } from '@novasamatech/host-papp';
4
+ export { AllowanceError, AllowanceErrorReason, AllowanceService, HostMetadata, PairingStatus, PappAdapter, SS_PASEO_STABLE_STAGE_ENDPOINTS, SS_STABLE_STAGE_ENDPOINTS, SigningPayloadRequest, SigningPayloadResponse, SigningRawRequest, StoredUserSession, UserSession } from '@novasamatech/host-papp';
5
5
  import { PolkadotSigner } from 'polkadot-api';
6
+ import { StatementProver } from '@novasamatech/statement-store';
6
7
  import { StorageAdapter } from '@novasamatech/storage-adapter';
7
8
 
8
9
  /**
@@ -100,6 +101,81 @@ declare function createSessionSigner(session: UserSession, adapter: TerminalAdap
100
101
  */
101
102
  declare function createSessionSignerForAccount(session: UserSession, ref: ProductAccountRef): PolkadotSigner;
102
103
 
104
+ /**
105
+ * Get a `PolkadotSigner` for a Bulletin allowance slot.
106
+ *
107
+ * Allocates an allowance slot via the paired wallet (or returns the cached
108
+ * one), derives the slot-account keypair, and returns a `PolkadotSigner`
109
+ * that signs Bulletin extrinsics with it. Replaces the manual
110
+ * `requestResourceAllocation` + `createSlotAccountSigner` two-step for the
111
+ * common case.
112
+ *
113
+ * @param adapter Terminal adapter.
114
+ * @param productId The product id the slot is allocated under. Passed to the
115
+ * host as the calling product id in the allowance request.
116
+ * @param sessionId Paired session to allocate against. Defaults to the only
117
+ * paired session; throws `AllowanceError('NoSession')` when zero or more
118
+ * than one sessions are paired and no explicit id is supplied.
119
+ * @throws {AllowanceError} On rejection, missing session, host-side failure,
120
+ * or unexpected response shape.
121
+ *
122
+ * @example
123
+ * ```ts
124
+ * import { createTerminalAdapter, getBulletinSigner } from "@parity/product-sdk-terminal";
125
+ *
126
+ * const adapter = createTerminalAdapter({ appId: "my-cli" });
127
+ * // ... QR pair, wait for session ...
128
+ * const signer = await getBulletinSigner(adapter, "my-cli.dot");
129
+ * await client.bulletin.tx.TransactionStorage.store({ data }).signAndSubmit(signer);
130
+ * ```
131
+ */
132
+ declare function getBulletinSigner(adapter: TerminalAdapter, productId: string, sessionId?: string): Promise<PolkadotSigner>;
133
+ /**
134
+ * Get a `StatementProver` for a statement-store allowance slot.
135
+ *
136
+ * Allocates an allowance slot via the paired wallet (or returns the cached
137
+ * one) and returns the upstream `StatementProver` for the slot. Use when
138
+ * publishing statements through `@novasamatech/statement-store` without
139
+ * holding a long-lived key yourself.
140
+ *
141
+ * @param adapter Terminal adapter.
142
+ * @param productId The product id the slot is allocated under.
143
+ * @param sessionId Paired session to allocate against. Defaults to the only
144
+ * paired session; throws `AllowanceError('NoSession')` when zero or more
145
+ * than one sessions are paired and no explicit id is supplied.
146
+ * @throws {AllowanceError} On rejection, missing session, host-side failure,
147
+ * or unexpected response shape.
148
+ */
149
+ declare function getStatementStoreProver(adapter: TerminalAdapter, productId: string, sessionId?: string): Promise<StatementProver>;
150
+ /**
151
+ * Cache-only probe for a Bulletin allowance slot. Resolves `true` when a
152
+ * slot key for `(sessionId, productId, bulletin)` is already cached on
153
+ * disk; `false` when it is not. Never prompts the paired wallet.
154
+ *
155
+ * Pair with {@link getBulletinSigner} for the "check first, fetch only
156
+ * if needed" flow:
157
+ *
158
+ * @example
159
+ * ```ts
160
+ * if (await hasBulletinAllowance(adapter, "my-cli.dot")) {
161
+ * // happy path — fetch the signer without risking a wallet prompt
162
+ * const signer = await getBulletinSigner(adapter, "my-cli.dot");
163
+ * } else {
164
+ * // tell the user a wallet prompt will fire, then call getBulletinSigner
165
+ * }
166
+ * ```
167
+ */
168
+ declare function hasBulletinAllowance(adapter: TerminalAdapter, productId: string, sessionId?: string): Promise<boolean>;
169
+ /**
170
+ * Cache-only probe for a Statement Store allowance slot. Resolves `true`
171
+ * when a slot key for `(sessionId, productId, statementStore)` is already
172
+ * cached on disk; `false` when it is not. Never prompts the paired wallet.
173
+ *
174
+ * Pair with {@link getStatementStoreProver} for the "check first, fetch
175
+ * only if needed" flow.
176
+ */
177
+ declare function hasStatementStoreAllowance(adapter: TerminalAdapter, productId: string, sessionId?: string): Promise<boolean>;
178
+
103
179
  /**
104
180
  * Helpers for working with persisted sessions on a `TerminalAdapter`.
105
181
  */
@@ -152,4 +228,4 @@ declare function renderQrCode(data: string, options?: QrRenderOptions): Promise<
152
228
  */
153
229
  declare function createNodeStorageAdapter(appId: string, storageDir?: string): StorageAdapter;
154
230
 
155
- export { type ProductAccountRef, type QrRenderOptions, TerminalAdapter, createNodeStorageAdapter, createSessionSigner, createSessionSignerForAccount, renderQrCode, waitForSessions };
231
+ export { type ProductAccountRef, type QrRenderOptions, TerminalAdapter, createNodeStorageAdapter, createSessionSigner, createSessionSignerForAccount, getBulletinSigner, getStatementStoreProver, hasBulletinAllowance, hasStatementStoreAllowance, renderQrCode, waitForSessions };
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
- createNodeStorageAdapter
2
+ createNodeStorageAdapter,
3
+ sanitizeKey
3
4
  } from "./chunk-GHOFKVUG.js";
4
5
 
5
6
  // src/adapter.ts
@@ -616,6 +617,250 @@ var makeSession2;
616
617
  var ext2;
617
618
  var fakeAdapter2;
618
619
 
620
+ // src/allowance.ts
621
+ import { AllowanceError } from "@novasamatech/host-papp";
622
+
623
+ // src/allowance-cache.ts
624
+ import { gcm } from "@noble/ciphers/aes.js";
625
+ import { blake2b } from "@noble/hashes/blake2.js";
626
+ import { fromHex, toHex } from "@polkadot-api/utils";
627
+ import { readFile, writeFile } from "fs/promises";
628
+ import { join } from "path";
629
+ import { Bytes, Enum, Struct, Vector, _void, str } from "scale-ts";
630
+ var AllowanceResourceKindCodec = Enum({
631
+ bulletin: _void,
632
+ statementStore: _void
633
+ });
634
+ var StoredAllowanceEntryCodec = Struct({
635
+ productId: str,
636
+ resource: AllowanceResourceKindCodec,
637
+ slotAccountKey: Bytes()
638
+ });
639
+ var StoredAllowancesCodec = Vector(StoredAllowanceEntryCodec);
640
+ function getAes(appId) {
641
+ const key = blake2b(new TextEncoder().encode(appId), { dkLen: 16 });
642
+ const nonce = blake2b(new TextEncoder().encode("nonce"), { dkLen: 32 });
643
+ return gcm(key, nonce);
644
+ }
645
+ function allowanceFilePath(storageDir, appId, sessionId) {
646
+ return join(storageDir, `${sanitizeKey(appId, `AllowanceKeys_${sessionId}`)}.json`);
647
+ }
648
+ async function readStoredAllowances(storageDir, appId, sessionId) {
649
+ let hex;
650
+ try {
651
+ hex = await readFile(allowanceFilePath(storageDir, appId, sessionId), "utf-8");
652
+ } catch (err) {
653
+ if (err.code === "ENOENT") {
654
+ return [];
655
+ }
656
+ throw err;
657
+ }
658
+ const decrypted = getAes(appId).decrypt(fromHex(hex));
659
+ return StoredAllowancesCodec.dec(decrypted);
660
+ }
661
+
662
+ // src/allowance.ts
663
+ function resolveSessionId(adapter, sessionId) {
664
+ if (sessionId) return sessionId;
665
+ const sessions = adapter.sessions.sessions.read();
666
+ if (sessions.length === 0) {
667
+ throw new AllowanceError("NoSession", "No paired session \u2014 pair a phone first.");
668
+ }
669
+ if (sessions.length > 1) {
670
+ throw new AllowanceError(
671
+ "NoSession",
672
+ `Multiple paired sessions (${sessions.length}) \u2014 pass an explicit sessionId.`
673
+ );
674
+ }
675
+ return sessions[0].id;
676
+ }
677
+ async function getBulletinSigner(adapter, productId, sessionId) {
678
+ const resolvedSessionId = resolveSessionId(adapter, sessionId);
679
+ return adapter.allowance.getBulletinSigner(resolvedSessionId, productId).match(
680
+ (signer) => signer,
681
+ (err) => {
682
+ throw err;
683
+ }
684
+ );
685
+ }
686
+ async function getStatementStoreProver(adapter, productId, sessionId) {
687
+ const resolvedSessionId = resolveSessionId(adapter, sessionId);
688
+ return adapter.allowance.getStatementStoreProver(resolvedSessionId, productId).match(
689
+ (prover) => prover,
690
+ (err) => {
691
+ throw err;
692
+ }
693
+ );
694
+ }
695
+ async function hasAllowanceForResource(adapter, productId, resource, sessionId) {
696
+ const resolvedSessionId = resolveSessionId(adapter, sessionId);
697
+ if (adapter.storageDir === void 0) {
698
+ return false;
699
+ }
700
+ const entries = await readStoredAllowances(
701
+ adapter.storageDir,
702
+ adapter.appId,
703
+ resolvedSessionId
704
+ );
705
+ return entries.some(
706
+ (entry) => entry.productId === productId && entry.resource.tag === resource
707
+ );
708
+ }
709
+ async function hasBulletinAllowance(adapter, productId, sessionId) {
710
+ return hasAllowanceForResource(adapter, productId, "bulletin", sessionId);
711
+ }
712
+ async function hasStatementStoreAllowance(adapter, productId, sessionId) {
713
+ return hasAllowanceForResource(adapter, productId, "statementStore", sessionId);
714
+ }
715
+ if (void 0) {
716
+ let makeAdapter = function(opts) {
717
+ return {
718
+ sessions: {
719
+ sessions: {
720
+ read: () => opts.sessions
721
+ }
722
+ },
723
+ allowance: {
724
+ getBulletinSigner: opts.getBulletinSigner ?? vi.fn(() => ok({})),
725
+ getStatementStoreProver: opts.getStatementStoreProver ?? vi.fn(() => ok({}))
726
+ }
727
+ // The convenience never touches the rest of the adapter; leave it unstubbed.
728
+ };
729
+ };
730
+ makeAdapter2 = makeAdapter;
731
+ const { describe, test, expect, vi } = void 0;
732
+ const { ok, err } = await null;
733
+ describe("getBulletinSigner \u2014 sessionId defaulting", () => {
734
+ test("uses the only paired session when sessionId is omitted", async () => {
735
+ const fakeSigner = { _tag: "signer" };
736
+ const getBulletinSignerFn = vi.fn(() => ok(fakeSigner));
737
+ const adapter = makeAdapter({
738
+ sessions: [{ id: "sess-only" }],
739
+ getBulletinSigner: getBulletinSignerFn
740
+ });
741
+ const result = await getBulletinSigner(adapter, "my-app.dot");
742
+ expect(result).toBe(fakeSigner);
743
+ expect(getBulletinSignerFn).toHaveBeenCalledWith("sess-only", "my-app.dot");
744
+ });
745
+ test("uses the explicit sessionId when supplied", async () => {
746
+ const fakeSigner = { _tag: "signer" };
747
+ const getBulletinSignerFn = vi.fn(() => ok(fakeSigner));
748
+ const adapter = makeAdapter({
749
+ sessions: [{ id: "sess-a" }, { id: "sess-b" }],
750
+ getBulletinSigner: getBulletinSignerFn
751
+ });
752
+ await getBulletinSigner(adapter, "my-app.dot", "sess-b");
753
+ expect(getBulletinSignerFn).toHaveBeenCalledWith("sess-b", "my-app.dot");
754
+ });
755
+ test("throws AllowanceError('NoSession') when zero sessions and no id", async () => {
756
+ const adapter = makeAdapter({ sessions: [] });
757
+ await expect(getBulletinSigner(adapter, "my-app.dot")).rejects.toBeInstanceOf(
758
+ AllowanceError
759
+ );
760
+ await expect(getBulletinSigner(adapter, "my-app.dot")).rejects.toMatchObject({
761
+ reason: "NoSession"
762
+ });
763
+ });
764
+ test("throws AllowanceError('NoSession') when multiple sessions and no id", async () => {
765
+ const adapter = makeAdapter({ sessions: [{ id: "a" }, { id: "b" }] });
766
+ await expect(getBulletinSigner(adapter, "my-app.dot")).rejects.toBeInstanceOf(
767
+ AllowanceError
768
+ );
769
+ await expect(getBulletinSigner(adapter, "my-app.dot")).rejects.toMatchObject({
770
+ reason: "NoSession"
771
+ });
772
+ });
773
+ });
774
+ describe("getBulletinSigner \u2014 error unwrapping", () => {
775
+ test("rethrows the AllowanceError from the underlying service as a thrown exception", async () => {
776
+ const underlyingErr = new AllowanceError("Rejected", "user said no");
777
+ const adapter = makeAdapter({
778
+ sessions: [{ id: "s" }],
779
+ getBulletinSigner: vi.fn(() => err(underlyingErr))
780
+ });
781
+ await expect(getBulletinSigner(adapter, "p")).rejects.toBe(underlyingErr);
782
+ });
783
+ });
784
+ describe("getStatementStoreProver \u2014 same defaulting", () => {
785
+ test("uses the only paired session when sessionId is omitted", async () => {
786
+ const fakeProver = { _tag: "prover" };
787
+ const getStatementStoreProverFn = vi.fn(() => ok(fakeProver));
788
+ const adapter = makeAdapter({
789
+ sessions: [{ id: "sess-only" }],
790
+ getStatementStoreProver: getStatementStoreProverFn
791
+ });
792
+ const result = await getStatementStoreProver(adapter, "my-app.dot");
793
+ expect(result).toBe(fakeProver);
794
+ expect(getStatementStoreProverFn).toHaveBeenCalledWith("sess-only", "my-app.dot");
795
+ });
796
+ test("uses the explicit sessionId when supplied", async () => {
797
+ const fakeProver = { _tag: "prover" };
798
+ const getStatementStoreProverFn = vi.fn(() => ok(fakeProver));
799
+ const adapter = makeAdapter({
800
+ sessions: [{ id: "sess-a" }, { id: "sess-b" }],
801
+ getStatementStoreProver: getStatementStoreProverFn
802
+ });
803
+ await getStatementStoreProver(adapter, "my-app.dot", "sess-b");
804
+ expect(getStatementStoreProverFn).toHaveBeenCalledWith("sess-b", "my-app.dot");
805
+ });
806
+ test("throws AllowanceError('NoSession') when zero sessions and no id", async () => {
807
+ const adapter = makeAdapter({ sessions: [] });
808
+ await expect(getStatementStoreProver(adapter, "my-app.dot")).rejects.toBeInstanceOf(
809
+ AllowanceError
810
+ );
811
+ await expect(getStatementStoreProver(adapter, "my-app.dot")).rejects.toMatchObject({
812
+ reason: "NoSession"
813
+ });
814
+ });
815
+ test("throws AllowanceError('NoSession') when multiple sessions and no id", async () => {
816
+ const adapter = makeAdapter({ sessions: [{ id: "a" }, { id: "b" }] });
817
+ await expect(getStatementStoreProver(adapter, "my-app.dot")).rejects.toBeInstanceOf(
818
+ AllowanceError
819
+ );
820
+ await expect(getStatementStoreProver(adapter, "my-app.dot")).rejects.toMatchObject({
821
+ reason: "NoSession"
822
+ });
823
+ });
824
+ });
825
+ describe("getStatementStoreProver \u2014 error unwrapping", () => {
826
+ test("rethrows the AllowanceError from the underlying service as a thrown exception", async () => {
827
+ const underlyingErr = new AllowanceError("Rejected", "user said no");
828
+ const adapter = makeAdapter({
829
+ sessions: [{ id: "s" }],
830
+ getStatementStoreProver: vi.fn(() => err(underlyingErr))
831
+ });
832
+ await expect(getStatementStoreProver(adapter, "p")).rejects.toBe(underlyingErr);
833
+ });
834
+ });
835
+ describe("has*Allowance \u2014 sessionId defaulting + storageDir fallback", () => {
836
+ test("hasBulletinAllowance throws AllowanceError('NoSession') when zero sessions and no id", async () => {
837
+ const adapter = makeAdapter({ sessions: [] });
838
+ await expect(hasBulletinAllowance(adapter, "p")).rejects.toBeInstanceOf(AllowanceError);
839
+ await expect(hasBulletinAllowance(adapter, "p")).rejects.toMatchObject({
840
+ reason: "NoSession"
841
+ });
842
+ });
843
+ test("hasBulletinAllowance returns false (conservative) when storageDir is undefined", async () => {
844
+ const adapter = makeAdapter({ sessions: [{ id: "sess-only" }] });
845
+ expect(await hasBulletinAllowance(adapter, "p")).toBe(false);
846
+ });
847
+ test("hasStatementStoreAllowance throws AllowanceError('NoSession') when zero sessions and no id", async () => {
848
+ const adapter = makeAdapter({ sessions: [] });
849
+ await expect(hasStatementStoreAllowance(adapter, "p")).rejects.toBeInstanceOf(
850
+ AllowanceError
851
+ );
852
+ });
853
+ test("hasStatementStoreAllowance returns false (conservative) when storageDir is undefined", async () => {
854
+ const adapter = makeAdapter({ sessions: [{ id: "sess-only" }] });
855
+ expect(await hasStatementStoreAllowance(adapter, "p")).toBe(false);
856
+ });
857
+ });
858
+ }
859
+ var makeAdapter2;
860
+
861
+ // src/index.ts
862
+ import { AllowanceError as AllowanceError2 } from "@novasamatech/host-papp";
863
+
619
864
  // src/sessions.ts
620
865
  function waitForSessions(adapter, timeoutMs = 3e3) {
621
866
  return new Promise((resolve) => {
@@ -763,12 +1008,17 @@ if (void 0) {
763
1008
  });
764
1009
  }
765
1010
  export {
1011
+ AllowanceError2 as AllowanceError,
766
1012
  SS_PASEO_STABLE_STAGE_ENDPOINTS,
767
1013
  SS_STABLE_STAGE_ENDPOINTS,
768
1014
  createNodeStorageAdapter,
769
1015
  createSessionSigner,
770
1016
  createSessionSignerForAccount,
771
1017
  createTerminalAdapter,
1018
+ getBulletinSigner,
1019
+ getStatementStoreProver,
1020
+ hasBulletinAllowance,
1021
+ hasStatementStoreAllowance,
772
1022
  renderQrCode,
773
1023
  waitForSessions
774
1024
  };