@parity/product-deploy 0.8.3-rc.6 → 0.8.3-rc.8

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 (59) hide show
  1. package/bin/bulletin-deploy +33 -7
  2. package/dist/allocations-B65Is4Md.d.ts +97 -0
  3. package/dist/auth/index.d.ts +7 -0
  4. package/dist/auth/index.js +25 -0
  5. package/dist/auth/vendor/index.d.ts +32 -0
  6. package/dist/auth/vendor/index.js +26 -0
  7. package/dist/auth/vendor/ui/index.d.ts +15 -0
  8. package/dist/auth/vendor/ui/index.js +10 -0
  9. package/dist/auth-DkRZBK-T.d.ts +122 -0
  10. package/dist/auth-config.d.ts +39 -0
  11. package/dist/auth-config.js +20 -0
  12. package/dist/bug-report.js +4 -4
  13. package/dist/chunk-327NAPBD.js +52 -0
  14. package/dist/{chunk-22CE57IY.js → chunk-3OWKSL7K.js} +1 -1
  15. package/dist/chunk-7DGFJC6E.js +379 -0
  16. package/dist/{chunk-3TOFKDMY.js → chunk-EGNMZHMR.js} +1 -1
  17. package/dist/chunk-JQKKMUCT.js +0 -0
  18. package/dist/{chunk-L5Z3TJD7.js → chunk-OCKCB72S.js} +6 -6
  19. package/dist/{chunk-YUIBBHS2.js → chunk-OFVBJOFB.js} +1 -1
  20. package/dist/chunk-RIRDBSBG.js +36 -0
  21. package/dist/{chunk-Y7F57J2T.js → chunk-RPU72Z4B.js} +606 -384
  22. package/dist/{chunk-6XDIJYDO.js → chunk-T4PAK4YK.js} +2 -2
  23. package/dist/{chunk-M6DM2NUT.js → chunk-WHMNBSG7.js} +8 -2
  24. package/dist/{chunk-3ASTLJSZ.js → chunk-YOQLRCQV.js} +2 -2
  25. package/dist/{chunk-LX77LVIM.js → chunk-YXGNQZZF.js} +17 -3
  26. package/dist/chunk-probe.js +3 -3
  27. package/dist/commands/login.d.ts +28 -0
  28. package/dist/commands/login.js +63 -0
  29. package/dist/commands/logout.d.ts +21 -0
  30. package/dist/commands/logout.js +37 -0
  31. package/dist/commands/whoami.d.ts +22 -0
  32. package/dist/commands/whoami.js +47 -0
  33. package/dist/deploy.d.ts +40 -3
  34. package/dist/deploy.js +17 -8
  35. package/dist/dotns.js +3 -3
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.js +13 -12
  38. package/dist/manifest/publish.js +10 -9
  39. package/dist/memory-report.js +2 -2
  40. package/dist/merkle.d.ts +3 -1
  41. package/dist/merkle.js +9 -8
  42. package/dist/personhood/bind-paid-alias.js +3 -3
  43. package/dist/personhood/bind-personal-id.js +2 -2
  44. package/dist/personhood/bootstrap.js +16 -16
  45. package/dist/personhood/claim-pgas.js +2 -2
  46. package/dist/personhood/people-client.js +3 -3
  47. package/dist/personhood/proof-validity.js +2 -2
  48. package/dist/personhood/reprove.js +5 -5
  49. package/dist/run-state.js +1 -1
  50. package/dist/signer-CriGqahj.d.ts +35 -0
  51. package/dist/storage-signer.d.ts +38 -0
  52. package/dist/storage-signer.js +28 -0
  53. package/dist/telemetry.d.ts +1 -1
  54. package/dist/telemetry.js +2 -2
  55. package/dist/version-check.js +3 -3
  56. package/package.json +17 -3
  57. package/dist/{chunk-LHLCPDGL.js → chunk-7URNKK6J.js} +3 -3
  58. package/dist/{chunk-7Y7RDOGT.js → chunk-EATOPQFR.js} +5 -5
  59. package/dist/{chunk-SLE4P6MO.js → chunk-EJI3MX4G.js} +3 -3
@@ -1,3 +1,8 @@
1
+ import {
2
+ MirrorSkipped,
3
+ mirrorToGitHubPages,
4
+ pollMirrorFreshness
5
+ } from "./chunk-HOTQDYHD.js";
1
6
  import {
2
7
  computeStats,
3
8
  renderSummary,
@@ -18,12 +23,17 @@ import {
18
23
  classifyFile,
19
24
  parseManifest
20
25
  } from "./chunk-S7EM5VMW.js";
26
+ import {
27
+ DOT_DAPP_ID,
28
+ DOT_PRODUCT_ID,
29
+ hasPersistedSession
30
+ } from "./chunk-327NAPBD.js";
21
31
  import {
22
32
  setDeployContext
23
- } from "./chunk-6XDIJYDO.js";
33
+ } from "./chunk-T4PAK4YK.js";
24
34
  import {
25
35
  probeChunks
26
- } from "./chunk-22CE57IY.js";
36
+ } from "./chunk-3OWKSL7K.js";
27
37
  import {
28
38
  packSection
29
39
  } from "./chunk-C2TS5MER.js";
