@parity/product-deploy 0.8.3-rc.0 → 0.8.3-rc.11

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 (62) hide show
  1. package/README.md +53 -2
  2. package/bin/bulletin-deploy +54 -8
  3. package/dist/allocations-B65Is4Md.d.ts +97 -0
  4. package/dist/auth/index.d.ts +7 -0
  5. package/dist/auth/index.js +26 -0
  6. package/dist/auth/vendor/index.d.ts +32 -0
  7. package/dist/auth/vendor/index.js +27 -0
  8. package/dist/auth/vendor/ui/index.d.ts +15 -0
  9. package/dist/auth/vendor/ui/index.js +10 -0
  10. package/dist/auth-DkRZBK-T.d.ts +122 -0
  11. package/dist/auth-config.d.ts +39 -0
  12. package/dist/auth-config.js +20 -0
  13. package/dist/bug-report.js +4 -4
  14. package/dist/chunk-2OZVKA3D.js +410 -0
  15. package/dist/{chunk-PV3N2XTC.js → chunk-4CZ4GIWK.js} +677 -356
  16. package/dist/{chunk-LJMYOXQV.js → chunk-4GX3KJPU.js} +12 -1
  17. package/dist/{chunk-OAWXITVX.js → chunk-4W6VNILJ.js} +2 -2
  18. package/dist/{chunk-CBCUKOOJ.js → chunk-AKCO2LGH.js} +1 -1
  19. package/dist/{chunk-VRZXAB7J.js → chunk-AMGKEAOH.js} +2 -2
  20. package/dist/chunk-JQKKMUCT.js +0 -0
  21. package/dist/{chunk-UXVBF7TD.js → chunk-KFIIAUQU.js} +17 -3
  22. package/dist/{chunk-L5Z3TJD7.js → chunk-OCKCB72S.js} +6 -6
  23. package/dist/{chunk-YT2XCGZK.js → chunk-P2ZOBSCJ.js} +12 -5
  24. package/dist/chunk-RIRDBSBG.js +36 -0
  25. package/dist/{chunk-Z6PRIHI7.js → chunk-X2Q5FPIQ.js} +1 -1
  26. package/dist/chunk-YUSHBZBX.js +52 -0
  27. package/dist/chunk-probe.js +3 -3
  28. package/dist/commands/login.d.ts +28 -0
  29. package/dist/commands/login.js +116 -0
  30. package/dist/commands/logout.d.ts +21 -0
  31. package/dist/commands/logout.js +37 -0
  32. package/dist/commands/whoami.d.ts +22 -0
  33. package/dist/commands/whoami.js +47 -0
  34. package/dist/deploy.d.ts +49 -3
  35. package/dist/deploy.js +19 -8
  36. package/dist/dotns.d.ts +7 -0
  37. package/dist/dotns.js +3 -3
  38. package/dist/index.d.ts +1 -0
  39. package/dist/index.js +13 -12
  40. package/dist/manifest/publish.js +10 -9
  41. package/dist/memory-report.js +2 -2
  42. package/dist/merkle.d.ts +3 -1
  43. package/dist/merkle.js +9 -8
  44. package/dist/personhood/bind-paid-alias.js +3 -3
  45. package/dist/personhood/bind-personal-id.js +2 -2
  46. package/dist/personhood/bootstrap.js +16 -16
  47. package/dist/personhood/claim-pgas.js +2 -2
  48. package/dist/personhood/people-client.js +3 -3
  49. package/dist/personhood/proof-validity.js +2 -2
  50. package/dist/personhood/reprove.js +5 -5
  51. package/dist/run-state.js +1 -1
  52. package/dist/signer-CriGqahj.d.ts +35 -0
  53. package/dist/storage-signer.d.ts +38 -0
  54. package/dist/storage-signer.js +28 -0
  55. package/dist/telemetry.d.ts +1 -1
  56. package/dist/telemetry.js +2 -2
  57. package/dist/version-check.js +3 -3
  58. package/package.json +17 -3
  59. package/tools/release-retry-wrapper.mjs +1 -0
  60. package/dist/{chunk-LHLCPDGL.js → chunk-7URNKK6J.js} +3 -3
  61. package/dist/{chunk-7Y7RDOGT.js → chunk-EATOPQFR.js} +5 -5
  62. 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,24 +23,30 @@ 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-YUSHBZBX.js";
21
31
  import {
22
32
  setDeployContext
23
- } from "./chunk-VRZXAB7J.js";
33
+ } from "./chunk-AMGKEAOH.js";
24
34
  import {
25
35
  probeChunks
26
- } from "./chunk-Z6PRIHI7.js";
36
+ } from "./chunk-X2Q5FPIQ.js";
27
37
  import {
28
38
  packSection
29
39
  } from "./chunk-C2TS5MER.js";
