@parity/product-deploy 0.8.3 → 0.9.0-rc.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.
Files changed (57) hide show
  1. package/LICENSE +674 -201
  2. package/README.md +9 -3
  3. package/assets/environments.json +0 -1
  4. package/dist/auth/index.d.ts +2 -3
  5. package/dist/auth/index.js +1 -1
  6. package/dist/auth/vendor/index.d.ts +2 -3
  7. package/dist/auth/vendor/index.js +1 -1
  8. package/dist/auth/vendor/ui/index.d.ts +1 -2
  9. package/dist/auth-C-Pel0AT.d.ts +235 -0
  10. package/dist/auth-config.d.ts +30 -8
  11. package/dist/auth-config.js +14 -6
  12. package/dist/bug-report.js +4 -4
  13. package/dist/{chunk-XM5MFMMX.js → chunk-2SR5D4CP.js} +19 -15
  14. package/dist/{chunk-HK3B5MKA.js → chunk-6NW5M3F5.js} +9 -20
  15. package/dist/{chunk-2OZVKA3D.js → chunk-DHY2ZXVZ.js} +36 -28
  16. package/dist/chunk-G56VYTUD.js +75 -0
  17. package/dist/{chunk-5K3RI5C2.js → chunk-GL3U7K2B.js} +0 -1
  18. package/dist/{chunk-YUSHBZBX.js → chunk-H4LWILH4.js} +32 -6
  19. package/dist/chunk-J7CYVTAW.js +43 -0
  20. package/dist/{chunk-WF2XKCEZ.js → chunk-LCKLYFAZ.js} +1 -1
  21. package/dist/{chunk-3LNKPUZ7.js → chunk-NP4SLURL.js} +1 -1
  22. package/dist/{chunk-LRRQP7DH.js → chunk-O2NWQLYB.js} +2 -2
  23. package/dist/{chunk-DKGCOGRT.js → chunk-RD2QHYTL.js} +144 -37
  24. package/dist/{chunk-S7S7WENB.js → chunk-WZBAQCA5.js} +2 -2
  25. package/dist/{chunk-JMHX3M4C.js → chunk-YIKGVALU.js} +3 -3
  26. package/dist/chunk-probe.js +3 -3
  27. package/dist/commands/login.d.ts +34 -9
  28. package/dist/commands/login.js +171 -68
  29. package/dist/commands/logout.d.ts +1 -2
  30. package/dist/commands/logout.js +5 -3
  31. package/dist/commands/whoami.d.ts +1 -2
  32. package/dist/commands/whoami.js +7 -4
  33. package/dist/deploy.d.ts +7 -1
  34. package/dist/deploy.js +12 -9
  35. package/dist/dotns.js +4 -4
  36. package/dist/environments.js +1 -1
  37. package/dist/index.js +11 -10
  38. package/dist/manifest/publish.js +11 -10
  39. package/dist/manifest/types.d.ts +1 -1
  40. package/dist/memory-report.js +2 -2
  41. package/dist/merkle.js +10 -9
  42. package/dist/personhood/bootstrap.js +10 -10
  43. package/dist/personhood/people-client.js +4 -4
  44. package/dist/run-state.js +1 -1
  45. package/dist/{signer-CriGqahj.d.ts → signer-vR6KKC7V.d.ts} +1 -1
  46. package/dist/spinner.d.ts +21 -0
  47. package/dist/spinner.js +6 -0
  48. package/dist/sss-allowance.d.ts +29 -0
  49. package/dist/sss-allowance.js +8 -0
  50. package/dist/storage-signer.d.ts +94 -2
  51. package/dist/storage-signer.js +18 -9
  52. package/dist/telemetry.js +2 -2
  53. package/dist/version-check.d.ts +0 -3
  54. package/dist/version-check.js +3 -3
  55. package/package.json +19 -15
  56. package/dist/allocations-B65Is4Md.d.ts +0 -97
  57. package/dist/auth-DkRZBK-T.d.ts +0 -122