@@ -35,7 +45,7 @@ import {
35
45
  parseDomainName,
36
46
  popStatusName,
37
47
  verifyNonceAdvanced
38
- } from "./chunk-3TOFKDMY.js";
48
+ } from "./chunk-EGNMZHMR.js";
39
49
  import {
40
50
  derivePoolAccounts,
41
51
  detectTestnet,
@@ -57,7 +67,7 @@ import {
57
67
  truncateAddress,
58
68
  withDeploySpan,
59
69
  withSpan
60
- } from "./chunk-M6DM2NUT.js";
70
+ } from "./chunk-WHMNBSG7.js";
61
71
  import {
62
72
  DEFAULT_ENV_ID,
63
73
  getPopSelfServeConfig,
@@ -67,11 +77,6 @@ import {
67
77
  import {
68
78
  NonRetryableError
69
79
  } from "./chunk-ZOC4GITL.js";
70
- import {
71
- MirrorSkipped,
72
- mirrorToGitHubPages,
73
- pollMirrorFreshness
74
- } from "./chunk-HOTQDYHD.js";
75
80
 
76
81
  // src/merkle.ts
77
82
  import * as fs2 from "fs";
@@ -91,8 +96,8 @@ import * as path from "path";
91
96
  import { execSync } from "child_process";
92
97
  import { sha256 } from "@noble/hashes/sha256";
93
98
  import { blake2b } from "@noble/hashes/blake2b";
94
- import { createClient as createPolkadotClient, Enum } from "polkadot-api";
95
- import { getWsProvider, WsEvent } from "polkadot-api/ws";
99
+ import { createClient as createPolkadotClient2, Enum as Enum2 } from "polkadot-api";
100
+ import { getWsProvider as getWsProvider2, WsEvent } from "polkadot-api/ws";
96
101
  import { CID } from "multiformats/cid";
97
102
  import { create as createMultihash } from "multiformats/hashes/digest";
98
103
  import { base32 } from "multiformats/bases/base32";
@@ -100,10 +105,176 @@ import { base58btc } from "multiformats/bases/base58";
100
105
  import * as dagPB from "@ipld/dag-pb";
101
106
  import { UnixFS } from "ipfs-unixfs";
102
107
  import { cryptoWaitReady } from "@polkadot/util-crypto";
103
- import { getPolkadotSigner } from "polkadot-api/signer";
104
- import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
105
- import { mnemonicToEntropy, entropyToMiniSecret, ss58Address } from "@polkadot-labs/hdkd-helpers";
108
+ import { getPolkadotSigner as getPolkadotSigner2 } from "polkadot-api/signer";
109
+ import { sr25519CreateDerive as sr25519CreateDerive2 } from "@polkadot-labs/hdkd";
110
+ import { mnemonicToEntropy, entropyToMiniSecret, ss58Address as ss58Address2 } from "@polkadot-labs/hdkd-helpers";
106
111
  import { CarReader } from "@ipld/car/reader";
112
+
113
+ // src/storage-signer.ts
114
+ import { readFile, writeFile, mkdir } from "fs/promises";
115
+ import { homedir } from "os";
116
+ import { join, dirname } from "path";
117
+
118
+ // node_modules/@polkadot-api/utils/dist/hex.js
119
+ var HEX_STR = "0123456789abcdef";
120
+ function toHex(bytes) {
121
+ const result = new Array(bytes.length + 1);
122
+ result[0] = "0x";
123
+ for (let i = 0; i < bytes.length; ) {
124
+ const b = bytes[i++];
125
+ result[i] = HEX_STR[b >> 4] + HEX_STR[b & 15];
126
+ }
127
+ return result.join("");
128
+ }
129
+ var HEX_MAP = {
130
+ 0: 0,
131
+ 1: 1,
132
+ 2: 2,
133
+ 3: 3,
134
+ 4: 4,
135
+ 5: 5,
136
+ 6: 6,
137
+ 7: 7,
138
+ 8: 8,
139
+ 9: 9,
140
+ a: 10,
141
+ b: 11,
142
+ c: 12,
143
+ d: 13,
144
+ e: 14,
145
+ f: 15,
146
+ A: 10,
147
+ B: 11,
148
+ C: 12,
149
+ D: 13,
150
+ E: 14,
151
+ F: 15
152
+ };
153
+ function fromHex(hexString) {
154
+ const isOdd = hexString.length % 2;
155
+ const base = (hexString[1] === "x" ? 2 : 0) + isOdd;
156
+ const nBytes = (hexString.length - base) / 2 + isOdd;
157
+ const bytes = new Uint8Array(nBytes);
158
+ if (isOdd) bytes[0] = 0 | HEX_MAP[hexString[2]];
159
+ for (let i = 0; i < nBytes; ) {
160
+ const idx = base + i * 2;
161
+ const a = HEX_MAP[hexString[idx]];
162
+ const b = HEX_MAP[hexString[idx + 1]];
163
+ bytes[isOdd + i++] = a << 4 | b;
164
+ }
165
+ return bytes;
166
+ }
167
+
168
+ // src/storage-signer.ts
169
+ import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
170
+ import { sr25519, ss58Address } from "@polkadot-labs/hdkd-helpers";
171
+ import { createClient as createPolkadotClient, Enum } from "polkadot-api";
172
+ import { getPolkadotSigner } from "polkadot-api/signer";
173
+ import { getWsProvider } from "polkadot-api/ws";
174
+ function sanitize(appId) {
175
+ return appId.replace(/[^a-zA-Z0-9_.-]/g, "_");
176
+ }
177
+ function cacheFilePath(appId, storageDir) {
178
+ return join(storageDir ?? homedir(), ".polkadot-apps", `${sanitize(appId)}_AllowanceKeys.json`);
179
+ }
180
+ function normalizeSchnorrkelKey(key) {
181
+ if (key.length !== 64) return key;
182
+ const out = new Uint8Array(key);
183
+ let carry = 0;
184
+ for (let i = 0; i < 32; i++) {
185
+ const v = key[i] * 8 + carry;
186
+ out[i] = v & 255;
187
+ carry = v >> 8;
188
+ }
189
+ return out;
190
+ }
191
+ function signerFromSecret(secret) {
192
+ if (secret.length === 32) {
193
+ const kp = sr25519CreateDerive(secret)("");
194
+ return getPolkadotSigner(kp.publicKey, "Sr25519", async (d) => kp.sign(d));
195
+ }
196
+ if (secret.length === 64) {
197
+ const normalized = normalizeSchnorrkelKey(secret);
198
+ const pub = sr25519.getPublicKey(normalized);
199
+ return getPolkadotSigner(pub, "Sr25519", async (d) => sr25519.sign(d, normalized));
200
+ }
201
+ throw new Error(
202
+ `BulletInAllowance slot key: unexpected length ${secret.length} (expected 32 or 64)`
203
+ );
204
+ }
205
+ async function readBulletinSlotSigner(appId, storageDir) {
206
+ let raw;
207
+ try {
208
+ raw = await readFile(cacheFilePath(appId, storageDir), "utf-8");
209
+ } catch (e) {
210
+ if (e?.code === "ENOENT") return null;
211
+ throw e;
212
+ }
213
+ let cache;
214
+ try {
215
+ cache = JSON.parse(raw);
216
+ } catch {
217
+ return null;
218
+ }
219
+ const entry = cache?.entries?.BulletInAllowance;
220
+ if (!entry?.slotAccountKey) return null;
221
+ let secret;
222
+ try {
223
+ secret = fromHex(entry.slotAccountKey);
224
+ } catch {
225
+ return null;
226
+ }
227
+ const signer = signerFromSecret(secret);
228
+ return { signer, ss58: ss58Address(signer.publicKey) };
229
+ }
230
+ async function writeBulletinSlotKey(appId, hexKey, storageDir) {
231
+ const path3 = cacheFilePath(appId, storageDir);
232
+ await mkdir(dirname(path3), { recursive: true, mode: 448 });
233
+ let existing = { version: 1, entries: {} };
234
+ try {
235
+ existing = JSON.parse(await readFile(path3, "utf-8"));
236
+ } catch {
237
+ }
238
+ existing.entries ??= {};
239
+ existing.entries.BulletInAllowance = { tag: "BulletInAllowance", slotAccountKey: hexKey };
240
+ await writeFile(path3, `${JSON.stringify(existing, null, 2)}
241
+ `, { mode: 384 });
242
+ }
243
+ function extractBulletinSlotKey(outcomes) {
244
+ for (const outcome of outcomes) {
245
+ if (outcome.tag !== "Allocated") continue;
246
+ const allocated = outcome.value;
247
+ if (allocated?.tag !== "BulletInAllowance") continue;
248
+ const key = allocated.value?.slotAccountKey;
249
+ if (!(key instanceof Uint8Array)) continue;
250
+ return toHex(normalizeSchnorrkelKey(key));
251
+ }
252
+ return null;
253
+ }
254
+ async function getSlotSignerProvider(signer, ss58) {
255
+ const primary = BULLETIN_ENDPOINTS[0];
256
+ console.log(` Connecting to Bulletin (slot signer): ${primary}`);
257
+ const client = createPolkadotClient(getWsProvider(
258
+ BULLETIN_ENDPOINTS,
259
+ { heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS, onStatusChanged: makeBulletinStatusHandler(primary) }
260
+ ));
261
+ const unsafeApi = client.getUnsafeApi();
262
+ const [auth, currentBlock] = await Promise.all([
263
+ unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum("Account", ss58)),
264
+ client.getFinalizedBlock()
265
+ ]);
266
+ const now = currentBlock.number;
267
+ if (!auth || Number(auth.expiration ?? 0) <= now) {
268
+ client.destroy();
269
+ throw new Error(`Slot account ${ss58} not authorized on Bulletin`);
270
+ }
271
+ console.log(` Using slot signer: ${ss58} (authorized until block ${Number(auth?.expiration ?? 0)})`);
272
+ setDeployAttribute("deploy.signer.mode", "slot");
273
+ setDeployAttribute("deploy.signer.address", truncateAddress(ss58));
274
+ return { client, unsafeApi, signer, ss58 };
275
+ }
276
+
277
+ // src/deploy.ts
107
278
  function friendlyChainError(msg) {
108
279
  if (/"type":\s*"Invalid"[\s\S]*?"type":\s*"Payment"/i.test(msg)) {
109
280
  return "Bulletin quota exhausted (signed extension rejected the tx \u2014 signer is out of allowed txs or bytes; grant quota on-chain)";
@@ -166,10 +337,10 @@ var CID_CONFIG = { version: 1, codec: 85, hashCode: 18, hashLength: 32 };
166
337
  function deriveRootSigner(mnemonic, path3 = "") {
167
338
  const entropy = mnemonicToEntropy(mnemonic);
168
339
  const miniSecret = entropyToMiniSecret(entropy);
169
- const derive = sr25519CreateDerive(miniSecret);
340
+ const derive = sr25519CreateDerive2(miniSecret);
170
341
  const keyPair = derive(path3);
171
- const signer = getPolkadotSigner(keyPair.publicKey, "Sr25519", keyPair.sign);
172
- return { signer, ss58: ss58Address(keyPair.publicKey) };
342
+ const signer = getPolkadotSigner2(keyPair.publicKey, "Sr25519", keyPair.sign);
343
+ return { signer, ss58: ss58Address2(keyPair.publicKey) };
173
344
  }
174
345
  function createCID(data, codec = CID_CONFIG.codec, hashCode = CID_CONFIG.hashCode) {
175
346
  let hash;
@@ -223,7 +394,7 @@ function toHashingEnum(mhCode) {
223
394
  async function getProvider() {
224
395
  const primary = BULLETIN_ENDPOINTS[0];
225
396
  console.log(` Connecting to Bulletin: ${primary}`);
226
- const client = createPolkadotClient(getWsProvider(
397
+ const client = createPolkadotClient2(getWsProvider2(
227
398
  BULLETIN_ENDPOINTS,
228
399
  { heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS, onStatusChanged: makeBulletinStatusHandler(primary) }
229
400
  ));
@@ -251,7 +422,7 @@ async function getProvider() {
251
422
  async function getDirectProvider(mnemonic, derivationPath = "") {
252
423
  const primary = BULLETIN_ENDPOINTS[0];
253
424
  console.log(` Connecting to Bulletin: ${primary}`);
254
- const client = createPolkadotClient(getWsProvider(
425
+ const client = createPolkadotClient2(getWsProvider2(
255
426
  BULLETIN_ENDPOINTS,
256
427
  { heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS, onStatusChanged: makeBulletinStatusHandler(primary) }
257
428
  ));
@@ -259,7 +430,7 @@ async function getDirectProvider(mnemonic, derivationPath = "") {
259
430
  const { signer, ss58 } = deriveRootSigner(mnemonic, derivationPath);
260
431
  console.log(` Using direct signer: ${ss58}${derivationPath ? ` (path: ${derivationPath})` : ""}`);
261
432
  let [auth, currentBlock] = await Promise.all([
262
- unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum("Account", ss58)),
433
+ unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum2("Account", ss58)),
263
434
  client.getFinalizedBlock()
264
435
  ]);
265
436
  let now = currentBlock.number;
@@ -267,7 +438,7 @@ async function getDirectProvider(mnemonic, derivationPath = "") {
267
438
  try {
268
439
  await ensureAuthorized(unsafeApi, ss58, "direct signer");
269
440
  [auth, currentBlock] = await Promise.all([
270
- unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum("Account", ss58)),
441
+ unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum2("Account", ss58)),
271
442
  client.getFinalizedBlock()
272
443
  ]);
273
444
  now = currentBlock.number;
@@ -281,52 +452,35 @@ async function getDirectProvider(mnemonic, derivationPath = "") {
281
452
  setDeployAttribute("deploy.signer.address", truncateAddress(ss58));
282
453
  return { client, unsafeApi, signer, ss58 };
283
454
  }
284
- async function getSignerProvider(signer, ss58) {
285
- const primary = BULLETIN_ENDPOINTS[0];
286
- console.log(` Connecting to Bulletin: ${primary}`);
287
- const client = createPolkadotClient(getWsProvider(
288
- BULLETIN_ENDPOINTS,
289
- { heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS, onStatusChanged: makeBulletinStatusHandler(primary) }
290
- ));
291
- const unsafeApi = client.getUnsafeApi();
292
- console.log(` Using external signer: ${ss58}`);
293
- let [auth, currentBlock] = await Promise.all([
294
- unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum("Account", ss58)),
295
- client.getFinalizedBlock()
296
- ]);
297
- let now = currentBlock.number;
298
- if (!auth || Number(auth.expiration ?? 0) <= now) {
299
- try {
300
- await ensureAuthorized(unsafeApi, ss58, "external signer");
301
- [auth, currentBlock] = await Promise.all([
302
- unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum("Account", ss58)),
303
- client.getFinalizedBlock()
304
- ]);
305
- now = currentBlock.number;
306
- } catch (e) {
307
- client.destroy();
308
- throw new NonRetryableError(`Account ${ss58} is not authorized for Bulletin storage and auto-authorization failed: ${e.message}`);
309
- }
310
- }
311
- console.log(` Authorization: expires at block ${Number(auth?.expiration ?? 0)} (current: ${now})`);
312
- setDeployAttribute("deploy.signer.mode", "external");
313
- setDeployAttribute("deploy.signer.address", truncateAddress(ss58));
314
- return { client, unsafeApi, signer, ss58 };
315
- }
316
455
  function __selectStorageProviderModeForTest(options) {
317
- if (options.signer && options.signerAddress) return "signer";
456
+ if (options.storageSigner && options.storageSignerAddress) return "storageSigner";
318
457
  if (options.mnemonic) return "direct";
319
458
  return "pool";
320
459
  }
460
+ function chooseSignerInput(opts) {
461
+ if (opts.mnemonic) return "mnemonic";
462
+ if (opts.hasInjectedSigner) return "injected";
463
+ if (opts.suri) return "resolve";
464
+ if (opts.hasSession) return "resolve";
465
+ return "pool";
466
+ }
321
467
  function selectStorageReconnect(options) {
322
- switch (__selectStorageProviderModeForTest(options)) {
323
- case "signer":
324
- return () => getSignerProvider(options.signer, options.signerAddress);
325
- case "direct":
326
- return () => getDirectProvider(options.mnemonic, options.derivationPath);
327
- case "pool":
328
- return () => getProvider();
468
+ if (options.storageSigner && options.storageSignerAddress) {
469
+ let useSlot = true;
470
+ return async () => {
471
+ if (!useSlot) return getProvider();
472
+ try {
473
+ return await getSlotSignerProvider(options.storageSigner, options.storageSignerAddress);
474
+ } catch {
475
+ useSlot = false;
476
+ setDeployAttribute("deploy.signer.mode", "pool-fallback");
477
+ return getProvider();
478
+ }
479
+ };
329
480
  }
481
+ if (options.mnemonic)
482
+ return () => getDirectProvider(options.mnemonic, options.derivationPath);
483
+ return () => getProvider();
330
484
  }
331
485
  function watchTransaction(tx, signer, txOpts, onSuccess, { label = "transaction", rpc, senderSS58, expectedNonce, timeoutMs, fetchNonce: fetchNonceOverride } = {}) {
332
486
  const timeout = timeoutMs ?? TX_TIMEOUT_MS;
@@ -491,7 +645,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
491
645
  }
492
646
  }
493
647
  const readUploadAuthorization = () => Promise.all([
494
- unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum("Account", ss58)),
648
+ unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum2("Account", ss58)),
495
649
  unsafeApi.query.System.Number.getValue()
496
650
  ]);
497
651
  let uploadAuth;
@@ -1120,7 +1274,7 @@ async function storeDirectoryV2(directoryPath, opts = {}) {
1120
1274
  useKubo = hasIPFS();
1121
1275
  }
1122
1276
  const phaseA = await withSpan("deploy.merkleize", `1a. merkleize (${useKubo ? "kubo" : "js"}, stable)`, { "deploy.directory": dirBasename, "deploy.merkle": useKubo ? "kubo" : "js" }, async () => {
1123
- const r = await merkleizeWithStableOrder(directoryPath, prevManifest?.stableBlockOrder, { useKubo });
1277
+ const r = await merkleizeWithStableOrder(directoryPath, prevManifest?.stableBlockOrder, { useKubo, phase: "Phase A" });
1124
1278
  sampleMemory("merkleize_end");
1125
1279
  return r;
1126
1280
  });
@@ -1239,7 +1393,7 @@ async function storeDirectoryV2(directoryPath, opts = {}) {
1239
1393
  carChunksA.length = 0;
1240
1394
  phaseA.carBytes = new Uint8Array(0);
1241
1395
  const phaseB = await withSpan("deploy.merkleize", "1c. merkleize (js, finalise)", { "deploy.directory": dirBasename }, async () => {
1242
- const r = await merkleizeWithStableOrder(directoryPath, phaseA.stableOrder, { useKubo });
1396
+ const r = await merkleizeWithStableOrder(directoryPath, phaseA.stableOrder, { useKubo, phase: "Phase B" });
1243
1397
  sampleMemory("merkleize_finalise_end");
1244
1398
  return r;
1245
1399
  });
@@ -1654,359 +1808,416 @@ async function deploy(content, domainName = null, options = {}) {
1654
1808
  BULLETIN_ENDPOINTS = userRpc ? [userRpc, ...envBulletin.filter((e) => e !== userRpc)] : envBulletin;
1655
1809
  _deployRpcFailedOver = false;
1656
1810
  POOL_SIZE = options.poolSize ?? parseInt(process.env.BULLETIN_POOL_SIZE ?? String(DEFAULT_POOL_SIZE), 10);
1811
+ let sessionCleanup;
1812
+ const hasSession = hasPersistedSession();
1813
+ const signerChoice = chooseSignerInput({
1814
+ mnemonic: options.mnemonic,
1815
+ suri: options.suri,
1816
+ hasInjectedSigner: !!(options.signer && options.signerAddress),
1817
+ hasSession
1818
+ });
1819
+ let resolvedUserSession = void 0;
1820
+ if (signerChoice === "resolve") {
1821
+ const { resolveSigner: resolveSignerFn } = await import("./auth/index.js");
1822
+ const { getAuthClient } = await import("./auth-config.js");
1823
+ const authClient = await getAuthClient(envId);
1824
+ try {
1825
+ const resolved = await resolveSignerFn(authClient, { suri: options.suri });
1826
+ options = { ...options, signer: resolved.signer, signerAddress: resolved.address };
1827
+ sessionCleanup = resolved.destroy.bind(resolved);
1828
+ console.log(` Using ${resolved.source} signer: ${resolved.address}`);
1829
+ if (resolved.source === "session") resolvedUserSession = resolved;
1830
+ } catch (e) {
1831
+ if (options.suri) throw e;
1832
+ if (e?.name === "SignerNotAvailableError") {
1833
+ console.log(
1834
+ " Login session unavailable or expired \u2014 falling back to pool. Run `bulletin-deploy login` to use your identity."
1835
+ );
1836
+ } else {
1837
+ throw e;
1838
+ }
1839
+ }
1840
+ }
1841
+ if (!options.storageSigner) {
1842
+ try {
1843
+ let slotResult = await readBulletinSlotSigner(DOT_DAPP_ID);
1844
+ if (!slotResult && resolvedUserSession?.userSession) {
1845
+ const { requestResourceAllocation } = await import("./auth/index.js");
1846
+ console.log("Requesting Bulletin storage allowance \u2014 check your phone to approve");
1847
+ const outcomes = await requestResourceAllocation(
1848
+ resolvedUserSession.userSession,
1849
+ DOT_PRODUCT_ID,
1850
+ [{ tag: "BulletInAllowance", value: void 0 }]
1851
+ );
1852
+ const hexKey = extractBulletinSlotKey(outcomes);
1853
+ if (hexKey) {
1854
+ await writeBulletinSlotKey(DOT_DAPP_ID, hexKey);
1855
+ slotResult = await readBulletinSlotSigner(DOT_DAPP_ID);
1856
+ }
1857
+ }
1858
+ if (slotResult) {
1859
+ options = { ...options, storageSigner: slotResult.signer, storageSignerAddress: slotResult.ss58 };
1860
+ }
1861
+ } catch {
1862
+ }
1863
+ }
1657
1864
  initTelemetry();
1658
1865
  const randomSuffix = Math.floor(Math.random() * 100).toString().padStart(2, "0");
1659
1866
  const parsed = domainName ? parseDomainName(domainName) : null;
1660
1867
  const name = parsed ? parsed.label : `test-domain-${Date.now().toString(36)}${randomSuffix}`;
1661
- return withDeploySpan(name, async () => {
1662
- const deployTag = options.tag ?? process.env.DEPLOY_TAG;
1663
- if (deployTag) {
1664
- setDeployAttribute("deploy.tag", deployTag);
1665
- setDeploySentryTag("deploy.tag", deployTag);
1666
- }
1667
- setDeployAttribute("deploy.env", envId);
1668
- setDeployAttribute("deploy.label", parsed?.label ?? name);
1669
- setDeployAttribute("deploy.subdomain", String(parsed?.isSubdomain ?? false));
1670
- if (envNetwork) setDeployAttribute("deploy.network", envNetwork);
1671
- if (envSource) setDeployAttribute("deploy.environments_source", envSource);
1672
- let cid;
1673
- let ipfsCid;
1674
- let mirrorPromise = Promise.resolve(null);
1675
- console.log("\n" + "=".repeat(60));
1676
- console.log(`DEPLOYING TO TESTNET v${VERSION}`);
1677
- console.log("=".repeat(60));
1678
- if (envName) console.log(` Environment: ${envName}`);
1679
- console.log(` Domain: ${name}.dot`);
1680
- if (deployTag) console.log(` Tag: ${deployTag}`);
1681
- if (options.inputCar) console.log(` Input CAR: ${path.resolve(options.inputCar)}`);
1682
- else if (typeof content === "string") console.log(` Build dir: ${path.resolve(content)}`);
1683
- if (process.env.CI) console.log(` Runner: ${resolveRunner()} (${resolveRunnerType()})`);
1684
- if (options.password) console.log(` Encrypted: yes`);
1685
- let provider;
1686
- const reconnect = selectStorageReconnect(options);
1687
- let dotnsPreflight = null;
1688
- let previousContenthashCid = null;
1689
- try {
1868
+ try {
1869
+ return await withDeploySpan(name, async () => {
1870
+ const deployTag = options.tag ?? process.env.DEPLOY_TAG;
1871
+ if (deployTag) {
1872
+ setDeployAttribute("deploy.tag", deployTag);
1873
+ setDeploySentryTag("deploy.tag", deployTag);
1874
+ }
1875
+ setDeployAttribute("deploy.env", envId);
1876
+ setDeployAttribute("deploy.label", parsed?.label ?? name);
1877
+ setDeployAttribute("deploy.subdomain", String(parsed?.isSubdomain ?? false));
1878
+ if (envNetwork) setDeployAttribute("deploy.network", envNetwork);
1879
+ if (envSource) setDeployAttribute("deploy.environments_source", envSource);
1880
+ let cid;
1881
+ let ipfsCid;
1882
+ let mirrorPromise = Promise.resolve(null);
1690
1883
  console.log("\n" + "=".repeat(60));
1691
- console.log("Preflight");
1884
+ console.log(`DEPLOYING TO TESTNET v${VERSION}`);
1692
1885
  console.log("=".repeat(60));
1693
- const preflight = new DotNS();
1694
- await preflight.connect(resolveDotnsConnectOptions(options, envAssetHub, envAutoAccountMapping, envContracts, envNativeToEthRatio, envId, envPopSelfServe, envRegisterStorageDeposit));
1695
- if (parsed?.isSubdomain) {
1696
- try {
1697
- const subResult = await preflight.checkSubdomainOwnership(parsed.sublabel, parsed.parentLabel);
1698
- assertSubdomainOwnerMatchesSigner(subResult, preflight.evmAddress, parsed.sublabel, parsed.parentLabel);
1699
- if (!subResult.owned) {
1700
- const { owned: parentOwned, owner: parentOwner } = await preflight.checkOwnership(parsed.parentLabel);
1701
- if (!parentOwned) {
1702
- throw new NonRetryableError(
1703
- `Cannot deploy ${parsed.fullName}: parent ${parsed.parentLabel}.dot is owned by ${parentOwner ?? "no one"}, not by this signer.`
1704
- );
1886
+ if (envName) console.log(` Environment: ${envName}`);
1887
+ console.log(` Domain: ${name}.dot`);
1888
+ if (deployTag) console.log(` Tag: ${deployTag}`);
1889
+ if (options.inputCar) console.log(` Input CAR: ${path.resolve(options.inputCar)}`);
1890
+ else if (typeof content === "string") console.log(` Build dir: ${path.resolve(content)}`);
1891
+ if (process.env.CI) console.log(` Runner: ${resolveRunner()} (${resolveRunnerType()})`);
1892
+ if (options.password) console.log(` Encrypted: yes`);
1893
+ let provider;
1894
+ const reconnect = selectStorageReconnect(options);
1895
+ let dotnsPreflight = null;
1896
+ let previousContenthashCid = null;
1897
+ try {
1898
+ console.log("\n" + "=".repeat(60));
1899
+ console.log("Preflight");
1900
+ console.log("=".repeat(60));
1901
+ const preflight = new DotNS();
1902
+ await preflight.connect(resolveDotnsConnectOptions(options, envAssetHub, envAutoAccountMapping, envContracts, envNativeToEthRatio, envId, envPopSelfServe, envRegisterStorageDeposit));
1903
+ if (parsed?.isSubdomain) {
1904
+ try {
1905
+ const subResult = await preflight.checkSubdomainOwnership(parsed.sublabel, parsed.parentLabel);
1906
+ assertSubdomainOwnerMatchesSigner(subResult, preflight.evmAddress, parsed.sublabel, parsed.parentLabel);
1907
+ if (!subResult.owned) {
1908
+ const { owned: parentOwned, owner: parentOwner } = await preflight.checkOwnership(parsed.parentLabel);
1909
+ if (!parentOwned) {
1910
+ throw new NonRetryableError(
1911
+ `Cannot deploy ${parsed.fullName}: parent ${parsed.parentLabel}.dot is owned by ${parentOwner ?? "no one"}, not by this signer.`
1912
+ );
1913
+ }
1705
1914
  }
1915
+ previousContenthashCid = await readPreviousContenthashSafe(preflight, parsed.fullName);
1916
+ setDeployAttribute("deploy.incremental", previousContenthashCid ? "true" : "false");
1917
+ } finally {
1918
+ preflight.disconnect();
1919
+ }
1920
+ console.log(` Mode: subdomain (parent ${parsed.parentLabel}.dot owned by signer)`);
1921
+ } else {
1922
+ try {
1923
+ dotnsPreflight = await preflight.preflight(name);
1924
+ previousContenthashCid = await readPreviousContenthashSafe(preflight, name);
1925
+ setDeployAttribute("deploy.incremental", previousContenthashCid ? "true" : "false");
1926
+ } finally {
1927
+ preflight.disconnect();
1928
+ }
1929
+ if (dotnsPreflight) {
1930
+ setDeployAttribute("deploy.dotns.preflight.action", dotnsPreflight.plannedAction);
1931
+ setDeployAttribute("deploy.dotns.preflight.classification", popStatusName(dotnsPreflight.classification.status));
1932
+ }
1933
+ const alreadyOwned = dotnsPreflight.plannedAction === "already-owned-by-us";
1934
+ const reqSuffix = alreadyOwned ? " (already owned, requirement not enforced)" : "";
1935
+ console.log(` DotNS: ${name}.dot requires ${popStatusName(dotnsPreflight.classification.status)}${reqSuffix}`);
1936
+ if (dotnsPreflight.canProceed) {
1937
+ const fromName = popStatusName(dotnsPreflight.userStatus);
1938
+ console.log(` Your PoP: ${fromName}`);
1939
+ console.log(` Domain: ${dotnsPreflight.plannedAction === "already-owned-by-us" ? "owned by you" : "available"}`);
1940
+ }
1941
+ if (!dotnsPreflight.canProceed) {
1942
+ throw new NonRetryableError(
1943
+ dotnsPreflight.reason ?? "DotNS preflight rejected the deploy; please check the label and signer."
1944
+ );
1706
1945
  }
1707
- previousContenthashCid = await readPreviousContenthashSafe(preflight, parsed.fullName);
1708
- setDeployAttribute("deploy.incremental", previousContenthashCid ? "true" : "false");
1709
- } finally {
1710
- preflight.disconnect();
1711
- }
1712
- console.log(` Mode: subdomain (parent ${parsed.parentLabel}.dot owned by signer)`);
1713
- } else {
1714
- try {
1715
- dotnsPreflight = await preflight.preflight(name);
1716
- previousContenthashCid = await readPreviousContenthashSafe(preflight, name);
1717
- setDeployAttribute("deploy.incremental", previousContenthashCid ? "true" : "false");
1718
- } finally {
1719
- preflight.disconnect();
1720
- }
1721
- if (dotnsPreflight) {
1722
- setDeployAttribute("deploy.dotns.preflight.action", dotnsPreflight.plannedAction);
1723
- setDeployAttribute("deploy.dotns.preflight.classification", popStatusName(dotnsPreflight.classification.status));
1724
- }
1725
- const alreadyOwned = dotnsPreflight.plannedAction === "already-owned-by-us";
1726
- const reqSuffix = alreadyOwned ? " (already owned, requirement not enforced)" : "";
1727
- console.log(` DotNS: ${name}.dot requires ${popStatusName(dotnsPreflight.classification.status)}${reqSuffix}`);
1728
- if (dotnsPreflight.canProceed) {
1729
- const fromName = popStatusName(dotnsPreflight.userStatus);
1730
- console.log(` Your PoP: ${fromName}`);
1731
- console.log(` Domain: ${dotnsPreflight.plannedAction === "already-owned-by-us" ? "owned by you" : "available"}`);
1732
- }
1733
- if (!dotnsPreflight.canProceed) {
1734
- throw new NonRetryableError(
1735
- dotnsPreflight.reason ?? "DotNS preflight rejected the deploy; please check the label and signer."
1736
- );
1737
1946
  }
1738
- }
1739
- provider = await reconnect();
1740
- const providerWithReconnect = { ...provider, reconnect };
1741
- const isTestnet = await detectTestnet(provider.unsafeApi);
1742
- setDeployAttribute("deploy.is_testnet", isTestnet ? "true" : "false");
1743
- console.log("\n" + "=".repeat(60));
1744
- console.log("Storage");
1745
- console.log("=".repeat(60));
1746
- setDeployAttribute("deploy.content_type", "unknown");
1747
- setDeployAttribute("deploy.encrypted", "false");
1748
- await withSpan("deploy.storage", "1. storage", {}, async () => {
1749
- if (options.inputCar) {
1750
- setDeployAttribute("deploy.content_type", "inputCar");
1751
- const carPath = path.resolve(options.inputCar);
1752
- if (!fs.existsSync(carPath)) throw new Error(`CAR file not found: ${carPath}`);
1753
- console.log(`
1947
+ provider = await reconnect();
1948
+ const providerWithReconnect = { ...provider, reconnect };
1949
+ const isTestnet = await detectTestnet(provider.unsafeApi);
1950
+ setDeployAttribute("deploy.is_testnet", isTestnet ? "true" : "false");
1951
+ console.log("\n" + "=".repeat(60));
1952
+ console.log("Storage");
1953
+ console.log("=".repeat(60));
1954
+ setDeployAttribute("deploy.content_type", "unknown");
1955
+ setDeployAttribute("deploy.encrypted", "false");
1956
+ await withSpan("deploy.storage", "1. storage", {}, async () => {
1957
+ if (options.inputCar) {
1958
+ setDeployAttribute("deploy.content_type", "inputCar");
1959
+ const carPath = path.resolve(options.inputCar);
1960
+ if (!fs.existsSync(carPath)) throw new Error(`CAR file not found: ${carPath}`);
1961
+ console.log(`
1754
1962
  Mode: Pre-built CAR`);
1755
- console.log(` Path: ${carPath}`);
1756
- let carContent = fs.readFileSync(carPath);
1757
- console.log(` Size: ${(carContent.length / 1024 / 1024).toFixed(2)} MB`);
1758
- const reader = await CarReader.fromBytes(carContent);
1759
- const roots = await reader.getRoots();
1760
- if (roots.length === 0) throw new Error("CAR file has no roots");
1761
- ipfsCid = roots[0].toString();
1762
- console.log(` Root CID: ${ipfsCid}`);
1763
- if (options.password) {
1764
- setDeployAttribute("deploy.encrypted", "true");
1765
- console.log(` Encrypting CAR file...`);
1766
- carContent = await encryptContent(carContent, options.password);
1767
- console.log(` Encrypted: ${(carContent.length / 1024 / 1024).toFixed(2)} MB`);
1768
- }
1769
- let carChunks;
1770
- if (options.password) {
1771
- carChunks = chunk(carContent, CHUNK_SIZE);
1772
- } else {
1773
- try {
1774
- let prevStableOrder = [];
1775
- const manifestBytes = await extractManifestFromCar(carContent);
1776
- if (manifestBytes) {
1777
- const parsed2 = parseManifest(Buffer.from(manifestBytes).toString("utf8"));
1778
- if (parsed2.ok) prevStableOrder = parsed2.manifest.stableBlockOrder;
1779
- }
1780
- const rebuilt = await rebuildOrderedCarFromBytes(carContent, prevStableOrder);
1781
- if (Buffer.compare(Buffer.from(rebuilt.carBytes), Buffer.from(carContent)) === 0) {
1782
- carChunks = rebuilt.chunks;
1783
- } else {
1784
- captureWarning("input CAR ordered rechunk drift; falling back to size chunking", {
1785
- rootCid: ipfsCid
1963
+ console.log(` Path: ${carPath}`);
1964
+ let carContent = fs.readFileSync(carPath);
1965
+ console.log(` Size: ${(carContent.length / 1024 / 1024).toFixed(2)} MB`);
1966
+ const reader = await CarReader.fromBytes(carContent);
1967
+ const roots = await reader.getRoots();
1968
+ if (roots.length === 0) throw new Error("CAR file has no roots");
1969
+ ipfsCid = roots[0].toString();
1970
+ console.log(` Root CID: ${ipfsCid}`);
1971
+ if (options.password) {
1972
+ setDeployAttribute("deploy.encrypted", "true");
1973
+ console.log(` Encrypting CAR file...`);
1974
+ carContent = await encryptContent(carContent, options.password);
1975
+ console.log(` Encrypted: ${(carContent.length / 1024 / 1024).toFixed(2)} MB`);
1976
+ }
1977
+ let carChunks;
1978
+ if (options.password) {
1979
+ carChunks = chunk(carContent, CHUNK_SIZE);
1980
+ } else {
1981
+ try {
1982
+ let prevStableOrder = [];
1983
+ const manifestBytes = await extractManifestFromCar(carContent);
1984
+ if (manifestBytes) {
1985
+ const parsed2 = parseManifest(Buffer.from(manifestBytes).toString("utf8"));
1986
+ if (parsed2.ok) prevStableOrder = parsed2.manifest.stableBlockOrder;
1987
+ }
1988
+ const rebuilt = await rebuildOrderedCarFromBytes(carContent, prevStableOrder);
1989
+ if (Buffer.compare(Buffer.from(rebuilt.carBytes), Buffer.from(carContent)) === 0) {
1990
+ carChunks = rebuilt.chunks;
1991
+ } else {
1992
+ captureWarning("input CAR ordered rechunk drift; falling back to size chunking", {
1993
+ rootCid: ipfsCid
1994
+ });
1995
+ carChunks = chunk(carContent, CHUNK_SIZE);
1996
+ }
1997
+ } catch (err) {
1998
+ captureWarning("input CAR ordered rechunk failed; falling back to size chunking", {
1999
+ rootCid: ipfsCid,
2000
+ reason: err?.message ?? String(err)
1786
2001
  });
1787
2002
  carChunks = chunk(carContent, CHUNK_SIZE);
1788
2003
  }
1789
- } catch (err) {
1790
- captureWarning("input CAR ordered rechunk failed; falling back to size chunking", {
1791
- rootCid: ipfsCid,
1792
- reason: err?.message ?? String(err)
1793
- });
1794
- carChunks = chunk(carContent, CHUNK_SIZE);
1795
2004
  }
1796
- }
1797
- const predictedStorageCid = computeStorageCid(carChunks);
1798
- if (options.ghPagesMirror) {
1799
- mirrorPromise = mirrorToGitHubPages({
1800
- domain: name,
1801
- carBytes: carContent,
1802
- cid: predictedStorageCid,
1803
- toolVersion: VERSION,
1804
- bulletinRpc: BULLETIN_ENDPOINTS[0],
1805
- encrypted: Boolean(options.password)
1806
- }).catch((err) => err instanceof Error ? err : new Error(String(err)));
1807
- }
1808
- cid = (await storeChunkedContent(carChunks, providerWithReconnect)).storageCid;
1809
- } else if (process.env.IPFS_CID) {
1810
- setDeployAttribute("deploy.content_type", "ipfsCid");
1811
- if (options.password) {
1812
- throw new Error(
1813
- "IPFS_CID and --password are mutually exclusive: IPFS_CID skips the upload step, so there is nothing to encrypt. Either unset IPFS_CID to upload and encrypt fresh content, or remove --password to reuse the existing CID as-is."
1814
- );
1815
- }
1816
- cid = process.env.IPFS_CID;
1817
- ipfsCid = cid;
1818
- console.log(`
2005
+ const predictedStorageCid = computeStorageCid(carChunks);
2006
+ if (options.ghPagesMirror) {
2007
+ mirrorPromise = mirrorToGitHubPages({
2008
+ domain: name,
2009
+ carBytes: carContent,
2010
+ cid: predictedStorageCid,
2011
+ toolVersion: VERSION,
2012
+ bulletinRpc: BULLETIN_ENDPOINTS[0],
2013
+ encrypted: Boolean(options.password)
2014
+ }).catch((err) => err instanceof Error ? err : new Error(String(err)));
2015
+ }
2016
+ cid = (await storeChunkedContent(carChunks, providerWithReconnect)).storageCid;
2017
+ } else if (process.env.IPFS_CID) {
2018
+ setDeployAttribute("deploy.content_type", "ipfsCid");
2019
+ if (options.password) {
2020
+ throw new Error(
2021
+ "IPFS_CID and --password are mutually exclusive: IPFS_CID skips the upload step, so there is nothing to encrypt. Either unset IPFS_CID to upload and encrypt fresh content, or remove --password to reuse the existing CID as-is."
2022
+ );
2023
+ }
2024
+ cid = process.env.IPFS_CID;
2025
+ ipfsCid = cid;
2026
+ console.log(`
1819
2027
  Using CID: ${cid}`);
1820
- } else if (Array.isArray(content)) {
1821
- setDeployAttribute("deploy.content_type", "multiChunk");
1822
- console.log(`
1823
- Mode: Multi-chunk (${content.length} chunks)`);
1824
- let contentChunks = content;
1825
- if (options.password) {
1826
- setDeployAttribute("deploy.encrypted", "true");
1827
- console.log(` Encrypting...`);
1828
- const encrypted = await encryptContent(Buffer.concat(content), options.password);
1829
- console.log(` Encrypted: ${(encrypted.length / 1024).toFixed(1)} KB`);
1830
- contentChunks = chunk(encrypted);
1831
- }
1832
- cid = (await storeChunkedContent(contentChunks, providerWithReconnect)).storageCid;
1833
- } else if (typeof content === "string") {
1834
- setDeployAttribute("deploy.content_type", "path");
1835
- const contentPath = path.resolve(content);
1836
- if (!fs.existsSync(contentPath)) throw new Error(`Path not found: ${contentPath}`);
1837
- const stats = fs.statSync(contentPath);
1838
- if (stats.isDirectory()) {
1839
- setDeployAttribute("deploy.content_type", "directory");
2028
+ } else if (Array.isArray(content)) {
2029
+ setDeployAttribute("deploy.content_type", "multiChunk");
1840
2030
  console.log(`
2031
+ Mode: Multi-chunk (${content.length} chunks)`);
2032
+ let contentChunks = content;
2033
+ if (options.password) {
2034
+ setDeployAttribute("deploy.encrypted", "true");
2035
+ console.log(` Encrypting...`);
2036
+ const encrypted = await encryptContent(Buffer.concat(content), options.password);
2037
+ console.log(` Encrypted: ${(encrypted.length / 1024).toFixed(1)} KB`);
2038
+ contentChunks = chunk(encrypted);
2039
+ }
2040
+ cid = (await storeChunkedContent(contentChunks, providerWithReconnect)).storageCid;
2041
+ } else if (typeof content === "string") {
2042
+ setDeployAttribute("deploy.content_type", "path");
2043
+ const contentPath = path.resolve(content);
2044
+ if (!fs.existsSync(contentPath)) throw new Error(`Path not found: ${contentPath}`);
2045
+ const stats = fs.statSync(contentPath);
2046
+ if (stats.isDirectory()) {
2047
+ setDeployAttribute("deploy.content_type", "directory");
2048
+ console.log(`
1841
2049
  Mode: Directory`);
1842
- console.log(` Path: ${contentPath}`);
1843
- if (previousContenthashCid) console.log(` Incremental: previous contenthash ${previousContenthashCid}`);
1844
- else console.log(` Incremental: first deploy (no previous contenthash)`);
1845
- if (options.password) setDeployAttribute("deploy.encrypted", "true");
1846
- const storeFn = options.password ? storeDirectory : storeDirectoryV2;
1847
- const { storageCid: sCid, ipfsCid: iCid } = await storeFn(contentPath, {
1848
- provider: providerWithReconnect,
1849
- password: options.password,
1850
- jsMerkle: options.jsMerkle,
1851
- previousContenthash: previousContenthashCid,
1852
- allowLargeDeploy: options.allowLargeDeploy,
1853
- reproducibleSource: options.reproducibleSource,
1854
- domain: name,
1855
- gateway: envIpfs,
1856
- dumpCar: options.dumpCar,
1857
- onCarReady: (carBytes, predictedCid) => {
1858
- if (options.ghPagesMirror) {
1859
- mirrorPromise = mirrorToGitHubPages({
1860
- domain: name,
1861
- carBytes,
1862
- cid: predictedCid,
1863
- toolVersion: VERSION,
1864
- bulletinRpc: BULLETIN_ENDPOINTS[0],
1865
- encrypted: Boolean(options.password)
1866
- }).catch((err) => err instanceof Error ? err : new Error(String(err)));
2050
+ console.log(` Path: ${contentPath}`);
2051
+ if (previousContenthashCid) console.log(` Incremental: previous contenthash ${previousContenthashCid}`);
2052
+ else console.log(` Incremental: first deploy (no previous contenthash)`);
2053
+ if (options.password) setDeployAttribute("deploy.encrypted", "true");
2054
+ const storeFn = options.password ? storeDirectory : storeDirectoryV2;
2055
+ const { storageCid: sCid, ipfsCid: iCid } = await storeFn(contentPath, {
2056
+ provider: providerWithReconnect,
2057
+ password: options.password,
2058
+ jsMerkle: options.jsMerkle,
2059
+ previousContenthash: previousContenthashCid,
2060
+ allowLargeDeploy: options.allowLargeDeploy,
2061
+ reproducibleSource: options.reproducibleSource,
2062
+ domain: name,
2063
+ gateway: envIpfs,
2064
+ dumpCar: options.dumpCar,
2065
+ onCarReady: (carBytes, predictedCid) => {
2066
+ if (options.ghPagesMirror) {
2067
+ mirrorPromise = mirrorToGitHubPages({
2068
+ domain: name,
2069
+ carBytes,
2070
+ cid: predictedCid,
2071
+ toolVersion: VERSION,
2072
+ bulletinRpc: BULLETIN_ENDPOINTS[0],
2073
+ encrypted: Boolean(options.password)
2074
+ }).catch((err) => err instanceof Error ? err : new Error(String(err)));
2075
+ }
1867
2076
  }
2077
+ });
2078
+ cid = sCid;
2079
+ ipfsCid = iCid;
2080
+ } else {
2081
+ setDeployAttribute("deploy.content_type", "file");
2082
+ console.log(`
2083
+ Mode: File`);
2084
+ console.log(` Path: ${contentPath}`);
2085
+ let fileContent = fs.readFileSync(contentPath);
2086
+ if (options.password) {
2087
+ setDeployAttribute("deploy.encrypted", "true");
2088
+ console.log(` Encrypting...`);
2089
+ fileContent = await encryptContent(fileContent, options.password);
2090
+ console.log(` Encrypted: ${(fileContent.length / 1024).toFixed(1)} KB`);
1868
2091
  }
1869
- });
1870
- cid = sCid;
1871
- ipfsCid = iCid;
1872
- } else {
1873
- setDeployAttribute("deploy.content_type", "file");
2092
+ if (fileContent.length > MAX_FILE_SIZE) {
2093
+ console.log(` Exceeds 8MB, chunking...`);
2094
+ cid = (await storeChunkedContent(chunk(fileContent), providerWithReconnect)).storageCid;
2095
+ } else {
2096
+ cid = await storeFile(fileContent, providerWithReconnect);
2097
+ }
2098
+ }
2099
+ } else if (content instanceof Uint8Array) {
2100
+ setDeployAttribute("deploy.content_type", "multiChunk");
1874
2101
  console.log(`
1875
- Mode: File`);
1876
- console.log(` Path: ${contentPath}`);
1877
- let fileContent = fs.readFileSync(contentPath);
2102
+ Mode: Bytes`);
2103
+ let bytesContent = content;
1878
2104
  if (options.password) {
1879
2105
  setDeployAttribute("deploy.encrypted", "true");
1880
2106
  console.log(` Encrypting...`);
1881
- fileContent = await encryptContent(fileContent, options.password);
1882
- console.log(` Encrypted: ${(fileContent.length / 1024).toFixed(1)} KB`);
2107
+ bytesContent = await encryptContent(bytesContent, options.password);
2108
+ console.log(` Encrypted: ${(bytesContent.length / 1024).toFixed(1)} KB`);
1883
2109
  }
1884
- if (fileContent.length > MAX_FILE_SIZE) {
2110
+ if (bytesContent.length > MAX_FILE_SIZE) {
1885
2111
  console.log(` Exceeds 8MB, chunking...`);
1886
- cid = (await storeChunkedContent(chunk(fileContent), providerWithReconnect)).storageCid;
2112
+ cid = (await storeChunkedContent(chunk(bytesContent), providerWithReconnect)).storageCid;
1887
2113
  } else {
1888
- cid = await storeFile(fileContent, providerWithReconnect);
2114
+ cid = await storeFile(bytesContent, providerWithReconnect);
1889
2115
  }
1890
- }
1891
- } else if (content instanceof Uint8Array) {
1892
- setDeployAttribute("deploy.content_type", "multiChunk");
1893
- console.log(`
1894
- Mode: Bytes`);
1895
- let bytesContent = content;
1896
- if (options.password) {
1897
- setDeployAttribute("deploy.encrypted", "true");
1898
- console.log(` Encrypting...`);
1899
- bytesContent = await encryptContent(bytesContent, options.password);
1900
- console.log(` Encrypted: ${(bytesContent.length / 1024).toFixed(1)} KB`);
1901
- }
1902
- if (bytesContent.length > MAX_FILE_SIZE) {
1903
- console.log(` Exceeds 8MB, chunking...`);
1904
- cid = (await storeChunkedContent(chunk(bytesContent), providerWithReconnect)).storageCid;
1905
2116
  } else {
1906
- cid = await storeFile(bytesContent, providerWithReconnect);
2117
+ throw new Error("Invalid content: must be path, Uint8Array, or Array<Uint8Array>");
1907
2118
  }
1908
- } else {
1909
- throw new Error("Invalid content: must be path, Uint8Array, or Array<Uint8Array>");
1910
- }
1911
- });
1912
- setDeployAttribute("deploy.cid", cid);
1913
- if (options.attributes) {
1914
- for (const [key, value] of Object.entries(options.attributes)) {
1915
- setDeployAttribute(key, value);
1916
- }
1917
- }
1918
- console.log("\n" + "=".repeat(60));
1919
- console.log("DotNS");
1920
- console.log("=".repeat(60));
1921
- await withSpan("deploy.dotns", "2. dotns", { "deploy.domain": name, "deploy.subdomain": String(parsed?.isSubdomain ?? false) }, async () => {
1922
- const dotns = new DotNS();
1923
- await dotns.connect(resolveDotnsConnectOptions(options, envAssetHub, envAutoAccountMapping, envContracts, envNativeToEthRatio, envId, envPopSelfServe, envRegisterStorageDeposit));
1924
- if (parsed?.isSubdomain) {
1925
- const { owned, owner } = await dotns.checkSubdomainOwnership(parsed.sublabel, parsed.parentLabel);
1926
- if (owned) {
1927
- console.log(` Status: Already owned`);
1928
- } else if (owner) {
1929
- throw new Error(`Subdomain ${parsed.fullName} is owned by ${owner}, not ${dotns.evmAddress}`);
1930
- } else {
1931
- const parentOwnership = await dotns.checkOwnership(parsed.parentLabel);
1932
- if (!parentOwnership.owned) throw new Error(`You must own ${parsed.parentLabel}.dot to register subdomains under it`);
1933
- console.log(` Status: Registering subdomain...`);
1934
- await dotns.registerSubdomain(parsed.sublabel, parsed.parentLabel);
1935
- }
1936
- } else {
1937
- const { owned } = await dotns.checkOwnership(name);
1938
- if (owned) {
1939
- console.log(` Status: Already owned`);
1940
- } else {
1941
- console.log(` Status: Registering...`);
1942
- await dotns.register(name);
2119
+ });
2120
+ setDeployAttribute("deploy.cid", cid);
2121
+ if (options.attributes) {
2122
+ for (const [key, value] of Object.entries(options.attributes)) {
2123
+ setDeployAttribute(key, value);
1943
2124
  }
1944
2125
  }
1945
- const contenthashHex = `0x${encodeContenthash(cid)}`;
1946
- await dotns.setContenthash(name, contenthashHex);
1947
- if (options.publish && parsed) {
1948
- await publish(dotns, parsed, options.failOnPublishError);
1949
- }
1950
- dotns.disconnect();
1951
- });
1952
- await withSpan("deploy.p2p-check", "3. p2p-check", { "deploy.domain": name }, async () => {
1953
- const probe = await probeP2pRetrieval(provider.client, cid);
1954
- setDeployAttribute("deploy.p2p.retrievable", probe.retrievable ? "true" : "false");
1955
- setDeployAttribute("deploy.p2p.check_ms", String(probe.durationMs));
1956
- setDeployAttribute("deploy.p2p.error_variant", probe.errorVariant);
1957
- if (probe.retrievable) {
1958
- console.log(` P2P retrieval: \u2713 (${probe.durationMs}ms)`);
1959
- } else {
1960
- console.log(` P2P retrieval: \u26A0 not yet retrievable (${probe.errorVariant}, ${probe.durationMs}ms)`);
1961
- }
1962
- });
1963
- if (options.ghPagesMirror) {
1964
2126
  console.log("\n" + "=".repeat(60));
1965
- console.log("Final checks");
2127
+ console.log("DotNS");
1966
2128
  console.log("=".repeat(60));
1967
- await withSpan("deploy.gh-pages-mirror", "4. gh-pages-mirror", { "deploy.domain": name }, async () => {
1968
- const mirror = await mirrorPromise;
1969
- if (mirror === null) {
1970
- console.log(" GitHub Pages mirror: skipped (only directory deploys produce a CAR suitable for mirroring).");
1971
- return;
1972
- }
1973
- if (mirror instanceof MirrorSkipped) {
1974
- console.log(` GitHub Pages mirror: skipped \u2014 ${mirror.message}`);
1975
- return;
2129
+ await withSpan("deploy.dotns", "2. dotns", { "deploy.domain": name, "deploy.subdomain": String(parsed?.isSubdomain ?? false) }, async () => {
2130
+ const dotns = new DotNS();
2131
+ await dotns.connect(resolveDotnsConnectOptions(options, envAssetHub, envAutoAccountMapping, envContracts, envNativeToEthRatio, envId, envPopSelfServe, envRegisterStorageDeposit));
2132
+ if (parsed?.isSubdomain) {
2133
+ const { owned, owner } = await dotns.checkSubdomainOwnership(parsed.sublabel, parsed.parentLabel);
2134
+ if (owned) {
2135
+ console.log(` Status: Already owned`);
2136
+ } else if (owner) {
2137
+ throw new Error(`Subdomain ${parsed.fullName} is owned by ${owner}, not ${dotns.evmAddress}`);
2138
+ } else {
2139
+ const parentOwnership = await dotns.checkOwnership(parsed.parentLabel);
2140
+ if (!parentOwnership.owned) throw new Error(`You must own ${parsed.parentLabel}.dot to register subdomains under it`);
2141
+ console.log(` Status: Registering subdomain...`);
2142
+ await dotns.registerSubdomain(parsed.sublabel, parsed.parentLabel);
2143
+ }
2144
+ } else {
2145
+ const { owned } = await dotns.checkOwnership(name);
2146
+ if (owned) {
2147
+ console.log(` Status: Already owned`);
2148
+ } else {
2149
+ console.log(` Status: Registering...`);
2150
+ await dotns.register(name);
2151
+ }
1976
2152
  }
1977
- if (mirror instanceof Error) {
1978
- console.log(` GitHub Pages mirror: failed (non-fatal) \u2014 ${mirror.message}`);
1979
- captureWarning("gh-pages mirror failed", { error: mirror.message.slice(0, 200) });
1980
- return;
2153
+ const contenthashHex = `0x${encodeContenthash(cid)}`;
2154
+ await dotns.setContenthash(name, contenthashHex);
2155
+ if (options.publish && parsed) {
2156
+ await publish(dotns, parsed, options.failOnPublishError);
1981
2157
  }
1982
- console.log(` Mirror: ${mirror.url}`);
1983
- console.log(` Manifest: https://${mirror.owner}.github.io/${mirror.repo}/${mirror.manifestPath}`);
1984
- setDeployAttribute("deploy.gh_pages_url", mirror.url);
1985
- process.stdout.write(" Verifying Pages serves this deploy's CAR... ");
1986
- const freshness = await pollMirrorFreshness(mirror.url, cid, { timeoutMs: 3 * 60 * 1e3, intervalMs: 1e4 });
1987
- if (freshness.verified) {
1988
- console.log(`ok (${freshness.attempts} attempt${freshness.attempts === 1 ? "" : "s"}, ${(freshness.durationMs / 1e3).toFixed(0)}s).`);
1989
- setDeployAttribute("deploy.gh_pages_freshness_verified", "true");
2158
+ dotns.disconnect();
2159
+ });
2160
+ await withSpan("deploy.p2p-check", "3. p2p-check", { "deploy.domain": name }, async () => {
2161
+ const probe = await probeP2pRetrieval(provider.client, cid);
2162
+ setDeployAttribute("deploy.p2p.retrievable", probe.retrievable ? "true" : "false");
2163
+ setDeployAttribute("deploy.p2p.check_ms", String(probe.durationMs));
2164
+ setDeployAttribute("deploy.p2p.error_variant", probe.errorVariant);
2165
+ if (probe.retrievable) {
2166
+ console.log(` P2P retrieval: \u2713 (${probe.durationMs}ms)`);
1990
2167
  } else {
1991
- console.log(`timed out.`);
1992
- console.log(` GitHub Pages last served cid=${freshness.lastCid ?? "n/a"} (expected ${cid}); it should catch up shortly. Non-fatal.`);
1993
- setDeployAttribute("deploy.gh_pages_freshness_verified", "false");
2168
+ console.log(` P2P retrieval: \u26A0 not yet retrievable (${probe.errorVariant}, ${probe.durationMs}ms)`);
1994
2169
  }
1995
2170
  });
2171
+ if (options.ghPagesMirror) {
2172
+ console.log("\n" + "=".repeat(60));
2173
+ console.log("Final checks");
2174
+ console.log("=".repeat(60));
2175
+ await withSpan("deploy.gh-pages-mirror", "4. gh-pages-mirror", { "deploy.domain": name }, async () => {
2176
+ const mirror = await mirrorPromise;
2177
+ if (mirror === null) {
2178
+ console.log(" GitHub Pages mirror: skipped (only directory deploys produce a CAR suitable for mirroring).");
2179
+ return;
2180
+ }
2181
+ if (mirror instanceof MirrorSkipped) {
2182
+ console.log(` GitHub Pages mirror: skipped \u2014 ${mirror.message}`);
2183
+ return;
2184
+ }
2185
+ if (mirror instanceof Error) {
2186
+ console.log(` GitHub Pages mirror: failed (non-fatal) \u2014 ${mirror.message}`);
2187
+ captureWarning("gh-pages mirror failed", { error: mirror.message.slice(0, 200) });
2188
+ return;
2189
+ }
2190
+ console.log(` Mirror: ${mirror.url}`);
2191
+ console.log(` Manifest: https://${mirror.owner}.github.io/${mirror.repo}/${mirror.manifestPath}`);
2192
+ setDeployAttribute("deploy.gh_pages_url", mirror.url);
2193
+ process.stdout.write(" Verifying Pages serves this deploy's CAR... ");
2194
+ const freshness = await pollMirrorFreshness(mirror.url, cid, { timeoutMs: 3 * 60 * 1e3, intervalMs: 1e4 });
2195
+ if (freshness.verified) {
2196
+ console.log(`ok (${freshness.attempts} attempt${freshness.attempts === 1 ? "" : "s"}, ${(freshness.durationMs / 1e3).toFixed(0)}s).`);
2197
+ setDeployAttribute("deploy.gh_pages_freshness_verified", "true");
2198
+ } else {
2199
+ console.log(`timed out.`);
2200
+ console.log(` GitHub Pages last served cid=${freshness.lastCid ?? "n/a"} (expected ${cid}); it should catch up shortly. Non-fatal.`);
2201
+ setDeployAttribute("deploy.gh_pages_freshness_verified", "false");
2202
+ }
2203
+ });
2204
+ }
2205
+ console.log("\n" + "=".repeat(60));
2206
+ console.log("DEPLOYMENT COMPLETE!");
2207
+ console.log("=".repeat(60));
2208
+ console.log("\n\u{1F53A} Polkadot Triangle");
2209
+ console.log(` - Polkadot Desktop: ${name}.dot`);
2210
+ console.log(` - Polkadot Browser: ${browserUrlFor(name, envId)}`);
2211
+ console.log("\n" + "=".repeat(60) + "\n");
2212
+ return { domainName: name, fullDomain: `${name}.dot`, cid, ipfsCid };
2213
+ } finally {
2214
+ if (_deployRpcFailedOver) setDeployAttribute("deploy.rpc.failed_over", "true");
2215
+ provider?.client.destroy();
1996
2216
  }
1997
- console.log("\n" + "=".repeat(60));
1998
- console.log("DEPLOYMENT COMPLETE!");
1999
- console.log("=".repeat(60));
2000
- console.log("\n\u{1F53A} Polkadot Triangle");
2001
- console.log(` - Polkadot Desktop: ${name}.dot`);
2002
- console.log(` - Polkadot Browser: ${browserUrlFor(name, envId)}`);
2003
- console.log("\n" + "=".repeat(60) + "\n");
2004
- return { domainName: name, fullDomain: `${name}.dot`, cid, ipfsCid };
2005
- } finally {
2006
- if (_deployRpcFailedOver) setDeployAttribute("deploy.rpc.failed_over", "true");
2007
- provider?.client.destroy();
2008
- }
2009
- });
2217
+ });
2218
+ } finally {
2219
+ sessionCleanup?.();
2220
+ }
2010
2221
  }
2011
2222
 
2012
2223
  // src/merkle.ts
@@ -2183,12 +2394,13 @@ async function merkleizeKuboBackend(directoryPath) {
2183
2394
  const { fileBlocks, fileCids, rootBlockCids, subdirCids } = walkFileBlocks(cidStr, blocks);
2184
2395
  return { rootCid: cidStr, blocks, fileBlocks, fileCids, rootBlockCids, subdirCids };
2185
2396
  }
2186
- async function merkleizeBackend(directoryPath, useKubo) {
2397
+ async function merkleizeBackend(directoryPath, useKubo, phase) {
2398
+ const tag = phase ? ` \u2014 ${phase}` : "";
2187
2399
  if (useKubo) {
2188
- console.log(` Merkleizing (Kubo): ${directoryPath}`);
2400
+ console.log(` Merkleizing (Kubo${tag}): ${directoryPath}`);
2189
2401
  return merkleizeKuboBackend(directoryPath);
2190
2402
  }
2191
- console.log(` Merkleizing (JS): ${directoryPath}`);
2403
+ console.log(` Merkleizing (JS${tag}): ${directoryPath}`);
2192
2404
  return merkleizeJSBackend(directoryPath);
2193
2405
  }
2194
2406
  function encodeCarFrame(cid, payload) {
@@ -2237,6 +2449,7 @@ function frameBlockCid(frame) {
2237
2449
  async function buildOrderedCar(options) {
2238
2450
  const { output, prevStableOrder = [] } = options;
2239
2451
  const clsFn = options.classifyFn ?? ((p) => classifyFile(p));
2452
+ const tag = options.phase ? ` \u2014 ${options.phase}` : "";
2240
2453
  const MANIFEST_PATH_LITERAL = ".bulletin-deploy/manifest.json";
2241
2454
  const stableFiles = [];
2242
2455
  const volatileFiles = [];
@@ -2321,7 +2534,7 @@ async function buildOrderedCar(options) {
2321
2534
  const s1Bytes = section1Chunks.reduce((s, b) => s + b.length, 0);
2322
2535
  const s2Bytes = section2Chunks.reduce((s, b) => s + b.length, 0);
2323
2536
  console.log(
2324
- ` CAR (3-section): ${(carBytes.length / 1024 / 1024).toFixed(2)} MB (s0=${section0Bytes.length}B s1=${s1Bytes}B s2=${s2Bytes}B), ${allChunks.length} frames (${section0Chunks.length} header + ${section1Chunks.length} data + ${section2Chunks.length} manifest)`
2537
+ ` CAR (3-section${tag}): ${(carBytes.length / 1024 / 1024).toFixed(2)} MB (s0=${section0Bytes.length}B s1=${s1Bytes}B s2=${s2Bytes}B), ${allChunks.length} frames (${section0Chunks.length} header + ${section1Chunks.length} data + ${section2Chunks.length} manifest)`
2325
2538
  );
2326
2539
  return {
2327
2540
  carBytes,
@@ -2358,8 +2571,9 @@ async function rebuildOrderedCarFromBytes(carBytes, prevStableOrder = []) {
2358
2571
  }
2359
2572
  async function merkleizeWithStableOrder(directoryPath, prevStableOrder, options) {
2360
2573
  const useKubo = options?.useKubo ?? false;
2361
- const output = await merkleizeBackend(directoryPath, useKubo);
2362
- return buildOrderedCar({ output, classifyFn: options?.classifyFn, prevStableOrder });
2574
+ const phase = options?.phase;
2575
+ const output = await merkleizeBackend(directoryPath, useKubo, phase);
2576
+ return buildOrderedCar({ output, classifyFn: options?.classifyFn, prevStableOrder, phase });
2363
2577
  }
2364
2578
  async function merkleizeJS(directoryPath) {
2365
2579
  console.log(` Merkleizing (JS): ${directoryPath}`);
@@ -2403,11 +2617,18 @@ export {
2403
2617
  rebuildOrderedCarFromBytes,
2404
2618
  merkleizeWithStableOrder,
2405
2619
  merkleizeJS,
2620
+ readBulletinSlotSigner,
2621
+ writeBulletinSlotKey,
2622
+ extractBulletinSlotKey,
2623
+ getSlotSignerProvider,
2406
2624
  friendlyChainError,
2407
2625
  DEFAULT_BULLETIN_RPC,
2408
2626
  DEFAULT_POOL_SIZE,
2627
+ BULLETIN_ENDPOINTS,
2409
2628
  setWsHaltCallback,
2629
+ makeBulletinStatusHandler,
2410
2630
  CHUNK_MORTALITY_PERIOD,
2631
+ WS_HEARTBEAT_TIMEOUT_MS,
2411
2632
  retryBudgetExhausted,
2412
2633
  isConnectionError,
2413
2634
  deriveRootSigner,
@@ -2421,6 +2642,7 @@ export {
2421
2642
  ENCRYPT_PBKDF2_ITERATIONS,
2422
2643
  encryptContent,
2423
2644
  __selectStorageProviderModeForTest,
2645
+ chooseSignerInput,
2424
2646
  storeFile,
2425
2647
  __assignDenseNoncesForTest,
2426
2648
  storeChunkedContent,