30
40
  import {
31
41
  DotNS,
42
+ PUBLISHER_ABI,
32
43
  PublisherNotSupportedError,
33
44
  TX_TIMEOUT_MS,
34
45
  fetchNonce,
35
46
  parseDomainName,
36
47
  popStatusName,
37
48
  verifyNonceAdvanced
38
- } from "./chunk-LJMYOXQV.js";
49
+ } from "./chunk-4GX3KJPU.js";
39
50
  import {
40
51
  derivePoolAccounts,
41
52
  detectTestnet,
@@ -57,7 +68,7 @@ import {
57
68
  truncateAddress,
58
69
  withDeploySpan,
59
70
  withSpan
60
- } from "./chunk-YT2XCGZK.js";
71
+ } from "./chunk-P2ZOBSCJ.js";
61
72
  import {
62
73
  DEFAULT_ENV_ID,
63
74
  getPopSelfServeConfig,
@@ -67,11 +78,6 @@ import {
67
78
  import {
68
79
  NonRetryableError
69
80
  } from "./chunk-ZOC4GITL.js";
70
- import {
71
- MirrorSkipped,
72
- mirrorToGitHubPages,
73
- pollMirrorFreshness
74
- } from "./chunk-HOTQDYHD.js";
75
81
 
76
82
  // src/merkle.ts
77
83
  import * as fs2 from "fs";
@@ -91,19 +97,186 @@ import * as path from "path";
91
97
  import { execSync } from "child_process";
92
98
  import { sha256 } from "@noble/hashes/sha256";
93
99
  import { blake2b } from "@noble/hashes/blake2b";
94
- import { createClient as createPolkadotClient, Enum } from "polkadot-api";
95
- import { getWsProvider, WsEvent } from "polkadot-api/ws";
100
+ import { createClient as createPolkadotClient2, Enum as Enum2 } from "polkadot-api";
101
+ import { getWsProvider as getWsProvider2, WsEvent } from "polkadot-api/ws";
96
102
  import { CID } from "multiformats/cid";
97
103
  import { create as createMultihash } from "multiformats/hashes/digest";
98
104
  import { base32 } from "multiformats/bases/base32";
99
105
  import { base58btc } from "multiformats/bases/base58";
100
106
  import * as dagPB from "@ipld/dag-pb";
101
107
  import { UnixFS } from "ipfs-unixfs";
108
+ import { keccak256, toBytes } from "viem";
102
109
  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";
110
+ import { getPolkadotSigner as getPolkadotSigner2 } from "polkadot-api/signer";
111
+ import { sr25519CreateDerive as sr25519CreateDerive2 } from "@polkadot-labs/hdkd";
112
+ import { mnemonicToEntropy, entropyToMiniSecret, ss58Address as ss58Address2 } from "@polkadot-labs/hdkd-helpers";
106
113
  import { CarReader } from "@ipld/car/reader";
114
+
115
+ // src/storage-signer.ts
116
+ import { readFile, writeFile, mkdir } from "fs/promises";
117
+ import { homedir } from "os";
118
+ import { join, dirname } from "path";
119
+
120
+ // node_modules/@polkadot-api/utils/dist/hex.js
121
+ var HEX_STR = "0123456789abcdef";
122
+ function toHex(bytes) {
123
+ const result = new Array(bytes.length + 1);
124
+ result[0] = "0x";
125
+ for (let i = 0; i < bytes.length; ) {
126
+ const b = bytes[i++];
127
+ result[i] = HEX_STR[b >> 4] + HEX_STR[b & 15];
128
+ }
129
+ return result.join("");
130
+ }
131
+ var HEX_MAP = {
132
+ 0: 0,
133
+ 1: 1,
134
+ 2: 2,
135
+ 3: 3,
136
+ 4: 4,
137
+ 5: 5,
138
+ 6: 6,
139
+ 7: 7,
140
+ 8: 8,
141
+ 9: 9,
142
+ a: 10,
143
+ b: 11,
144
+ c: 12,
145
+ d: 13,
146
+ e: 14,
147
+ f: 15,
148
+ A: 10,
149
+ B: 11,
150
+ C: 12,
151
+ D: 13,
152
+ E: 14,
153
+ F: 15
154
+ };
155
+ function fromHex(hexString) {
156
+ const isOdd = hexString.length % 2;
157
+ const base = (hexString[1] === "x" ? 2 : 0) + isOdd;
158
+ const nBytes = (hexString.length - base) / 2 + isOdd;
159
+ const bytes = new Uint8Array(nBytes);
160
+ if (isOdd) bytes[0] = 0 | HEX_MAP[hexString[2]];
161
+ for (let i = 0; i < nBytes; ) {
162
+ const idx = base + i * 2;
163
+ const a = HEX_MAP[hexString[idx]];
164
+ const b = HEX_MAP[hexString[idx + 1]];
165
+ bytes[isOdd + i++] = a << 4 | b;
166
+ }
167
+ return bytes;
168
+ }
169
+
170
+ // src/storage-signer.ts
171
+ import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
172
+ import { sr25519, ss58Address } from "@polkadot-labs/hdkd-helpers";
173
+ import { createClient as createPolkadotClient, Enum } from "polkadot-api";
174
+ import { getPolkadotSigner } from "polkadot-api/signer";
175
+ import { getWsProvider } from "polkadot-api/ws";
176
+ function sanitize(appId) {
177
+ return appId.replace(/[^a-zA-Z0-9_.-]/g, "_");
178
+ }
179
+ function cacheFilePath(appId, storageDir) {
180
+ return join(storageDir ?? homedir(), ".polkadot-apps", `${sanitize(appId)}_AllowanceKeys.json`);
181
+ }
182
+ function normalizeSchnorrkelKey(key) {
183
+ if (key.length !== 64) return key;
184
+ const out = new Uint8Array(key);
185
+ let carry = 0;
186
+ for (let i = 0; i < 32; i++) {
187
+ const v = key[i] * 8 + carry;
188
+ out[i] = v & 255;
189
+ carry = v >> 8;
190
+ }
191
+ return out;
192
+ }
193
+ function signerFromSecret(secret) {
194
+ if (secret.length === 32) {
195
+ const kp = sr25519CreateDerive(secret)("");
196
+ return getPolkadotSigner(kp.publicKey, "Sr25519", async (d) => kp.sign(d));
197
+ }
198
+ if (secret.length === 64) {
199
+ const normalized = normalizeSchnorrkelKey(secret);
200
+ const pub = sr25519.getPublicKey(normalized);
201
+ return getPolkadotSigner(pub, "Sr25519", async (d) => sr25519.sign(d, normalized));
202
+ }
203
+ throw new Error(
204
+ `BulletInAllowance slot key: unexpected length ${secret.length} (expected 32 or 64)`
205
+ );
206
+ }
207
+ async function readBulletinSlotSigner(appId, storageDir) {
208
+ let raw;
209
+ try {
210
+ raw = await readFile(cacheFilePath(appId, storageDir), "utf-8");
211
+ } catch (e) {
212
+ if (e?.code === "ENOENT") return null;
213
+ throw e;
214
+ }
215
+ let cache;
216
+ try {
217
+ cache = JSON.parse(raw);
218
+ } catch {
219
+ return null;
220
+ }
221
+ const entry = cache?.entries?.BulletInAllowance;
222
+ if (!entry?.slotAccountKey) return null;
223
+ let secret;
224
+ try {
225
+ secret = fromHex(entry.slotAccountKey);
226
+ } catch {
227
+ return null;
228
+ }
229
+ const signer = signerFromSecret(secret);
230
+ return { signer, ss58: ss58Address(signer.publicKey) };
231
+ }
232
+ async function writeBulletinSlotKey(appId, hexKey, storageDir) {
233
+ const path3 = cacheFilePath(appId, storageDir);
234
+ await mkdir(dirname(path3), { recursive: true, mode: 448 });
235
+ let existing = { version: 1, entries: {} };
236
+ try {
237
+ existing = JSON.parse(await readFile(path3, "utf-8"));
238
+ } catch {
239
+ }
240
+ existing.entries ??= {};
241
+ existing.entries.BulletInAllowance = { tag: "BulletInAllowance", slotAccountKey: hexKey };
242
+ await writeFile(path3, `${JSON.stringify(existing, null, 2)}
243
+ `, { mode: 384 });
244
+ }
245
+ function extractBulletinSlotKey(outcomes) {
246
+ for (const outcome of outcomes) {
247
+ if (outcome.tag !== "Allocated") continue;
248
+ const allocated = outcome.value;
249
+ if (allocated?.tag !== "BulletInAllowance") continue;
250
+ const key = allocated.value?.slotAccountKey;
251
+ if (!(key instanceof Uint8Array)) continue;
252
+ return toHex(key);
253
+ }
254
+ return null;
255
+ }
256
+ async function getSlotSignerProvider(signer, ss58) {
257
+ const primary = BULLETIN_ENDPOINTS[0];
258
+ console.log(` Connecting to Bulletin (slot signer): ${primary}`);
259
+ const client = createPolkadotClient(getWsProvider(
260
+ BULLETIN_ENDPOINTS,
261
+ { heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS, onStatusChanged: makeBulletinStatusHandler(primary) }
262
+ ));
263
+ const unsafeApi = client.getUnsafeApi();
264
+ const [auth, currentBlock] = await Promise.all([
265
+ unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum("Account", ss58)),
266
+ client.getFinalizedBlock()
267
+ ]);
268
+ const now = currentBlock.number;
269
+ if (!auth || Number(auth.expiration ?? 0) <= now) {
270
+ client.destroy();
271
+ throw new Error(`Slot account ${ss58} not authorized on Bulletin`);
272
+ }
273
+ console.log(` Using slot signer: ${ss58} (authorized until block ${Number(auth?.expiration ?? 0)})`);
274
+ setDeployAttribute("deploy.signer.mode", "slot");
275
+ setDeployAttribute("deploy.signer.address", truncateAddress(ss58));
276
+ return { client, unsafeApi, signer, ss58 };
277
+ }
278
+
279
+ // src/deploy.ts
107
280
  function friendlyChainError(msg) {
108
281
  if (/"type":\s*"Invalid"[\s\S]*?"type":\s*"Payment"/i.test(msg)) {
109
282
  return "Bulletin quota exhausted (signed extension rejected the tx \u2014 signer is out of allowed txs or bytes; grant quota on-chain)";
@@ -166,10 +339,10 @@ var CID_CONFIG = { version: 1, codec: 85, hashCode: 18, hashLength: 32 };
166
339
  function deriveRootSigner(mnemonic, path3 = "") {
167
340
  const entropy = mnemonicToEntropy(mnemonic);
168
341
  const miniSecret = entropyToMiniSecret(entropy);
169
- const derive = sr25519CreateDerive(miniSecret);
342
+ const derive = sr25519CreateDerive2(miniSecret);
170
343
  const keyPair = derive(path3);
171
- const signer = getPolkadotSigner(keyPair.publicKey, "Sr25519", keyPair.sign);
172
- return { signer, ss58: ss58Address(keyPair.publicKey) };
344
+ const signer = getPolkadotSigner2(keyPair.publicKey, "Sr25519", keyPair.sign);
345
+ return { signer, ss58: ss58Address2(keyPair.publicKey) };
173
346
  }
174
347
  function createCID(data, codec = CID_CONFIG.codec, hashCode = CID_CONFIG.hashCode) {
175
348
  let hash;
@@ -223,7 +396,7 @@ function toHashingEnum(mhCode) {
223
396
  async function getProvider() {
224
397
  const primary = BULLETIN_ENDPOINTS[0];
225
398
  console.log(` Connecting to Bulletin: ${primary}`);
226
- const client = createPolkadotClient(getWsProvider(
399
+ const client = createPolkadotClient2(getWsProvider2(
227
400
  BULLETIN_ENDPOINTS,
228
401
  { heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS, onStatusChanged: makeBulletinStatusHandler(primary) }
229
402
  ));
@@ -251,7 +424,7 @@ async function getProvider() {
251
424
  async function getDirectProvider(mnemonic, derivationPath = "") {
252
425
  const primary = BULLETIN_ENDPOINTS[0];
253
426
  console.log(` Connecting to Bulletin: ${primary}`);
254
- const client = createPolkadotClient(getWsProvider(
427
+ const client = createPolkadotClient2(getWsProvider2(
255
428
  BULLETIN_ENDPOINTS,
256
429
  { heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS, onStatusChanged: makeBulletinStatusHandler(primary) }
257
430
  ));
@@ -259,7 +432,7 @@ async function getDirectProvider(mnemonic, derivationPath = "") {
259
432
  const { signer, ss58 } = deriveRootSigner(mnemonic, derivationPath);
260
433
  console.log(` Using direct signer: ${ss58}${derivationPath ? ` (path: ${derivationPath})` : ""}`);
261
434
  let [auth, currentBlock] = await Promise.all([
262
- unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum("Account", ss58)),
435
+ unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum2("Account", ss58)),
263
436
  client.getFinalizedBlock()
264
437
  ]);
265
438
  let now = currentBlock.number;
@@ -267,7 +440,7 @@ async function getDirectProvider(mnemonic, derivationPath = "") {
267
440
  try {
268
441
  await ensureAuthorized(unsafeApi, ss58, "direct signer");
269
442
  [auth, currentBlock] = await Promise.all([
270
- unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum("Account", ss58)),
443
+ unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum2("Account", ss58)),
271
444
  client.getFinalizedBlock()
272
445
  ]);
273
446
  now = currentBlock.number;
@@ -284,14 +457,14 @@ async function getDirectProvider(mnemonic, derivationPath = "") {
284
457
  async function getSignerProvider(signer, ss58) {
285
458
  const primary = BULLETIN_ENDPOINTS[0];
286
459
  console.log(` Connecting to Bulletin: ${primary}`);
287
- const client = createPolkadotClient(getWsProvider(
460
+ const client = createPolkadotClient2(getWsProvider2(
288
461
  BULLETIN_ENDPOINTS,
289
462
  { heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS, onStatusChanged: makeBulletinStatusHandler(primary) }
290
463
  ));
291
464
  const unsafeApi = client.getUnsafeApi();
292
465
  console.log(` Using external signer: ${ss58}`);
293
466
  let [auth, currentBlock] = await Promise.all([
294
- unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum("Account", ss58)),
467
+ unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum2("Account", ss58)),
295
468
  client.getFinalizedBlock()
296
469
  ]);
297
470
  let now = currentBlock.number;
@@ -299,7 +472,7 @@ async function getSignerProvider(signer, ss58) {
299
472
  try {
300
473
  await ensureAuthorized(unsafeApi, ss58, "external signer");
301
474
  [auth, currentBlock] = await Promise.all([
302
- unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum("Account", ss58)),
475
+ unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum2("Account", ss58)),
303
476
  client.getFinalizedBlock()
304
477
  ]);
305
478
  now = currentBlock.number;
@@ -314,19 +487,37 @@ async function getSignerProvider(signer, ss58) {
314
487
  return { client, unsafeApi, signer, ss58 };
315
488
  }
316
489
  function __selectStorageProviderModeForTest(options) {
490
+ if (options.storageSigner && options.storageSignerAddress) return "storageSigner";
317
491
  if (options.signer && options.signerAddress) return "signer";
318
492
  if (options.mnemonic) return "direct";
319
493
  return "pool";
320
494
  }
495
+ function chooseSignerInput(opts) {
496
+ if (opts.mnemonic) return "mnemonic";
497
+ if (opts.hasInjectedSigner) return "injected";
498
+ if (opts.suri) return "resolve";
499
+ if (opts.hasSession) return "resolve";
500
+ return "pool";
501
+ }
321
502
  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();
503
+ if (options.storageSigner && options.storageSignerAddress) {
504
+ let useSlot = true;
505
+ return async () => {
506
+ if (!useSlot) return getProvider();
507
+ try {
508
+ return await getSlotSignerProvider(options.storageSigner, options.storageSignerAddress);
509
+ } catch {
510
+ useSlot = false;
511
+ setDeployAttribute("deploy.signer.mode", "pool-fallback");
512
+ return getProvider();
513
+ }
514
+ };
329
515
  }
516
+ if (options.signer && options.signerAddress)
517
+ return () => getSignerProvider(options.signer, options.signerAddress);
518
+ if (options.mnemonic)
519
+ return () => getDirectProvider(options.mnemonic, options.derivationPath);
520
+ return () => getProvider();
330
521
  }
331
522
  function watchTransaction(tx, signer, txOpts, onSuccess, { label = "transaction", rpc, senderSS58, expectedNonce, timeoutMs, fetchNonce: fetchNonceOverride } = {}) {
332
523
  const timeout = timeoutMs ?? TX_TIMEOUT_MS;
@@ -491,7 +682,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
491
682
  }
492
683
  }
493
684
  const readUploadAuthorization = () => Promise.all([
494
- unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum("Account", ss58)),
685
+ unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum2("Account", ss58)),
495
686
  unsafeApi.query.System.Number.getValue()
496
687
  ]);
497
688
  let uploadAuth;
@@ -1120,7 +1311,7 @@ async function storeDirectoryV2(directoryPath, opts = {}) {
1120
1311
  useKubo = hasIPFS();
1121
1312
  }
1122
1313
  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 });
1314
+ const r = await merkleizeWithStableOrder(directoryPath, prevManifest?.stableBlockOrder, { useKubo, phase: "Phase A" });
1124
1315
  sampleMemory("merkleize_end");
1125
1316
  return r;
1126
1317
  });
@@ -1239,7 +1430,7 @@ async function storeDirectoryV2(directoryPath, opts = {}) {
1239
1430
  carChunksA.length = 0;
1240
1431
  phaseA.carBytes = new Uint8Array(0);
1241
1432
  const phaseB = await withSpan("deploy.merkleize", "1c. merkleize (js, finalise)", { "deploy.directory": dirBasename }, async () => {
1242
- const r = await merkleizeWithStableOrder(directoryPath, phaseA.stableOrder, { useKubo });
1433
+ const r = await merkleizeWithStableOrder(directoryPath, phaseA.stableOrder, { useKubo, phase: "Phase B" });
1243
1434
  sampleMemory("merkleize_finalise_end");
1244
1435
  return r;
1245
1436
  });
@@ -1533,8 +1724,7 @@ async function publish(dotns, parsed, failOnError) {
1533
1724
  setDeployAttribute("deploy.publish.status", "failed");
1534
1725
  if (failOnError) throw e;
1535
1726
  const msg = e?.message ?? String(e);
1536
- console.error(` Warning: publish failed (non-fatal): ${msg}`);
1537
- captureWarning("publish failed", { error: msg.slice(0, 200) });
1727
+ console.log(` Publish failed: ${msg}`);
1538
1728
  }
1539
1729
  }
1540
1730
  async function unpublish(domainName, options = {}) {
@@ -1654,359 +1844,478 @@ async function deploy(content, domainName = null, options = {}) {
1654
1844
  BULLETIN_ENDPOINTS = userRpc ? [userRpc, ...envBulletin.filter((e) => e !== userRpc)] : envBulletin;
1655
1845
  _deployRpcFailedOver = false;
1656
1846
  POOL_SIZE = options.poolSize ?? parseInt(process.env.BULLETIN_POOL_SIZE ?? String(DEFAULT_POOL_SIZE), 10);
1847
+ let sessionCleanup;
1848
+ const hasSession = hasPersistedSession();
1849
+ const signerChoice = chooseSignerInput({
1850
+ mnemonic: options.mnemonic,
1851
+ suri: options.suri,
1852
+ hasInjectedSigner: !!(options.signer && options.signerAddress),
1853
+ hasSession
1854
+ });
1855
+ let resolvedUserSession = void 0;
1856
+ if (signerChoice === "resolve") {
1857
+ const { resolveSigner: resolveSignerFn } = await import("./auth/index.js");
1858
+ const { getAuthClient } = await import("./auth-config.js");
1859
+ const authClient = await getAuthClient(envId);
1860
+ try {
1861
+ const resolved = await resolveSignerFn(authClient, { suri: options.suri });
1862
+ options = { ...options, signer: resolved.signer, signerAddress: resolved.address };
1863
+ sessionCleanup = resolved.destroy.bind(resolved);
1864
+ console.log(` Using ${resolved.source} signer: ${resolved.address}`);
1865
+ if (resolved.source === "session") resolvedUserSession = resolved;
1866
+ } catch (e) {
1867
+ if (options.suri) throw e;
1868
+ 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
+ );
1872
+ } else {
1873
+ throw e;
1874
+ }
1875
+ }
1876
+ }
1877
+ if (!options.storageSigner) {
1878
+ 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
+ ]
1894
+ );
1895
+ const hexKey = extractBulletinSlotKey(outcomes);
1896
+ if (hexKey) {
1897
+ await writeBulletinSlotKey(DOT_DAPP_ID, hexKey);
1898
+ slotResult = await readBulletinSlotSigner(DOT_DAPP_ID);
1899
+ }
1900
+ }
1901
+ if (slotResult) {
1902
+ options = { ...options, storageSigner: slotResult.signer, storageSignerAddress: slotResult.ss58 };
1903
+ }
1904
+ } catch {
1905
+ }
1906
+ }
1657
1907
  initTelemetry();
1658
1908
  const randomSuffix = Math.floor(Math.random() * 100).toString().padStart(2, "0");
1659
1909
  const parsed = domainName ? parseDomainName(domainName) : null;
1660
1910
  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 {
1690
- console.log("\n" + "=".repeat(60));
1691
- console.log("Preflight");
1692
- 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
- );
1705
- }
1706
- }
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
- }
1911
+ try {
1912
+ return await withDeploySpan(name, async () => {
1913
+ const deployTag = options.tag ?? process.env.DEPLOY_TAG;
1914
+ if (deployTag) {
1915
+ setDeployAttribute("deploy.tag", deployTag);
1916
+ setDeploySentryTag("deploy.tag", deployTag);
1738
1917
  }
1739
- provider = await reconnect();
1740
- const providerWithReconnect = { ...provider, reconnect };
1741
- const isTestnet = await detectTestnet(provider.unsafeApi);
1742
- setDeployAttribute("deploy.is_testnet", isTestnet ? "true" : "false");
1918
+ setDeployAttribute("deploy.env", envId);
1919
+ setDeployAttribute("deploy.label", parsed?.label ?? name);
1920
+ setDeployAttribute("deploy.subdomain", String(parsed?.isSubdomain ?? false));
1921
+ if (envNetwork) setDeployAttribute("deploy.network", envNetwork);
1922
+ if (envSource) setDeployAttribute("deploy.environments_source", envSource);
1923
+ let cid;
1924
+ let ipfsCid;
1925
+ let mirrorPromise = Promise.resolve(null);
1743
1926
  console.log("\n" + "=".repeat(60));
1744
- console.log("Storage");
1927
+ console.log(`DEPLOYING TO TESTNET v${VERSION}`);
1745
1928
  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(`
1754
- 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;
1929
+ if (envName) console.log(` Environment: ${envName}`);
1930
+ console.log(` Domain: ${name}.dot`);
1931
+ if (deployTag) console.log(` Tag: ${deployTag}`);
1932
+ if (options.inputCar) console.log(` Input CAR: ${path.resolve(options.inputCar)}`);
1933
+ else if (typeof content === "string") console.log(` Build dir: ${path.resolve(content)}`);
1934
+ if (process.env.CI) console.log(` Runner: ${resolveRunner()} (${resolveRunnerType()})`);
1935
+ if (options.password) console.log(` Encrypted: yes`);
1936
+ let provider;
1937
+ const reconnect = selectStorageReconnect(options);
1938
+ let dotnsPreflight = null;
1939
+ let previousContenthashCid = null;
1940
+ let preflightPublishNeeded = false;
1941
+ try {
1942
+ console.log("\n" + "=".repeat(60));
1943
+ console.log("Preflight");
1944
+ console.log("=".repeat(60));
1945
+ const preflight = new DotNS();
1946
+ await preflight.connect(resolveDotnsConnectOptions(options, envAssetHub, envAutoAccountMapping, envContracts, envNativeToEthRatio, envId, envPopSelfServe, envRegisterStorageDeposit));
1947
+ if (parsed?.isSubdomain) {
1948
+ try {
1949
+ const subResult = await preflight.checkSubdomainOwnership(parsed.sublabel, parsed.parentLabel);
1950
+ assertSubdomainOwnerMatchesSigner(subResult, preflight.evmAddress, parsed.sublabel, parsed.parentLabel);
1951
+ if (!subResult.owned) {
1952
+ const { owned: parentOwned, owner: parentOwner } = await preflight.checkOwnership(parsed.parentLabel);
1953
+ if (!parentOwned) {
1954
+ throw new NonRetryableError(
1955
+ `Cannot deploy ${parsed.fullName}: parent ${parsed.parentLabel}.dot is owned by ${parentOwner ?? "no one"}, not by this signer.`
1956
+ );
1779
1957
  }
1780
- const rebuilt = await rebuildOrderedCarFromBytes(carContent, prevStableOrder);
1781
- if (Buffer.compare(Buffer.from(rebuilt.carBytes), Buffer.from(carContent)) === 0) {
1782
- carChunks = rebuilt.chunks;
1958
+ }
1959
+ previousContenthashCid = await readPreviousContenthashSafe(preflight, parsed.fullName);
1960
+ setDeployAttribute("deploy.incremental", previousContenthashCid ? "true" : "false");
1961
+ } finally {
1962
+ preflight.disconnect();
1963
+ }
1964
+ console.log(` Mode: subdomain (parent ${parsed.parentLabel}.dot owned by signer)`);
1965
+ } else {
1966
+ preflightPublishNeeded = false;
1967
+ try {
1968
+ dotnsPreflight = await preflight.preflight(name);
1969
+ previousContenthashCid = await readPreviousContenthashSafe(preflight, name);
1970
+ setDeployAttribute("deploy.incremental", previousContenthashCid ? "true" : "false");
1971
+ if (options.publish && parsed && !parsed.isSubdomain) {
1972
+ const publisher = preflight._contracts?.PUBLISHER;
1973
+ const zeroAddr = "0x0000000000000000000000000000000000000000";
1974
+ if (!publisher || publisher === zeroAddr) {
1975
+ console.log(` Publish: not supported on this environment \u2014 will be skipped`);
1783
1976
  } else {
1784
- captureWarning("input CAR ordered rechunk drift; falling back to size chunking", {
1785
- rootCid: ipfsCid
1786
- });
1787
- carChunks = chunk(carContent, CHUNK_SIZE);
1977
+ const labelhash = keccak256(toBytes(name));
1978
+ try {
1979
+ const alreadyPublished = await preflight.contractCall(
1980
+ publisher,
1981
+ PUBLISHER_ABI,
1982
+ "isPublished",
1983
+ [labelhash]
1984
+ );
1985
+ preflightPublishNeeded = !alreadyPublished;
1986
+ if (!preflightPublishNeeded) {
1987
+ console.log(` Publish: already published \u2014 will be skipped`);
1988
+ }
1989
+ } catch {
1990
+ preflightPublishNeeded = true;
1991
+ }
1788
1992
  }
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
1993
  }
1994
+ } finally {
1995
+ preflight.disconnect();
1996
+ }
1997
+ if (dotnsPreflight) {
1998
+ setDeployAttribute("deploy.dotns.preflight.action", dotnsPreflight.plannedAction);
1999
+ setDeployAttribute("deploy.dotns.preflight.classification", popStatusName(dotnsPreflight.classification.status));
1796
2000
  }
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)));
2001
+ const alreadyOwned = dotnsPreflight.plannedAction === "already-owned-by-us";
2002
+ const reqSuffix = alreadyOwned ? " (already owned, requirement not enforced)" : "";
2003
+ console.log(` DotNS: ${name}.dot requires ${popStatusName(dotnsPreflight.classification.status)}${reqSuffix}`);
2004
+ if (dotnsPreflight.canProceed) {
2005
+ const fromName = popStatusName(dotnsPreflight.userStatus);
2006
+ console.log(` Your PoP: ${fromName}`);
2007
+ console.log(` Domain: ${dotnsPreflight.plannedAction === "already-owned-by-us" ? "owned by you" : "available"}`);
1807
2008
  }
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."
2009
+ if (!dotnsPreflight.canProceed) {
2010
+ throw new NonRetryableError(
2011
+ dotnsPreflight.reason ?? "DotNS preflight rejected the deploy; please check the label and signer."
1814
2012
  );
1815
2013
  }
1816
- cid = process.env.IPFS_CID;
1817
- ipfsCid = cid;
1818
- console.log(`
1819
- 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);
2014
+ }
2015
+ if (options.signer && options.signerAddress) {
2016
+ const steps = computePhoneSigningSteps(dotnsPreflight, preflightPublishNeeded);
2017
+ if (steps.length === 1) {
2018
+ console.log(`
2019
+ \u{1F4F1} Have your phone ready \u2014 1 signature needed (${steps[0].toLowerCase()})`);
2020
+ } else if (steps.length > 1) {
2021
+ const display = steps.flatMap(
2022
+ (s, i) => s === "Register" && steps[i - 1] === "Commitment" ? ["(wait)", s] : [s]
2023
+ );
2024
+ console.log(`
2025
+ \u{1F4F1} Have your phone ready \u2014 ${steps.length} signatures needed`);
2026
+ console.log(` ${display.map((s) => s.toLowerCase()).join(" \xB7 ")}`);
1831
2027
  }
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
+ }
2029
+ provider = await reconnect();
2030
+ const providerWithReconnect = { ...provider, reconnect };
2031
+ const isTestnet = await detectTestnet(provider.unsafeApi);
2032
+ setDeployAttribute("deploy.is_testnet", isTestnet ? "true" : "false");
2033
+ console.log("\n" + "=".repeat(60));
2034
+ console.log("Storage");
2035
+ console.log("=".repeat(60));
2036
+ setDeployAttribute("deploy.content_type", "unknown");
2037
+ setDeployAttribute("deploy.encrypted", "false");
2038
+ await withSpan("deploy.storage", "1. storage", {}, async () => {
2039
+ if (options.inputCar) {
2040
+ setDeployAttribute("deploy.content_type", "inputCar");
2041
+ const carPath = path.resolve(options.inputCar);
2042
+ if (!fs.existsSync(carPath)) throw new Error(`CAR file not found: ${carPath}`);
1840
2043
  console.log(`
1841
- 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)));
2044
+ Mode: Pre-built CAR`);
2045
+ console.log(` Path: ${carPath}`);
2046
+ let carContent = fs.readFileSync(carPath);
2047
+ console.log(` Size: ${(carContent.length / 1024 / 1024).toFixed(2)} MB`);
2048
+ const reader = await CarReader.fromBytes(carContent);
2049
+ const roots = await reader.getRoots();
2050
+ if (roots.length === 0) throw new Error("CAR file has no roots");
2051
+ ipfsCid = roots[0].toString();
2052
+ console.log(` Root CID: ${ipfsCid}`);
2053
+ if (options.password) {
2054
+ setDeployAttribute("deploy.encrypted", "true");
2055
+ console.log(` Encrypting CAR file...`);
2056
+ carContent = await encryptContent(carContent, options.password);
2057
+ console.log(` Encrypted: ${(carContent.length / 1024 / 1024).toFixed(2)} MB`);
2058
+ }
2059
+ let carChunks;
2060
+ if (options.password) {
2061
+ carChunks = chunk(carContent, CHUNK_SIZE);
2062
+ } else {
2063
+ try {
2064
+ let prevStableOrder = [];
2065
+ const manifestBytes = await extractManifestFromCar(carContent);
2066
+ if (manifestBytes) {
2067
+ const parsed2 = parseManifest(Buffer.from(manifestBytes).toString("utf8"));
2068
+ if (parsed2.ok) prevStableOrder = parsed2.manifest.stableBlockOrder;
2069
+ }
2070
+ const rebuilt = await rebuildOrderedCarFromBytes(carContent, prevStableOrder);
2071
+ if (Buffer.compare(Buffer.from(rebuilt.carBytes), Buffer.from(carContent)) === 0) {
2072
+ carChunks = rebuilt.chunks;
2073
+ } else {
2074
+ captureWarning("input CAR ordered rechunk drift; falling back to size chunking", {
2075
+ rootCid: ipfsCid
2076
+ });
2077
+ carChunks = chunk(carContent, CHUNK_SIZE);
1867
2078
  }
2079
+ } catch (err) {
2080
+ captureWarning("input CAR ordered rechunk failed; falling back to size chunking", {
2081
+ rootCid: ipfsCid,
2082
+ reason: err?.message ?? String(err)
2083
+ });
2084
+ carChunks = chunk(carContent, CHUNK_SIZE);
1868
2085
  }
1869
- });
1870
- cid = sCid;
1871
- ipfsCid = iCid;
1872
- } else {
1873
- setDeployAttribute("deploy.content_type", "file");
2086
+ }
2087
+ const predictedStorageCid = computeStorageCid(carChunks);
2088
+ if (options.ghPagesMirror) {
2089
+ mirrorPromise = mirrorToGitHubPages({
2090
+ domain: name,
2091
+ carBytes: carContent,
2092
+ cid: predictedStorageCid,
2093
+ toolVersion: VERSION,
2094
+ bulletinRpc: BULLETIN_ENDPOINTS[0],
2095
+ encrypted: Boolean(options.password)
2096
+ }).catch((err) => err instanceof Error ? err : new Error(String(err)));
2097
+ }
2098
+ cid = (await storeChunkedContent(carChunks, providerWithReconnect)).storageCid;
2099
+ } else if (process.env.IPFS_CID) {
2100
+ setDeployAttribute("deploy.content_type", "ipfsCid");
2101
+ if (options.password) {
2102
+ throw new Error(
2103
+ "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."
2104
+ );
2105
+ }
2106
+ cid = process.env.IPFS_CID;
2107
+ ipfsCid = cid;
2108
+ console.log(`
2109
+ Using CID: ${cid}`);
2110
+ } else if (Array.isArray(content)) {
2111
+ setDeployAttribute("deploy.content_type", "multiChunk");
1874
2112
  console.log(`
2113
+ Mode: Multi-chunk (${content.length} chunks)`);
2114
+ let contentChunks = content;
2115
+ if (options.password) {
2116
+ setDeployAttribute("deploy.encrypted", "true");
2117
+ console.log(` Encrypting...`);
2118
+ const encrypted = await encryptContent(Buffer.concat(content), options.password);
2119
+ console.log(` Encrypted: ${(encrypted.length / 1024).toFixed(1)} KB`);
2120
+ contentChunks = chunk(encrypted);
2121
+ }
2122
+ cid = (await storeChunkedContent(contentChunks, providerWithReconnect)).storageCid;
2123
+ } else if (typeof content === "string") {
2124
+ setDeployAttribute("deploy.content_type", "path");
2125
+ const contentPath = path.resolve(content);
2126
+ if (!fs.existsSync(contentPath)) throw new Error(`Path not found: ${contentPath}`);
2127
+ const stats = fs.statSync(contentPath);
2128
+ if (stats.isDirectory()) {
2129
+ setDeployAttribute("deploy.content_type", "directory");
2130
+ console.log(`
2131
+ Mode: Directory`);
2132
+ console.log(` Path: ${contentPath}`);
2133
+ if (previousContenthashCid) console.log(` Incremental: previous contenthash ${previousContenthashCid}`);
2134
+ else console.log(` Incremental: first deploy (no previous contenthash)`);
2135
+ if (options.password) setDeployAttribute("deploy.encrypted", "true");
2136
+ const storeFn = options.password ? storeDirectory : storeDirectoryV2;
2137
+ const { storageCid: sCid, ipfsCid: iCid } = await storeFn(contentPath, {
2138
+ provider: providerWithReconnect,
2139
+ password: options.password,
2140
+ jsMerkle: options.jsMerkle,
2141
+ previousContenthash: previousContenthashCid,
2142
+ allowLargeDeploy: options.allowLargeDeploy,
2143
+ reproducibleSource: options.reproducibleSource,
2144
+ domain: name,
2145
+ gateway: envIpfs,
2146
+ dumpCar: options.dumpCar,
2147
+ onCarReady: (carBytes, predictedCid) => {
2148
+ if (options.ghPagesMirror) {
2149
+ mirrorPromise = mirrorToGitHubPages({
2150
+ domain: name,
2151
+ carBytes,
2152
+ cid: predictedCid,
2153
+ toolVersion: VERSION,
2154
+ bulletinRpc: BULLETIN_ENDPOINTS[0],
2155
+ encrypted: Boolean(options.password)
2156
+ }).catch((err) => err instanceof Error ? err : new Error(String(err)));
2157
+ }
2158
+ }
2159
+ });
2160
+ cid = sCid;
2161
+ ipfsCid = iCid;
2162
+ } else {
2163
+ setDeployAttribute("deploy.content_type", "file");
2164
+ console.log(`
1875
2165
  Mode: File`);
1876
- console.log(` Path: ${contentPath}`);
1877
- let fileContent = fs.readFileSync(contentPath);
2166
+ console.log(` Path: ${contentPath}`);
2167
+ let fileContent = fs.readFileSync(contentPath);
2168
+ if (options.password) {
2169
+ setDeployAttribute("deploy.encrypted", "true");
2170
+ console.log(` Encrypting...`);
2171
+ fileContent = await encryptContent(fileContent, options.password);
2172
+ console.log(` Encrypted: ${(fileContent.length / 1024).toFixed(1)} KB`);
2173
+ }
2174
+ if (fileContent.length > MAX_FILE_SIZE) {
2175
+ console.log(` Exceeds 8MB, chunking...`);
2176
+ cid = (await storeChunkedContent(chunk(fileContent), providerWithReconnect)).storageCid;
2177
+ } else {
2178
+ cid = await storeFile(fileContent, providerWithReconnect);
2179
+ }
2180
+ }
2181
+ } else if (content instanceof Uint8Array) {
2182
+ setDeployAttribute("deploy.content_type", "multiChunk");
2183
+ console.log(`
2184
+ Mode: Bytes`);
2185
+ let bytesContent = content;
1878
2186
  if (options.password) {
1879
2187
  setDeployAttribute("deploy.encrypted", "true");
1880
2188
  console.log(` Encrypting...`);
1881
- fileContent = await encryptContent(fileContent, options.password);
1882
- console.log(` Encrypted: ${(fileContent.length / 1024).toFixed(1)} KB`);
2189
+ bytesContent = await encryptContent(bytesContent, options.password);
2190
+ console.log(` Encrypted: ${(bytesContent.length / 1024).toFixed(1)} KB`);
1883
2191
  }
1884
- if (fileContent.length > MAX_FILE_SIZE) {
2192
+ if (bytesContent.length > MAX_FILE_SIZE) {
1885
2193
  console.log(` Exceeds 8MB, chunking...`);
1886
- cid = (await storeChunkedContent(chunk(fileContent), providerWithReconnect)).storageCid;
2194
+ cid = (await storeChunkedContent(chunk(bytesContent), providerWithReconnect)).storageCid;
1887
2195
  } else {
1888
- cid = await storeFile(fileContent, providerWithReconnect);
2196
+ cid = await storeFile(bytesContent, providerWithReconnect);
1889
2197
  }
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
- } else {
1906
- cid = await storeFile(bytesContent, providerWithReconnect);
1907
- }
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
2198
  } 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);
2199
+ throw new Error("Invalid content: must be path, Uint8Array, or Array<Uint8Array>");
1935
2200
  }
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);
2201
+ });
2202
+ setDeployAttribute("deploy.cid", cid);
2203
+ if (options.attributes) {
2204
+ for (const [key, value] of Object.entries(options.attributes)) {
2205
+ setDeployAttribute(key, value);
1943
2206
  }
1944
2207
  }
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
2208
  console.log("\n" + "=".repeat(60));