@@ -4,7 +4,7 @@ import {
4
4
 
5
5
  // src/auth/vendor/auth.ts
6
6
  import { readdir, unlink } from "fs/promises";
7
- import { homedir } from "os";
7
+ import { homedir, platform, release } from "os";
8
8
  import { join } from "path";
9
9
  import { deriveH160, ss58Encode } from "@parity/product-sdk-address";
10
10
  import {
@@ -95,22 +95,18 @@ function createSessionSigner(session, ref) {
95
95
  }
96
96
 
97
97
  // src/auth/vendor/allocations.ts
98
+ import {
99
+ requestResourceAllocation as terminalRequestResourceAllocation
100
+ } from "@parity/product-sdk-terminal/host";
98
101
  var DEFAULT_RESOURCES = [
99
102
  { tag: "BulletInAllowance", value: void 0 },
100
103
  { tag: "StatementStoreAllowance", value: void 0 },
101
104
  // derivation index 0 = the default product account.
102
105
  { tag: "SmartContractAllowance", value: 0 }
103
106
  ];
104
- async function requestResourceAllocation(session, productId, resources = DEFAULT_RESOURCES, onExisting = "Ignore") {
105
- const result = await session.requestResourceAllocation({
106
- callingProductId: productId,
107
- resources,
108
- onExisting
109
- });
110
- if (result.isErr()) {
111
- throw new Error(`Resource allocation request failed: ${result.error.message}`);
112
- }
113
- return result.value;
107
+ async function requestResourceAllocation(session, adapter, resources = DEFAULT_RESOURCES, onExisting = "Ignore") {
108
+ const outcomes = await terminalRequestResourceAllocation(session, adapter, resources, { onExisting });
109
+ return outcomes;
114
110
  }
115
111
  function summarizeOutcomes(outcomes, resources) {
116
112
  const granted = [];
@@ -133,8 +129,13 @@ function createAuthClient(config) {
133
129
  function createAdapter() {
134
130
  return createTerminalAdapter({
135
131
  appId: config.dappId,
136
- metadataUrl: config.metadataUrl,
137
- endpoints: config.peopleEndpoints
132
+ endpoints: config.peopleEndpoints,
133
+ hostMetadata: {
134
+ hostName: config.hostName,
135
+ hostVersion: config.hostVersion,
136
+ platformType: platform(),
137
+ platformVersion: release()
138
+ }
138
139
  });
139
140
  }
140
141
  function deriveSessionAddresses(session) {
@@ -166,6 +167,8 @@ function createAuthClient(config) {
166
167
  const sessions = await waitForSessions(adapter);
167
168
  if (sessions.length > 0) {
168
169
  const addresses = deriveSessionAddresses(sessions[0]);
170
+ adapter.destroy().catch(() => {
171
+ });
169
172
  return { kind: "existing", address: addresses.productAddress, addresses };
170
173
  }
171
174
  const authPromise = adapter.sso.authenticate();
@@ -214,7 +217,7 @@ function createAuthClient(config) {
214
217
  }
215
218
  });
216
219
  let authenticated = false;
217
- let address = null;
220
+ let handle = null;
218
221
  try {
219
222
  const result = await authPromise;
220
223
  result.match(
@@ -230,8 +233,8 @@ function createAuthClient(config) {
230
233
  if (authenticated) {
231
234
  const sessions = await waitForSessions(adapter, 3e3);
232
235
  if (sessions.length > 0) {
233
- const addresses = deriveSessionAddresses(sessions[0]);
234
- address = addresses.productAddress;
236
+ handle = buildSessionHandle(adapter, sessions[0]);
237
+ const { address, addresses } = handle;
235
238
  onStatus({ step: "success", address, addresses });
236
239
  } else {
237
240
  onStatus({
@@ -243,17 +246,9 @@ function createAuthClient(config) {
243
246
  } finally {
244
247
  unsubPairing();
245
248
  }
246
- return address;
249
+ return handle;
247
250
  }
248
- async function getSessionSigner() {
249
- const adapter = createAdapter();
250
- const sessions = await waitForSessions(adapter, 3e3);
251
- if (sessions.length === 0) {
252
- adapter.destroy().catch(() => {
253
- });
254
- return null;
255
- }
256
- const session = sessions[0];
251
+ function buildSessionHandle(adapter, session) {
257
252
  const signer = createSigner(session);
258
253
  const addresses = deriveSessionAddresses(session);
259
254
  let destroyed = false;
@@ -268,11 +263,22 @@ function createAuthClient(config) {
268
263
  addresses,
269
264
  signer,
270
265
  userSession: session,
266
+ adapter,
271
267
  destroy
272
268
  };
273
269
  }
274
- async function requestAllocation(session, resources = DEFAULT_RESOURCES, onExisting = "Ignore") {
275
- return requestResourceAllocation(session, config.productId, resources, onExisting);
270
+ async function getSessionSigner() {
271
+ const adapter = createAdapter();
272
+ const sessions = await waitForSessions(adapter, 3e3);
273
+ if (sessions.length === 0) {
274
+ adapter.destroy().catch(() => {
275
+ });
276
+ return null;
277
+ }
278
+ return buildSessionHandle(adapter, sessions[0]);
279
+ }
280
+ async function requestAllocation(session, adapter, resources = DEFAULT_RESOURCES, onExisting = "Ignore") {
281
+ return requestResourceAllocation(session, adapter, resources, onExisting);
276
282
  }
277
283
  async function findSession() {
278
284
  const adapter = createAdapter();
@@ -292,6 +298,8 @@ function createAuthClient(config) {
292
298
  const { adapter, address, session } = handle;
293
299
  try {
294
300
  onStatus({ step: "disconnecting", address });
301
+ await Promise.resolve(session.abortPendingRequests()).catch(() => {
302
+ });
295
303
  const result = await adapter.sessions.disconnect(session);
296
304
  if (result.isOk()) {
297
305
  await clearLocalAppStorage();
@@ -0,0 +1,75 @@
1
+ // src/sss-allowance.ts
2
+ import WebSocket from "ws";
3
+ var SSS_PREFIX = new TextEncoder().encode(":statement_allowance:");
4
+ function sssStorageKey(pubkey) {
5
+ if (pubkey.length !== 32) {
6
+ throw new Error(`SSS storage key requires a 32-byte public key, got ${pubkey.length} bytes`);
7
+ }
8
+ const full = new Uint8Array(SSS_PREFIX.length + pubkey.length);
9
+ full.set(SSS_PREFIX, 0);
10
+ full.set(pubkey, SSS_PREFIX.length);
11
+ return "0x" + Buffer.from(full).toString("hex");
12
+ }
13
+ function checkSSSAllowance(pubkey, peopleEndpoints, timeoutMs = 5e3) {
14
+ return new Promise((resolve) => {
15
+ const endpoint = peopleEndpoints[0];
16
+ if (!endpoint) {
17
+ resolve(null);
18
+ return;
19
+ }
20
+ let settled = false;
21
+ let timer = null;
22
+ function done(value) {
23
+ if (settled) return;
24
+ settled = true;
25
+ if (timer !== null) clearTimeout(timer);
26
+ try {
27
+ ws.terminate();
28
+ } catch {
29
+ }
30
+ resolve(value);
31
+ }
32
+ let ws;
33
+ try {
34
+ ws = new WebSocket(endpoint);
35
+ } catch {
36
+ resolve(null);
37
+ return;
38
+ }
39
+ timer = setTimeout(() => done(null), timeoutMs);
40
+ ws.on("error", () => done(null));
41
+ ws.on("open", () => {
42
+ let storageKey;
43
+ try {
44
+ storageKey = sssStorageKey(pubkey);
45
+ } catch {
46
+ done(null);
47
+ return;
48
+ }
49
+ const request = JSON.stringify({
50
+ jsonrpc: "2.0",
51
+ id: 1,
52
+ method: "state_getStorage",
53
+ params: [storageKey]
54
+ });
55
+ ws.send(request, (err) => {
56
+ if (err) done(null);
57
+ });
58
+ });
59
+ ws.on("message", (data) => {
60
+ try {
61
+ const msg = JSON.parse(data.toString());
62
+ const result = msg.result;
63
+ const present = result != null && result !== "0x";
64
+ done(present);
65
+ } catch {
66
+ done(null);
67
+ }
68
+ });
69
+ });
70
+ }
71
+
72
+ export {
73
+ sssStorageKey,
74
+ checkSSSAllowance
75
+ };
@@ -108,7 +108,6 @@ var environments_default = {
108
108
  e2eEligible: true,
109
109
  backend: "https://identity-backend-next.parity-testnet.parity.io",
110
110
  ipfs: "https://paseo-bulletin-next-ipfs.polkadot.io",
111
- docsUrl: "https://sre.teleport.parity.io/environments/paseo-next/",
112
111
  autoAccountMapping: true,
113
112
  nativeToEthRatio: 1e8,
114
113
  registerStorageDeposit: 2e12,
@@ -1,17 +1,28 @@
1
+ import {
2
+ VERSION
3
+ } from "./chunk-LCKLYFAZ.js";
1
4
  import {
2
5
  loadEnvironments
3
- } from "./chunk-5K3RI5C2.js";
6
+ } from "./chunk-GL3U7K2B.js";
4
7
 
5
8
  // src/auth-config.ts
6
- import { existsSync } from "fs";
9
+ import { existsSync, readdirSync } from "fs";
7
10
  import { homedir } from "os";
8
11
  import { join } from "path";
9
12
  var DOT_DAPP_ID = "dot-cli";
10
13
  var DOT_PRODUCT_ID = "playground.dot";
11
14
  var DOT_DERIVATION_INDEX = 0;
12
- var DOT_TERMINAL_METADATA_URL = "https://gist.githubusercontent.com/ReinhardHatko/1967dd3f4afe78683cc0ba14d6ec8744/raw/c1625eb7ed7671b7e09a3fa2a25998dde33c70b8/metadata.json";
15
+ var DOT_HOST_NAME = "bulletin-deploy";
16
+ var STALE_SESSION_MESSAGE = 'Stored login session could not be read \u2014 it may have been written by an older version. Run "bulletin-deploy logout", then "bulletin-deploy login" to pair again.';
13
17
  function hasPersistedSession() {
14
- return existsSync(join(homedir(), ".polkadot-apps", `${DOT_DAPP_ID}_SsoSessions.json`));
18
+ const dir = join(homedir(), ".polkadot-apps");
19
+ if (!existsSync(dir)) return false;
20
+ const prefix = `${DOT_DAPP_ID}_SsoSessions`;
21
+ try {
22
+ return readdirSync(dir).some((f) => f.startsWith(prefix) && f.endsWith(".json"));
23
+ } catch {
24
+ return false;
25
+ }
15
26
  }
16
27
  function buildAuthConfig(doc, envId) {
17
28
  const peopleChain = doc.chains.find((c) => c.id === "people");
@@ -31,10 +42,22 @@ function buildAuthConfig(doc, envId) {
31
42
  dappId: DOT_DAPP_ID,
32
43
  productId: DOT_PRODUCT_ID,
33
44
  derivationIndex: DOT_DERIVATION_INDEX,
34
- metadataUrl: DOT_TERMINAL_METADATA_URL,
45
+ hostName: DOT_HOST_NAME,
46
+ hostVersion: VERSION,
35
47
  peopleEndpoints
36
48
  };
37
49
  }
50
+ function resolveBulletinEndpoints(doc, envId) {
51
+ const bulletinChain = doc.chains.find((c) => c.id === "bulletin");
52
+ const endpoint = bulletinChain?.endpoints[envId];
53
+ if (!endpoint) return null;
54
+ return Array.isArray(endpoint.wss) ? endpoint.wss : [endpoint.wss];
55
+ }
56
+ async function getPeopleChainEndpoints(envId) {
57
+ const { doc } = await loadEnvironments();
58
+ const config = buildAuthConfig(doc, envId);
59
+ return config.peopleEndpoints;
60
+ }
38
61
  async function getAuthClient(envId) {
39
62
  const { createAuthClient } = await import("./auth/index.js");
40
63
  const { doc } = await loadEnvironments();
@@ -45,8 +68,11 @@ export {
45
68
  DOT_DAPP_ID,
46
69
  DOT_PRODUCT_ID,
47
70
  DOT_DERIVATION_INDEX,
48
- DOT_TERMINAL_METADATA_URL,
71
+ DOT_HOST_NAME,
72
+ STALE_SESSION_MESSAGE,
49
73
  hasPersistedSession,
50
74
  buildAuthConfig,
75
+ resolveBulletinEndpoints,
76
+ getPeopleChainEndpoints,
51
77
  getAuthClient
52
78
  };
@@ -0,0 +1,43 @@
1
+ // src/spinner.ts
2
+ var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3
+ var FRAME_MS = 120;
4
+ function startSpinner(label, stream = process.stdout) {
5
+ const start = Date.now();
6
+ if (!stream.isTTY) {
7
+ stream.write(` ${label}\u2026
8
+ `);
9
+ let stopped2 = false;
10
+ return {
11
+ stop(finalLine) {
12
+ if (stopped2) return;
13
+ stopped2 = true;
14
+ if (finalLine) stream.write(`${finalLine}
15
+ `);
16
+ }
17
+ };
18
+ }
19
+ let frame = 0;
20
+ const render = () => {
21
+ const elapsed = Math.round((Date.now() - start) / 1e3);
22
+ stream.write(`\r ${FRAMES[frame % FRAMES.length]} ${label}\u2026 (${elapsed}s)\x1B[K`);
23
+ frame++;
24
+ };
25
+ render();
26
+ const interval = setInterval(render, FRAME_MS);
27
+ interval.unref();
28
+ let stopped = false;
29
+ return {
30
+ stop(finalLine) {
31
+ if (stopped) return;
32
+ stopped = true;
33
+ clearInterval(interval);
34
+ stream.write("\r\x1B[K");
35
+ if (finalLine) stream.write(`${finalLine}
36
+ `);
37
+ }
38
+ };
39
+ }
40
+
41
+ export {
42
+ startSpinner
43
+ };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  package_default,
3
3
  writeRunState
4
- } from "./chunk-XM5MFMMX.js";
4
+ } from "./chunk-2SR5D4CP.js";
5
5
 
6
6
  // src/memory-report.ts
7
7
  import * as fs2 from "fs";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  captureWarning
3
- } from "./chunk-WF2XKCEZ.js";
3
+ } from "./chunk-LCKLYFAZ.js";
4
4
 
5
5
  // src/chunk-probe.ts
6
6
  import { Twox128, Blake2128Concat, decAnyMetadata, unifyMetadata } from "@polkadot-api/substrate-bindings";
@@ -2,11 +2,11 @@ import {
2
2
  classifyErrorArea,
3
3
  isInteractive,
4
4
  promptYesNo
5
- } from "./chunk-HK3B5MKA.js";
5
+ } from "./chunk-6NW5M3F5.js";
6
6
  import {
7
7
  VERSION,
8
8
  getCurrentSentryTraceId
9
- } from "./chunk-WF2XKCEZ.js";
9
+ } from "./chunk-LCKLYFAZ.js";
10
10
 
11
11
  // src/bug-report.ts
12
12
  import { execSync, execFileSync } from "child_process";
@@ -1,3 +1,6 @@
1
+ import {
2
+ checkSSSAllowance
3
+ } from "./chunk-G56VYTUD.js";
1
4
  import {
2
5
  MirrorSkipped,
3
6
  mirrorToGitHubPages,
@@ -24,16 +27,17 @@ import {
24
27
  parseManifest
25
28
  } from "./chunk-S7EM5VMW.js";
26
29
  import {
27
- DOT_DAPP_ID,
28
30
  DOT_PRODUCT_ID,
31
+ STALE_SESSION_MESSAGE,
32
+ getPeopleChainEndpoints,
29
33
  hasPersistedSession
30
- } from "./chunk-YUSHBZBX.js";
34
+ } from "./chunk-H4LWILH4.js";
31
35
  import {
32
36
  setDeployContext
33
- } from "./chunk-LRRQP7DH.js";
37
+ } from "./chunk-O2NWQLYB.js";
34
38
  import {
35
39
  probeChunks
36
- } from "./chunk-3LNKPUZ7.js";
40
+ } from "./chunk-NP4SLURL.js";
37
41
  import {
38
42
  packSection
39
43
  } from "./chunk-C2TS5MER.js";
@@ -46,7 +50,7 @@ import {
46
50
  parseDomainName,
47
51
  popStatusName,
48
52
  verifyNonceAdvanced
49
- } from "./chunk-S7S7WENB.js";
53
+ } from "./chunk-WZBAQCA5.js";
50
54
  import {
51
55
  derivePoolAccounts,
52
56
  detectTestnet,
@@ -68,13 +72,13 @@ import {
68
72
  truncateAddress,
69
73
  withDeploySpan,
70
74
  withSpan
71
- } from "./chunk-WF2XKCEZ.js";
75
+ } from "./chunk-LCKLYFAZ.js";
72
76
  import {
73
77
  DEFAULT_ENV_ID,
74
78
  getPopSelfServeConfig,
75
79
  loadEnvironments,
76
80
  resolveEndpoints
77
- } from "./chunk-5K3RI5C2.js";
81
+ } from "./chunk-GL3U7K2B.js";
78
82
  import {
79
83
  NonRetryableError
80
84
  } from "./chunk-ZOC4GITL.js";
@@ -253,6 +257,76 @@ function extractBulletinSlotKey(outcomes) {
253
257
  }
254
258
  return null;
255
259
  }
260
+ var BulletinSlotAuthError = class extends Error {
261
+ reason;
262
+ /** The on-chain expiration block (only set when reason === "expired"). */
263
+ expiration;
264
+ constructor(reason, ss58, expiration) {
265
+ const detail = reason === "expired" && expiration != null ? `expired at block ${expiration}` : "no on-chain authorization found";
266
+ super(`Slot account ${ss58} not authorized on Bulletin (${detail})`);
267
+ this.reason = reason;
268
+ this.expiration = expiration;
269
+ this.name = "BulletinSlotAuthError";
270
+ }
271
+ };
272
+ function isBulletinAuthActive(auth, blockNumber) {
273
+ if (auth == null) return { active: false, reason: "missing" };
274
+ const exp = Number(auth.expiration ?? 0);
275
+ if (exp <= blockNumber) return { active: false, reason: "expired", expiration: exp };
276
+ return { active: true, expiration: exp };
277
+ }
278
+ async function pollUntilBulletinAuthorized(queryFn, opts = {}) {
279
+ const { pollMs = 2e3, timeoutMs = 9e4 } = opts;
280
+ const debug = Boolean(process.env.DOT_DEBUG);
281
+ const deadline = Date.now() + timeoutMs;
282
+ while (Date.now() < deadline) {
283
+ try {
284
+ const { auth, blockNumber } = await queryFn();
285
+ const result = isBulletinAuthActive(auth, blockNumber);
286
+ if (result.active) {
287
+ if (debug) console.error(`[auth-poll] active expiration=${result.expiration}`);
288
+ return { authorized: true, expiration: result.expiration };
289
+ }
290
+ if (debug) {
291
+ console.error(
292
+ `[auth-poll] not-active reason=${result.reason}` + (result.expiration != null ? ` expiration=${result.expiration}` : "")
293
+ );
294
+ }
295
+ } catch (e) {
296
+ if (debug) console.error(`[auth-poll] query-errored: ${e instanceof Error ? e.message : String(e)}`);
297
+ }
298
+ const remaining = deadline - Date.now();
299
+ if (remaining <= 0) break;
300
+ await new Promise((r) => setTimeout(r, Math.min(pollMs, remaining)));
301
+ }
302
+ return { authorized: false, reason: "timeout" };
303
+ }
304
+ async function waitForBulletinAuthorization(ss58, opts = {}) {
305
+ const { quiet, endpoints, ...pollOpts } = opts;
306
+ const eps = endpoints && endpoints.length > 0 ? endpoints : BULLETIN_ENDPOINTS;
307
+ const primary = eps[0];
308
+ const onStatusChanged = quiet ? () => {
309
+ } : makeBulletinStatusHandler(primary);
310
+ const client = createPolkadotClient(getWsProvider(
311
+ eps,
312
+ { heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS, onStatusChanged }
313
+ ));
314
+ const unsafeApi = client.getUnsafeApi();
315
+ try {
316
+ return await pollUntilBulletinAuthorized(
317
+ async () => {
318
+ const [auth, block] = await Promise.all([
319
+ unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum("Account", ss58)),
320
+ client.getFinalizedBlock()
321
+ ]);
322
+ return { auth, blockNumber: block.number };
323
+ },
324
+ pollOpts
325
+ );
326
+ } finally {
327
+ client.destroy();
328
+ }
329
+ }
256
330
  async function getSlotSignerProvider(signer, ss58) {
257
331
  const primary = BULLETIN_ENDPOINTS[0];
258
332
  console.log(` Connecting to Bulletin (slot signer): ${primary}`);
@@ -265,12 +339,12 @@ async function getSlotSignerProvider(signer, ss58) {
265
339
  unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum("Account", ss58)),
266
340
  client.getFinalizedBlock()
267
341
  ]);
268
- const now = currentBlock.number;
269
- if (!auth || Number(auth.expiration ?? 0) <= now) {
342
+ const result = isBulletinAuthActive(auth, currentBlock.number);
343
+ if (!result.active) {
270
344
  client.destroy();
271
- throw new Error(`Slot account ${ss58} not authorized on Bulletin`);
345
+ throw new BulletinSlotAuthError(result.reason, ss58, result.expiration);
272
346
  }
273
- console.log(` Using slot signer: ${ss58} (authorized until block ${Number(auth?.expiration ?? 0)})`);
347
+ console.log(` Using slot signer: ${ss58} (authorized until block ${result.expiration})`);
274
348
  setDeployAttribute("deploy.signer.mode", "slot");
275
349
  setDeployAttribute("deploy.signer.address", truncateAddress(ss58));
276
350
  return { client, unsafeApi, signer, ss58 };
@@ -499,6 +573,10 @@ function chooseSignerInput(opts) {
499
573
  if (opts.hasSession) return "resolve";
500
574
  return "pool";
501
575
  }
576
+ function formatStorageSignerLine(slotAddress, failReason) {
577
+ if (slotAddress) return ` Storage signer: allowance slot ${slotAddress}`;
578
+ return ` Storage signer: pool fallback (${failReason ?? "no session"})`;
579
+ }
502
580
  function selectStorageReconnect(options) {
503
581
  if (options.storageSigner && options.storageSignerAddress) {
504
582
  let useSlot = true;
@@ -506,9 +584,20 @@ function selectStorageReconnect(options) {
506
584
  if (!useSlot) return getProvider();
507
585
  try {
508
586
  return await getSlotSignerProvider(options.storageSigner, options.storageSignerAddress);
509
- } catch {
587
+ } catch (e) {
510
588
  useSlot = false;
511
589
  setDeployAttribute("deploy.signer.mode", "pool-fallback");
590
+ let reason;
591
+ if (e instanceof BulletinSlotAuthError) {
592
+ reason = e.reason === "expired" && e.expiration != null ? `expired at block ${e.expiration}` : "no on-chain authorization found";
593
+ } else {
594
+ reason = e instanceof Error ? e.message : String(e);
595
+ }
596
+ console.warn(
597
+ `\u26A0 Bulletin allowance slot not usable: ${reason}
598
+ Falling back to the shared pool account for storage (fine on testnet).
599
+ To use your own allowance, run: bulletin-deploy logout && bulletin-deploy login`
600
+ );
512
601
  return getProvider();
513
602
  }
514
603
  };
@@ -1866,43 +1955,56 @@ async function deploy(content, domainName = null, options = {}) {
1866
1955
  } catch (e) {
1867
1956
  if (options.suri) throw e;
1868
1957
  if (e?.name === "SignerNotAvailableError") {
1869
- console.log(
1870
- " Login session unavailable or expired \u2014 falling back to pool. Run `bulletin-deploy login` to use your identity."
1871
- );
1958
+ if (hasSession) {
1959
+ console.error(STALE_SESSION_MESSAGE);
1960
+ } else {
1961
+ console.log(
1962
+ " Login session unavailable or expired \u2014 falling back to pool. Run `bulletin-deploy login` to use your identity."
1963
+ );
1964
+ }
1872
1965
  } else {
1873
1966
  throw e;
1874
1967
  }
1875
1968
  }
1876
1969
  }
1970
+ if (resolvedUserSession && options.signer) {
1971
+ try {
1972
+ const productPubkey = options.signer.publicKey;
1973
+ const peopleEndpoints = await getPeopleChainEndpoints(envId);
1974
+ const allowed = await checkSSSAllowance(productPubkey, peopleEndpoints);
1975
+ if (allowed === false) {
1976
+ throw new NonRetryableError(
1977
+ "Session signing allowance has expired (~2-3 days after login). Run `bulletin-deploy login` to renew."
1978
+ );
1979
+ }
1980
+ } catch (e) {
1981
+ if (e instanceof NonRetryableError) throw e;
1982
+ }
1983
+ }
1877
1984
  if (!options.storageSigner) {
1985
+ let storageLine = null;
1878
1986
  try {
1879
- let slotResult = await readBulletinSlotSigner(DOT_DAPP_ID);
1880
- if (!slotResult && resolvedUserSession?.userSession) {
1881
- const { requestResourceAllocation } = await import("./auth/index.js");
1882
- console.log("Requesting Bulletin storage allowance \u2014 check your phone to approve");
1883
- const outcomes = await requestResourceAllocation(
1884
- resolvedUserSession.userSession,
1885
- DOT_PRODUCT_ID,
1886
- // StatementStoreAllowance is required for createTransaction (the session
1887
- // signer uses it to send signing requests to the phone). Include it here
1888
- // so a deploy without a prior `login` still works. onExisting:"Ignore"
1889
- // makes this a no-op if the allowance was already granted by login.
1890
- [
1891
- { tag: "BulletInAllowance", value: void 0 },
1892
- { tag: "StatementStoreAllowance", value: void 0 }
1893
- ]
1987
+ if (resolvedUserSession?.userSession && resolvedUserSession?.adapter) {
1988
+ const { ss58Encode } = await import("@parity/product-sdk-address");
1989
+ const signerResult = await resolvedUserSession.adapter.allowance.getBulletinSigner(
1990
+ resolvedUserSession.userSession.id,
1991
+ DOT_PRODUCT_ID
1894
1992
  );
1895
- const hexKey = extractBulletinSlotKey(outcomes);
1896
- if (hexKey) {
1897
- await writeBulletinSlotKey(DOT_DAPP_ID, hexKey);
1898
- slotResult = await readBulletinSlotSigner(DOT_DAPP_ID);
1993
+ if (signerResult.isOk()) {
1994
+ const slotSigner = signerResult.value;
1995
+ const slotAddress = ss58Encode(slotSigner.publicKey);
1996
+ options = { ...options, storageSigner: slotSigner, storageSignerAddress: slotAddress };
1997
+ storageLine = formatStorageSignerLine(slotAddress);
1998
+ } else {
1999
+ storageLine = formatStorageSignerLine(null, signerResult.error.reason);
1899
2000
  }
1900
- }
1901
- if (slotResult) {
1902
- options = { ...options, storageSigner: slotResult.signer, storageSignerAddress: slotResult.ss58 };
2001
+ } else {
2002
+ storageLine = formatStorageSignerLine(null);
1903
2003
  }
1904
2004
  } catch {
2005
+ storageLine = formatStorageSignerLine(null, "error");
1905
2006
  }
2007
+ if (storageLine) console.log(storageLine);
1906
2008
  }
1907
2009
  initTelemetry();
1908
2010
  const randomSuffix = Math.floor(Math.random() * 100).toString().padStart(2, "0");
@@ -2718,6 +2820,10 @@ export {
2718
2820
  readBulletinSlotSigner,
2719
2821
  writeBulletinSlotKey,
2720
2822
  extractBulletinSlotKey,
2823
+ BulletinSlotAuthError,
2824
+ isBulletinAuthActive,
2825
+ pollUntilBulletinAuthorized,
2826
+ waitForBulletinAuthorization,
2721
2827
  getSlotSignerProvider,
2722
2828
  friendlyChainError,
2723
2829
  DEFAULT_BULLETIN_RPC,
@@ -2741,6 +2847,7 @@ export {
2741
2847
  encryptContent,
2742
2848
  __selectStorageProviderModeForTest,
2743
2849
  chooseSignerInput,
2850
+ formatStorageSignerLine,
2744
2851
  storeFile,
2745
2852
  __assignDenseNoncesForTest,
2746
2853
  storeChunkedContent,
@@ -8,10 +8,10 @@ import {
8
8
  setDeploySentryTag,
9
9
  truncateAddress,
10
10
  withSpan
11
- } from "./chunk-WF2XKCEZ.js";
11
+ } from "./chunk-LCKLYFAZ.js";
12
12
  import {
13
13
  validateContractAddresses
14
- } from "./chunk-5K3RI5C2.js";
14
+ } from "./chunk-GL3U7K2B.js";
15
15
  import {
16
16
  NonRetryableError
17
17
  } from "./chunk-ZOC4GITL.js";
@@ -6,15 +6,15 @@ import {
6
6
  resolveDotnsConnectOptions,
7
7
  storeDirectory,
8
8
  storeFile
9
- } from "./chunk-DKGCOGRT.js";
9
+ } from "./chunk-RD2QHYTL.js";
10
10
  import {
11
11
  DotNS
12
- } from "./chunk-S7S7WENB.js";
12
+ } from "./chunk-WZBAQCA5.js";
13
13
  import {
14
14
  getPopSelfServeConfig,
15
15
  loadEnvironments,
16
16
  resolveEndpoints
17
- } from "./chunk-5K3RI5C2.js";
17
+ } from "./chunk-GL3U7K2B.js";
18
18
  import {
19
19
  NonRetryableError
20
20
  } from "./chunk-ZOC4GITL.js";