1965
- console.log("Final checks");
2209
+ console.log("DotNS");
1966
2210
  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;
2211
+ await withSpan("deploy.dotns", "2. dotns", { "deploy.domain": name, "deploy.subdomain": String(parsed?.isSubdomain ?? false) }, async () => {
2212
+ const dotns = new DotNS();
2213
+ await dotns.connect({
2214
+ ...resolveDotnsConnectOptions(options, envAssetHub, envAutoAccountMapping, envContracts, envNativeToEthRatio, envId, envPopSelfServe, envRegisterStorageDeposit),
2215
+ ...options.signer && options.signerAddress ? { onPhoneSigningRequired: (label) => console.log(`
2216
+ Check your phone \u2192 ${label}`) } : {}
2217
+ });
2218
+ if (parsed?.isSubdomain) {
2219
+ const { owned, owner } = await dotns.checkSubdomainOwnership(parsed.sublabel, parsed.parentLabel);
2220
+ if (owned) {
2221
+ console.log(` Status: Already owned`);
2222
+ } else if (owner) {
2223
+ throw new Error(`Subdomain ${parsed.fullName} is owned by ${owner}, not ${dotns.evmAddress}`);
2224
+ } else {
2225
+ const parentOwnership = await dotns.checkOwnership(parsed.parentLabel);
2226
+ if (!parentOwnership.owned) throw new Error(`You must own ${parsed.parentLabel}.dot to register subdomains under it`);
2227
+ console.log(` Status: Registering subdomain...`);
2228
+ await dotns.registerSubdomain(parsed.sublabel, parsed.parentLabel);
2229
+ }
2230
+ } else {
2231
+ const { owned } = await dotns.checkOwnership(name);
2232
+ if (owned) {
2233
+ console.log(` Status: Already owned`);
2234
+ } else {
2235
+ console.log(` Status: Registering...`);
2236
+ await dotns.register(name);
2237
+ }
1976
2238
  }
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;
2239
+ const contenthashHex = `0x${encodeContenthash(cid)}`;
2240
+ await dotns.setContenthash(name, contenthashHex);
2241
+ if (options.publish && parsed) {
2242
+ if (preflightPublishNeeded !== false) {
2243
+ await publish(dotns, parsed, options.failOnPublishError);
2244
+ }
1981
2245
  }
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");
2246
+ dotns.disconnect();
2247
+ });
2248
+ await withSpan("deploy.p2p-check", "3. p2p-check", { "deploy.domain": name }, async () => {
2249
+ const probe = await probeP2pRetrieval(provider.client, cid);
2250
+ setDeployAttribute("deploy.p2p.retrievable", probe.retrievable ? "true" : "false");
2251
+ setDeployAttribute("deploy.p2p.check_ms", String(probe.durationMs));
2252
+ setDeployAttribute("deploy.p2p.error_variant", probe.errorVariant);
2253
+ if (probe.retrievable) {
2254
+ console.log(` P2P retrieval: \u2713 (${probe.durationMs}ms)`);
1990
2255
  } 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");
2256
+ console.log(` P2P retrieval: \u26A0 not yet retrievable (${probe.errorVariant}, ${probe.durationMs}ms)`);
1994
2257
  }
1995
2258
  });
2259
+ if (options.ghPagesMirror) {
2260
+ console.log("\n" + "=".repeat(60));
2261
+ console.log("Final checks");
2262
+ console.log("=".repeat(60));
2263
+ await withSpan("deploy.gh-pages-mirror", "4. gh-pages-mirror", { "deploy.domain": name }, async () => {
2264
+ const mirror = await mirrorPromise;
2265
+ if (mirror === null) {
2266
+ console.log(" GitHub Pages mirror: skipped (only directory deploys produce a CAR suitable for mirroring).");
2267
+ return;
2268
+ }
2269
+ if (mirror instanceof MirrorSkipped) {
2270
+ console.log(` GitHub Pages mirror: skipped \u2014 ${mirror.message}`);
2271
+ return;
2272
+ }
2273
+ if (mirror instanceof Error) {
2274
+ console.log(` GitHub Pages mirror: failed (non-fatal) \u2014 ${mirror.message}`);
2275
+ captureWarning("gh-pages mirror failed", { error: mirror.message.slice(0, 200) });
2276
+ return;
2277
+ }
2278
+ console.log(` Mirror: ${mirror.url}`);
2279
+ console.log(` Manifest: https://${mirror.owner}.github.io/${mirror.repo}/${mirror.manifestPath}`);
2280
+ setDeployAttribute("deploy.gh_pages_url", mirror.url);
2281
+ process.stdout.write(" Verifying Pages serves this deploy's CAR... ");
2282
+ const freshness = await pollMirrorFreshness(mirror.url, cid, { timeoutMs: 3 * 60 * 1e3, intervalMs: 1e4 });
2283
+ if (freshness.verified) {
2284
+ console.log(`ok (${freshness.attempts} attempt${freshness.attempts === 1 ? "" : "s"}, ${(freshness.durationMs / 1e3).toFixed(0)}s).`);
2285
+ setDeployAttribute("deploy.gh_pages_freshness_verified", "true");
2286
+ } else {
2287
+ console.log(`timed out.`);
2288
+ console.log(` GitHub Pages last served cid=${freshness.lastCid ?? "n/a"} (expected ${cid}); it should catch up shortly. Non-fatal.`);
2289
+ setDeployAttribute("deploy.gh_pages_freshness_verified", "false");
2290
+ }
2291
+ });
2292
+ }
2293
+ console.log("\n" + "=".repeat(60));
2294
+ console.log("DEPLOYMENT COMPLETE!");
2295
+ console.log("=".repeat(60));
2296
+ console.log("\n\u{1F53A} Polkadot Triangle");
2297
+ console.log(` - Polkadot Desktop: ${name}.dot`);
2298
+ console.log(` - Polkadot Browser: ${browserUrlFor(name, envId)}`);
2299
+ console.log("\n" + "=".repeat(60) + "\n");
2300
+ return { domainName: name, fullDomain: `${name}.dot`, cid, ipfsCid };
2301
+ } finally {
2302
+ if (_deployRpcFailedOver) setDeployAttribute("deploy.rpc.failed_over", "true");
2303
+ provider?.client.destroy();
1996
2304
  }
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
- });
2305
+ });
2306
+ } finally {
2307
+ sessionCleanup?.();
2308
+ }
2309
+ }
2310
+ function computePhoneSigningSteps(dotnsPreflight, publishNeeded) {
2311
+ if (!dotnsPreflight || dotnsPreflight.plannedAction === "abort") return [];
2312
+ const steps = [];
2313
+ if (dotnsPreflight.plannedAction === "register") {
2314
+ steps.push("Commitment", "Register");
2315
+ }
2316
+ steps.push("Link content");
2317
+ if (publishNeeded) steps.push("Publish to registry");
2318
+ return steps;
2010
2319
  }
2011
2320
 
2012
2321
  // src/merkle.ts
@@ -2183,12 +2492,13 @@ async function merkleizeKuboBackend(directoryPath) {
2183
2492
  const { fileBlocks, fileCids, rootBlockCids, subdirCids } = walkFileBlocks(cidStr, blocks);
2184
2493
  return { rootCid: cidStr, blocks, fileBlocks, fileCids, rootBlockCids, subdirCids };
2185
2494
  }
2186
- async function merkleizeBackend(directoryPath, useKubo) {
2495
+ async function merkleizeBackend(directoryPath, useKubo, phase) {
2496
+ const tag = phase ? ` \u2014 ${phase}` : "";
2187
2497
  if (useKubo) {
2188
- console.log(` Merkleizing (Kubo): ${directoryPath}`);
2498
+ console.log(` Merkleizing (Kubo${tag}): ${directoryPath}`);
2189
2499
  return merkleizeKuboBackend(directoryPath);
2190
2500
  }
2191
- console.log(` Merkleizing (JS): ${directoryPath}`);
2501
+ console.log(` Merkleizing (JS${tag}): ${directoryPath}`);
2192
2502
  return merkleizeJSBackend(directoryPath);
2193
2503
  }
2194
2504
  function encodeCarFrame(cid, payload) {
@@ -2237,6 +2547,7 @@ function frameBlockCid(frame) {
2237
2547
  async function buildOrderedCar(options) {
2238
2548
  const { output, prevStableOrder = [] } = options;
2239
2549
  const clsFn = options.classifyFn ?? ((p) => classifyFile(p));
2550
+ const tag = options.phase ? ` \u2014 ${options.phase}` : "";
2240
2551
  const MANIFEST_PATH_LITERAL = ".bulletin-deploy/manifest.json";
2241
2552
  const stableFiles = [];
2242
2553
  const volatileFiles = [];
@@ -2321,7 +2632,7 @@ async function buildOrderedCar(options) {
2321
2632
  const s1Bytes = section1Chunks.reduce((s, b) => s + b.length, 0);
2322
2633
  const s2Bytes = section2Chunks.reduce((s, b) => s + b.length, 0);
2323
2634
  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)`
2635
+ ` 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
2636
  );
2326
2637
  return {
2327
2638
  carBytes,
@@ -2358,8 +2669,9 @@ async function rebuildOrderedCarFromBytes(carBytes, prevStableOrder = []) {
2358
2669
  }
2359
2670
  async function merkleizeWithStableOrder(directoryPath, prevStableOrder, options) {
2360
2671
  const useKubo = options?.useKubo ?? false;
2361
- const output = await merkleizeBackend(directoryPath, useKubo);
2362
- return buildOrderedCar({ output, classifyFn: options?.classifyFn, prevStableOrder });
2672
+ const phase = options?.phase;
2673
+ const output = await merkleizeBackend(directoryPath, useKubo, phase);
2674
+ return buildOrderedCar({ output, classifyFn: options?.classifyFn, prevStableOrder, phase });
2363
2675
  }
2364
2676
  async function merkleizeJS(directoryPath) {
2365
2677
  console.log(` Merkleizing (JS): ${directoryPath}`);
@@ -2403,11 +2715,18 @@ export {
2403
2715
  rebuildOrderedCarFromBytes,
2404
2716
  merkleizeWithStableOrder,
2405
2717
  merkleizeJS,
2718
+ readBulletinSlotSigner,
2719
+ writeBulletinSlotKey,
2720
+ extractBulletinSlotKey,
2721
+ getSlotSignerProvider,
2406
2722
  friendlyChainError,
2407
2723
  DEFAULT_BULLETIN_RPC,
2408
2724
  DEFAULT_POOL_SIZE,
2725
+ BULLETIN_ENDPOINTS,
2409
2726
  setWsHaltCallback,
2727
+ makeBulletinStatusHandler,
2410
2728
  CHUNK_MORTALITY_PERIOD,
2729
+ WS_HEARTBEAT_TIMEOUT_MS,
2411
2730
  retryBudgetExhausted,
2412
2731
  isConnectionError,
2413
2732
  deriveRootSigner,
@@ -2421,6 +2740,7 @@ export {
2421
2740
  ENCRYPT_PBKDF2_ITERATIONS,
2422
2741
  encryptContent,
2423
2742
  __selectStorageProviderModeForTest,
2743
+ chooseSignerInput,
2424
2744
  storeFile,
2425
2745
  __assignDenseNoncesForTest,
2426
2746
  storeChunkedContent,
@@ -2442,5 +2762,6 @@ export {
2442
2762
  browserUrlFor,
2443
2763
  interpretBitswapResult,
2444
2764
  probeP2pRetrieval,
2445
- deploy
2765
+ deploy,
2766
+ computePhoneSigningSteps
2446
2767
  };