@parity/product-deploy 0.8.1 → 0.8.2-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +2 -230
  2. package/bin/bulletin-deploy +15 -1
  3. package/dist/bug-report.js +4 -4
  4. package/dist/{chunk-KB3EII7F.js → chunk-4Y4ZBN45.js} +1 -1
  5. package/dist/{chunk-INVA3XGG.js → chunk-5EJ247OO.js} +88 -77
  6. package/dist/{chunk-IRT4A7OO.js → chunk-7GYCJPFI.js} +128 -22
  7. package/dist/{chunk-MXMVZU2Q.js → chunk-CSDXTU3G.js} +2 -2
  8. package/dist/{chunk-L2SKSKB6.js → chunk-J3NIXHZZ.js} +108 -0
  9. package/dist/{chunk-RW3GWDGI.js → chunk-JQ5X3VMP.js} +15 -102
  10. package/dist/{chunk-V5N5EYNV.js → chunk-N27JUWU2.js} +6 -3
  11. package/dist/{chunk-R2CJ5I5R.js → chunk-PIGHAAM2.js} +3 -3
  12. package/dist/{chunk-O2BFXY3Y.js → chunk-R2ORPNZC.js} +1 -1
  13. package/dist/{chunk-WPJADKC7.js → chunk-RRYHCOOJ.js} +75 -33
  14. package/dist/{chunk-CNPB4VAM.js → chunk-XFX4VODU.js} +65 -0
  15. package/dist/chunk-probe.js +3 -3
  16. package/dist/deploy.d.ts +38 -1
  17. package/dist/deploy.js +16 -10
  18. package/dist/dotns.d.ts +33 -2
  19. package/dist/dotns.js +9 -5
  20. package/dist/environments.d.ts +15 -2
  21. package/dist/environments.js +3 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.js +13 -11
  24. package/dist/manifest/publish.js +11 -11
  25. package/dist/manifest-fetch.d.ts +22 -1
  26. package/dist/manifest-fetch.js +7 -1
  27. package/dist/manifest-roundtrip.js +1 -1
  28. package/dist/memory-report.js +2 -2
  29. package/dist/merkle.js +10 -10
  30. package/dist/personhood/bootstrap.js +5 -5
  31. package/dist/personhood/people-client.js +5 -5
  32. package/dist/pool.d.ts +2 -12
  33. package/dist/pool.js +3 -11
  34. package/dist/run-state.js +1 -1
  35. package/dist/telemetry.js +2 -2
  36. package/dist/version-check.js +3 -3
  37. package/docs/bootstrap.md +1 -1
  38. package/package.json +6 -3
@@ -11,7 +11,7 @@ import {
11
11
  extractManifestFromCar,
12
12
  fetchPreviousManifest,
13
13
  writePersistentLocalManifest
14
- } from "./chunk-L2SKSKB6.js";
14
+ } from "./chunk-J3NIXHZZ.js";
15
15
  import {
16
16
  MANIFEST_PATH,
17
17
  MANIFEST_VERSION,
@@ -20,10 +20,10 @@ import {
20
20
  } from "./chunk-S7EM5VMW.js";
21
21
  import {
22
22
  setDeployContext
23
- } from "./chunk-MXMVZU2Q.js";
23
+ } from "./chunk-CSDXTU3G.js";
24
24
  import {
25
25
  probeChunks
26
- } from "./chunk-KB3EII7F.js";
26
+ } from "./chunk-4Y4ZBN45.js";
27
27
  import {
28
28
  packSection
29
29
  } from "./chunk-C2TS5MER.js";
@@ -35,15 +35,15 @@ import {
35
35
  parseDomainName,
36
36
  popStatusName,
37
37
  verifyNonceAdvanced
38
- } from "./chunk-IRT4A7OO.js";
38
+ } from "./chunk-7GYCJPFI.js";
39
39
  import {
40
40
  derivePoolAccounts,
41
41
  detectTestnet,
42
42
  ensureAuthorized,
43
43
  fetchPoolAuthorizations,
44
- selectAccount,
45
- topUpBy
46
- } from "./chunk-RW3GWDGI.js";
44
+ isAuthorizationSufficient,
45
+ selectAccount
46
+ } from "./chunk-JQ5X3VMP.js";
47
47
  import {
48
48
  VERSION,
49
49
  captureWarning,
@@ -57,13 +57,13 @@ import {
57
57
  truncateAddress,
58
58
  withDeploySpan,
59
59
  withSpan
60
- } from "./chunk-INVA3XGG.js";
60
+ } from "./chunk-5EJ247OO.js";
61
61
  import {
62
62
  DEFAULT_ENV_ID,
63
63
  getPopSelfServeConfig,
64
64
  loadEnvironments,
65
65
  resolveEndpoints
66
- } from "./chunk-CNPB4VAM.js";
66
+ } from "./chunk-XFX4VODU.js";
67
67
  import {
68
68
  NonRetryableError
69
69
  } from "./chunk-ZOC4GITL.js";
@@ -449,7 +449,7 @@ var __assignDenseNoncesForTest = assignDenseNonces;
449
449
  async function storeChunkedContent(chunks, { client: existingClient, unsafeApi: existingApi, signer: existingSigner, ss58: existingSS58, reconnect, fetchNonce: fetchNonceOverride, skipCids, probeFailedCids, gateway: providerGateway, trustedCids, skipRootStore } = {}) {
450
450
  const _fetchNonce = fetchNonceOverride ?? fetchNonce;
451
451
  console.log(`
452
- Chunks: ${chunks.length}`);
452
+ Data chunks: ${chunks.length}`);
453
453
  const totalBytes = chunks.reduce((s, c) => s + c.length, 0);
454
454
  console.log(` Total: ${(totalBytes / 1024).toFixed(2)} KB`);
455
455
  let client, unsafeApi, signer, ss58;
@@ -508,17 +508,14 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
508
508
  throw e;
509
509
  }
510
510
  }
511
- const txsRemaining = uploadAuth ? BigInt(uploadAuth.extent.transactions_allowance) - BigInt(uploadAuth.extent.transactions) : 0n;
512
- const bytesRemaining = uploadAuth ? BigInt(uploadAuth.extent.bytes_allowance) - BigInt(uploadAuth.extent.bytes) : 0n;
513
- const isAuthorized = uploadAuth !== void 0 && Number(uploadAuth.expiration ?? 0) > currentBlockNum;
514
- if (!isAuthorized || txsRemaining < requiredTxs || bytesRemaining < requiredBytes) {
515
- const txsRemainingDisplay = txsRemaining < 0n ? 0n : txsRemaining;
516
- const bytesRemainingDisplay = bytesRemaining < 0n ? 0n : bytesRemaining;
511
+ const needs = { txs: requiredTxs, bytes: requiredBytes };
512
+ const sufficient = await isAuthorizationSufficient(unsafeApi, ss58, uploadAuth, currentBlockNum, needs);
513
+ if (!sufficient) {
517
514
  console.log(`
518
- Account needs re-authorization (authorized=${isAuthorized}, need ${requiredTxs} txs / ${(totalBytes / 1e6).toFixed(1)}MB, have ${txsRemainingDisplay} txs / ${(Number(bytesRemainingDisplay) / 1e6).toFixed(2)}MB)`);
515
+ Account needs re-authorization (need ${requiredTxs} txs / ${(totalBytes / 1e6).toFixed(1)}MB)`);
519
516
  console.log(` Attempting to re-authorize with Alice...`);
520
517
  try {
521
- await ensureAuthorized(unsafeApi, ss58, void 0, { txs: requiredTxs, bytes: requiredBytes });
518
+ await ensureAuthorized(unsafeApi, ss58, void 0, needs);
522
519
  console.log(` Re-authorization successful`);
523
520
  } catch (e) {
524
521
  throw new NonRetryableError(`Account ${ss58} has insufficient Bulletin authorization and auto-authorization via Alice failed (${e.message}). Authorize the account on-chain.`);
@@ -583,7 +580,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
583
580
  const MAX_CHUNK_RETRIES = 3;
584
581
  const MAX_REPROBE_RETRIES = 3;
585
582
  console.log(`
586
- Submitting ${chunks.length} chunks in batches of up to ${BATCH_SIZE_INITIAL}...`);
583
+ Submitting ${chunks.length} data chunks in batches of up to ${BATCH_SIZE_INITIAL}...`);
587
584
  const stored = new Array(chunks.length).fill(null);
588
585
  let tier2Verified = 0;
589
586
  let tier2Inconclusive = 0;
@@ -875,7 +872,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
875
872
  if (uploadReceipt) {
876
873
  setDeployAttribute("bulletin.upload.tx_hash", uploadReceipt.txHash);
877
874
  setDeployAttribute("bulletin.upload.block_hash", uploadReceipt.blockHash);
878
- setDeployAttribute("bulletin.upload.block_number", uploadReceipt.blockNumber);
875
+ setDeployAttribute("bulletin.upload.block_number", String(uploadReceipt.blockNumber));
879
876
  console.log(` Storage upload finalised @ block ${uploadReceipt.blockNumber} (tx ${uploadReceipt.txHash})`);
880
877
  }
881
878
  if (wsHaltDetected && reconnect && reconnectionsUsed < MAX_RECONNECTIONS) {
@@ -1109,7 +1106,8 @@ async function storeDirectoryV2(directoryPath, opts = {}) {
1109
1106
  sampleMemory("storage_start");
1110
1107
  const fetched = await fetchPreviousManifest(prevContenthash, {
1111
1108
  gateway,
1112
- domain: opts.domain
1109
+ domain: opts.domain,
1110
+ chainClient: opts.provider?.client
1113
1111
  });
1114
1112
  const prevManifest = fetched.source === "embedded" ? fetched.manifest : null;
1115
1113
  console.log(` Manifest fetch: ${fetched.source}${fetched.source !== "none" ? ` (${fetched.attempts} attempt${fetched.attempts === 1 ? "" : "s"})` : ""}`);
@@ -1577,6 +1575,45 @@ async function unpublish(domainName, options = {}) {
1577
1575
  }
1578
1576
  }
1579
1577
  }
1578
+ function browserUrlFor(name, envId) {
1579
+ const base = `https://${name}.dot.li`;
1580
+ return envId === "preview" ? `${base}?network=previewnet` : base;
1581
+ }
1582
+ function interpretBitswapResult(outcome) {
1583
+ if (outcome.ok) {
1584
+ return { retrievable: true, errorVariant: "none" };
1585
+ }
1586
+ const err = outcome.error;
1587
+ if (err != null && typeof err === "object" && "code" in err && err.code === -32810) {
1588
+ return { retrievable: false, errorVariant: "not_found" };
1589
+ }
1590
+ if (err instanceof Error && err.message === "p2p_probe_timeout") {
1591
+ return { retrievable: false, errorVariant: "timeout" };
1592
+ }
1593
+ return { retrievable: false, errorVariant: "error" };
1594
+ }
1595
+ async function probeP2pRetrieval(client, cid, timeoutMs = 3e3) {
1596
+ const t0 = Date.now();
1597
+ let outcome;
1598
+ try {
1599
+ const timeoutError = new Error("p2p_probe_timeout");
1600
+ const response = await Promise.race([
1601
+ client._request("bitswap_v1_get", [cid]),
1602
+ new Promise((_, reject) => {
1603
+ const t = setTimeout(() => reject(timeoutError), timeoutMs);
1604
+ if (typeof t === "object" && t !== null && typeof t.unref === "function") {
1605
+ t.unref();
1606
+ }
1607
+ })
1608
+ ]);
1609
+ outcome = { ok: true, response };
1610
+ } catch (err) {
1611
+ outcome = { ok: false, error: err };
1612
+ }
1613
+ const durationMs = Date.now() - t0;
1614
+ const { retrievable, errorVariant } = interpretBitswapResult(outcome);
1615
+ return { retrievable, errorVariant, durationMs };
1616
+ }
1580
1617
  async function deploy(content, domainName = null, options = {}) {
1581
1618
  if (options.signer && options.signerAddress && options.mnemonic) {
1582
1619
  throw new NonRetryableError("Pass either a mnemonic or an external signer, not both \u2014 they identify the signing account and only one can win.");
@@ -1708,17 +1745,8 @@ async function deploy(content, domainName = null, options = {}) {
1708
1745
  }
1709
1746
  provider = await reconnect();
1710
1747
  const providerWithReconnect = { ...provider, reconnect };
1711
- const [isTestnet, estimated] = await Promise.all([
1712
- detectTestnet(provider.unsafeApi),
1713
- estimateUploadBytes(content)
1714
- ]);
1748
+ const isTestnet = await detectTestnet(provider.unsafeApi);
1715
1749
  setDeployAttribute("deploy.is_testnet", isTestnet ? "true" : "false");
1716
- if (isTestnet && estimated != null) {
1717
- const uploadBytes = Math.ceil(estimated * 1.2);
1718
- const chunksNeeded = Math.max(1, Math.ceil(uploadBytes / CHUNK_SIZE));
1719
- const needs = { txs: BigInt(chunksNeeded + 2), bytes: BigInt(uploadBytes) };
1720
- await topUpBy(provider.unsafeApi, provider.ss58, needs, "uploader");
1721
- }
1722
1750
  console.log("\n" + "=".repeat(60));
1723
1751
  console.log("Storage");
1724
1752
  console.log("=".repeat(60));
@@ -1928,6 +1956,17 @@ async function deploy(content, domainName = null, options = {}) {
1928
1956
  }
1929
1957
  dotns.disconnect();
1930
1958
  });
1959
+ await withSpan("deploy.p2p-check", "3. p2p-check", { "deploy.domain": name }, async () => {
1960
+ const probe = await probeP2pRetrieval(provider.client, cid);
1961
+ setDeployAttribute("deploy.p2p.retrievable", probe.retrievable ? "true" : "false");
1962
+ setDeployAttribute("deploy.p2p.check_ms", String(probe.durationMs));
1963
+ setDeployAttribute("deploy.p2p.error_variant", probe.errorVariant);
1964
+ if (probe.retrievable) {
1965
+ console.log(` P2P retrieval: \u2713 (${probe.durationMs}ms)`);
1966
+ } else {
1967
+ console.log(` P2P retrieval: \u26A0 not yet retrievable (${probe.errorVariant}, ${probe.durationMs}ms)`);
1968
+ }
1969
+ });
1931
1970
  if (options.ghPagesMirror) {
1932
1971
  console.log("\n" + "=".repeat(60));
1933
1972
  console.log("Final checks");
@@ -1967,7 +2006,7 @@ async function deploy(content, domainName = null, options = {}) {
1967
2006
  console.log("=".repeat(60));
1968
2007
  console.log("\n\u{1F53A} Polkadot Triangle");
1969
2008
  console.log(` - Polkadot Desktop: ${name}.dot`);
1970
- console.log(` - Polkadot Browser: https://${name}.dot.li`);
2009
+ console.log(` - Polkadot Browser: ${browserUrlFor(name, envId)}`);
1971
2010
  console.log("\n" + "=".repeat(60) + "\n");
1972
2011
  return { domainName: name, fullDomain: `${name}.dot`, cid, ipfsCid };
1973
2012
  } finally {
@@ -2289,7 +2328,7 @@ async function buildOrderedCar(options) {
2289
2328
  const s1Bytes = section1Chunks.reduce((s, b) => s + b.length, 0);
2290
2329
  const s2Bytes = section2Chunks.reduce((s, b) => s + b.length, 0);
2291
2330
  console.log(
2292
- ` CAR (3-section): ${(carBytes.length / 1024 / 1024).toFixed(2)} MB (s0=${section0Bytes.length}B s1=${s1Bytes}B s2=${s2Bytes}B), ${allChunks.length} chunks (${section0Chunks.length}+${section1Chunks.length}+${section2Chunks.length})`
2331
+ ` 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)`
2293
2332
  );
2294
2333
  return {
2295
2334
  carBytes,
@@ -2407,5 +2446,8 @@ export {
2407
2446
  estimateUploadBytes,
2408
2447
  assertSubdomainOwnerMatchesSigner,
2409
2448
  unpublish,
2449
+ browserUrlFor,
2450
+ interpretBitswapResult,
2451
+ probeP2pRetrieval,
2410
2452
  deploy
2411
2453
  };
@@ -453,6 +453,34 @@ function isValidDoc(value) {
453
453
  const v = value;
454
454
  return Array.isArray(v.environments) && Array.isArray(v.chains);
455
455
  }
456
+ function deepMergeEnvironments(base, override) {
457
+ const mergedEnvironments = mergeById(base.environments, override.environments ?? []);
458
+ const mergedChains = mergeById(base.chains, override.chains ?? []);
459
+ return { environments: mergedEnvironments, chains: mergedChains };
460
+ }
461
+ function mergeById(base, overrides) {
462
+ const result = base.map((b) => {
463
+ const ov = overrides.find((o) => o.id === b.id);
464
+ return ov ? mergeObjects(b, ov) : b;
465
+ });
466
+ for (const ov of overrides) {
467
+ if (!base.find((b) => b.id === ov.id)) result.push(ov);
468
+ }
469
+ return result;
470
+ }
471
+ function mergeObjects(base, override) {
472
+ const result = { ...base };
473
+ for (const key of Object.keys(override)) {
474
+ const ov = override[key];
475
+ const bs = base[key];
476
+ if (ov !== null && typeof ov === "object" && !Array.isArray(ov) && bs !== null && typeof bs === "object" && !Array.isArray(bs)) {
477
+ result[key] = { ...bs, ...ov };
478
+ } else {
479
+ result[key] = ov;
480
+ }
481
+ }
482
+ return result;
483
+ }
456
484
  async function readBundled(bundledPath, capture) {
457
485
  try {
458
486
  const raw = await fs.readFile(bundledPath, "utf8");
@@ -468,6 +496,42 @@ async function loadEnvironments(opts = {}) {
468
496
  const warn = opts.warn ?? ((msg) => console.error(msg));
469
497
  const capture = opts.capture ?? (() => {
470
498
  });
499
+ const userFilePath = opts.userFilePath ?? process.env.BULLETIN_DEPLOY_ENV_FILE;
500
+ if (userFilePath) {
501
+ let raw;
502
+ try {
503
+ raw = await fs.readFile(userFilePath, "utf8");
504
+ } catch (err) {
505
+ throw new NonRetryableError(
506
+ `--environment-file: cannot read "${userFilePath}": ${err?.message ?? err}`
507
+ );
508
+ }
509
+ let userDoc;
510
+ try {
511
+ userDoc = JSON.parse(raw);
512
+ } catch (err) {
513
+ throw new NonRetryableError(
514
+ `--environment-file: "${userFilePath}" is not valid JSON: ${err?.message ?? err}`
515
+ );
516
+ }
517
+ const base = await readBundled(bundledPath, capture);
518
+ const baseDoc = base ?? (isValidDoc(environments_default) ? environments_default : HARDCODED_FALLBACK);
519
+ const partial = userDoc && typeof userDoc === "object" && !Array.isArray(userDoc) ? userDoc : {};
520
+ const merged = deepMergeEnvironments(baseDoc, partial);
521
+ warn(
522
+ `bulletin-deploy: Using user-supplied environment file "${userFilePath}" \u2014 values are NOT validated against chain; you own correctness.`
523
+ );
524
+ for (const env of merged.environments) {
525
+ if (env.contracts && Object.keys(env.contracts).length > 0) {
526
+ try {
527
+ validateContractAddresses(env.contracts, env.id);
528
+ } catch (err) {
529
+ warn(`bulletin-deploy: Warning: ${err?.message ?? err}`);
530
+ }
531
+ }
532
+ }
533
+ return { doc: merged, source: "file" };
534
+ }
471
535
  const bundled = await readBundled(bundledPath, capture);
472
536
  if (bundled) return { doc: bundled, source: "bundled" };
473
537
  if (isValidDoc(environments_default)) {
@@ -584,6 +648,7 @@ export {
584
648
  defaultBundledPath,
585
649
  isValidContractAddress,
586
650
  validateContractAddresses,
651
+ deepMergeEnvironments,
587
652
  loadEnvironments,
588
653
  resolveEndpoints,
589
654
  getPopSelfServeConfig,
@@ -5,9 +5,9 @@ import {
5
5
  _decodeStorageValue,
6
6
  _resetProbeSession,
7
7
  probeChunks
8
- } from "./chunk-KB3EII7F.js";
9
- import "./chunk-INVA3XGG.js";
10
- import "./chunk-V5N5EYNV.js";
8
+ } from "./chunk-4Y4ZBN45.js";
9
+ import "./chunk-5EJ247OO.js";
10
+ import "./chunk-N27JUWU2.js";
11
11
  export {
12
12
  ChainProbeCrossValidationError,
13
13
  ChainProbeMetadataError,
package/dist/deploy.d.ts CHANGED
@@ -303,6 +303,43 @@ declare function unpublish(domainName: string, options?: {
303
303
  status: "unpublished" | "already-unpublished";
304
304
  txHash?: string;
305
305
  }>;
306
+ /**
307
+ * Returns the dot.li browser URL for the given domain name, optionally
308
+ * suffixed with a network query parameter so the SPA opens the right chain.
309
+ * Currently only the "preview" env needs a suffix — the SPA defaults to
310
+ * paseo-next-v2 which would show "no content" for preview deployments.
311
+ * @param name - the DotNS label (e.g. "myapp")
312
+ * @param envId - the environment id from options.env ?? DEFAULT_ENV_ID
313
+ */
314
+ declare function browserUrlFor(name: string, envId: string | undefined): string;
315
+ type BitswapErrorVariant = "none" | "not_found" | "timeout" | "error";
316
+ interface BitswapProbeResult {
317
+ retrievable: boolean;
318
+ errorVariant: BitswapErrorVariant;
319
+ durationMs: number;
320
+ }
321
+ /**
322
+ * Pure classifier — maps a raw response or thrown error to {retrievable, errorVariant}.
323
+ * Exported for unit tests; does NOT touch telemetry or console.
324
+ */
325
+ declare function interpretBitswapResult(outcome: {
326
+ ok: true;
327
+ response: unknown;
328
+ } | {
329
+ ok: false;
330
+ error: unknown;
331
+ }): {
332
+ retrievable: boolean;
333
+ errorVariant: BitswapErrorVariant;
334
+ };
335
+ /**
336
+ * Calls bitswap_v1_get on the bulletin RPC client for the given base32 CIDv1 string.
337
+ * Never throws — wraps every outcome in BitswapProbeResult.
338
+ * @param client - polkadot-api client (ProviderResult.client)
339
+ * @param cid - base32 CIDv1 string (e.g. "bafyrei...")
340
+ * @param timeoutMs - safety ceiling; the RPC typically responds in ~600ms
341
+ */
342
+ declare function probeP2pRetrieval(client: any, cid: string, timeoutMs?: number): Promise<BitswapProbeResult>;
306
343
  declare function deploy(content: DeployContent, domainName?: string | null, options?: DeployOptions): Promise<DeployResult>;
307
344
 
308
- export { CHUNK_MORTALITY_PERIOD, DEFAULT_BULLETIN_RPC, DEFAULT_POOL_SIZE, type DeployContent, type DeployOptions, type DeployResult, ENCRYPT_KEY_LEN, ENCRYPT_MAGIC, ENCRYPT_NONCE_LEN, ENCRYPT_PBKDF2_ITERATIONS, ENCRYPT_SALT_LEN, ENCRYPT_TAG_LEN, type SizeDecision, type StoreDirectoryOptions, __assignDenseNoncesForTest, __selectStorageProviderModeForTest, applyManifestFetchAttributes, assertSubdomainOwnerMatchesSigner, buildFilesMap, checkDeploySize, chunk, computeStorageCid, createCID, deploy, deriveRootSigner, detectFramework, encodeContenthash, encryptContent, estimateUploadBytes, friendlyChainError, hasIPFS, isConnectionError, merkleize, resolveDotnsConnectOptions, resolveReproducibleTimestamp, retryBudgetExhausted, setWsHaltCallback, storeChunkedContent, storeDirectory, storeDirectoryV2, storeFile, unpublish };
345
+ export { type BitswapErrorVariant, type BitswapProbeResult, CHUNK_MORTALITY_PERIOD, DEFAULT_BULLETIN_RPC, DEFAULT_POOL_SIZE, type DeployContent, type DeployOptions, type DeployResult, ENCRYPT_KEY_LEN, ENCRYPT_MAGIC, ENCRYPT_NONCE_LEN, ENCRYPT_PBKDF2_ITERATIONS, ENCRYPT_SALT_LEN, ENCRYPT_TAG_LEN, type SizeDecision, type StoreDirectoryOptions, __assignDenseNoncesForTest, __selectStorageProviderModeForTest, applyManifestFetchAttributes, assertSubdomainOwnerMatchesSigner, browserUrlFor, buildFilesMap, checkDeploySize, chunk, computeStorageCid, createCID, deploy, deriveRootSigner, detectFramework, encodeContenthash, encryptContent, estimateUploadBytes, friendlyChainError, hasIPFS, interpretBitswapResult, isConnectionError, merkleize, probeP2pRetrieval, resolveDotnsConnectOptions, resolveReproducibleTimestamp, retryBudgetExhausted, setWsHaltCallback, storeChunkedContent, storeDirectory, storeDirectoryV2, storeFile, unpublish };
package/dist/deploy.js CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  __selectStorageProviderModeForTest,
13
13
  applyManifestFetchAttributes,
14
14
  assertSubdomainOwnerMatchesSigner,
15
+ browserUrlFor,
15
16
  buildFilesMap,
16
17
  checkDeploySize,
17
18
  chunk,
@@ -25,8 +26,10 @@ import {
25
26
  estimateUploadBytes,
26
27
  friendlyChainError,
27
28
  hasIPFS,
29
+ interpretBitswapResult,
28
30
  isConnectionError,
29
31
  merkleize,
32
+ probeP2pRetrieval,
30
33
  resolveDotnsConnectOptions,
31
34
  resolveReproducibleTimestamp,
32
35
  retryBudgetExhausted,
@@ -36,20 +39,20 @@ import {
36
39
  storeDirectoryV2,
37
40
  storeFile,
38
41
  unpublish
39
- } from "./chunk-WPJADKC7.js";
42
+ } from "./chunk-RRYHCOOJ.js";
40
43
  import "./chunk-IW3X2MJF.js";
41
44
  import "./chunk-KOSF5FDO.js";
42
- import "./chunk-L2SKSKB6.js";
45
+ import "./chunk-J3NIXHZZ.js";
43
46
  import "./chunk-S7EM5VMW.js";
44
- import "./chunk-MXMVZU2Q.js";
45
- import "./chunk-O2BFXY3Y.js";
46
- import "./chunk-KB3EII7F.js";
47
+ import "./chunk-CSDXTU3G.js";
48
+ import "./chunk-R2ORPNZC.js";
49
+ import "./chunk-4Y4ZBN45.js";
47
50
  import "./chunk-C2TS5MER.js";
48
- import "./chunk-IRT4A7OO.js";
49
- import "./chunk-RW3GWDGI.js";
50
- import "./chunk-INVA3XGG.js";
51
- import "./chunk-V5N5EYNV.js";
52
- import "./chunk-CNPB4VAM.js";
51
+ import "./chunk-7GYCJPFI.js";
52
+ import "./chunk-JQ5X3VMP.js";
53
+ import "./chunk-5EJ247OO.js";
54
+ import "./chunk-N27JUWU2.js";
55
+ import "./chunk-XFX4VODU.js";
53
56
  import {
54
57
  EXIT_CODE_NO_RETRY,
55
58
  NonRetryableError
@@ -71,6 +74,7 @@ export {
71
74
  __selectStorageProviderModeForTest,
72
75
  applyManifestFetchAttributes,
73
76
  assertSubdomainOwnerMatchesSigner,
77
+ browserUrlFor,
74
78
  buildFilesMap,
75
79
  checkDeploySize,
76
80
  chunk,
@@ -84,8 +88,10 @@ export {
84
88
  estimateUploadBytes,
85
89
  friendlyChainError,
86
90
  hasIPFS,
91
+ interpretBitswapResult,
87
92
  isConnectionError,
88
93
  merkleize,
94
+ probeP2pRetrieval,
89
95
  resolveDotnsConnectOptions,
90
96
  resolveReproducibleTimestamp,
91
97
  retryBudgetExhausted,
package/dist/dotns.d.ts CHANGED
@@ -94,7 +94,7 @@ interface DotnsPreflightResult {
94
94
  declare const MINIMUM_REGISTER_STORAGE_DEPOSIT = 2000000000000n;
95
95
  declare function fmtPas(plancks: bigint): string;
96
96
  type DotnsSuccessAction = Exclude<DotnsPreflightResult["plannedAction"], "abort">;
97
- declare function feeFloorFor(plannedAction: DotnsSuccessAction, storageDeposit?: bigint): bigint;
97
+ declare function feeFloorFor(plannedAction: DotnsSuccessAction, storageDeposit?: bigint, rentPriceNative?: bigint): bigint;
98
98
  declare const RPC_ENDPOINTS: string[];
99
99
  declare const CONTRACTS: {
100
100
  readonly DOTNS_REGISTRAR: "0x329aAA5b6bEa94E750b2dacBa74Bf41291E6c2BD";
@@ -117,6 +117,25 @@ declare const TX_NO_PROGRESS_MS: number;
117
117
  declare const WS_HEARTBEAT_TIMEOUT_MS: number;
118
118
  declare const DOTNS_TX_MAX_ATTEMPTS: number;
119
119
  declare function classifyTxRetryDecision(err: unknown): "retry" | "abort";
120
+ declare function dotnsRetryBackoffMs(attempt: number, rand?: () => number): number;
121
+ /** Wraps `sink` so that "failed" status events are buffered and only forwarded
122
+ * when `flush()` is called (i.e. on final abort). All other statuses pass
123
+ * through immediately. Call `reset()` at the top of each retry attempt to
124
+ * discard a buffered "failed" from the previous attempt.
125
+ *
126
+ * Closes two leak paths (issue #704):
127
+ * 1. Retry-recovered: attempt N emits "failed" before throwing; a later attempt
128
+ * succeeds → reset() at the top of the next iteration discards the buffer,
129
+ * and flush() is never called on the success return → sink never sees it.
130
+ * 2. Late watcher event: papi can emit a delayed drop/reorg after
131
+ * signAndSubmitExtrinsic has already resolved with "finalized"; the buffer
132
+ * is never flushed on the success path → silently dropped.
133
+ */
134
+ declare function makeRetryStatusFilter(sink: (status: string) => void): {
135
+ callback: (status: string) => void;
136
+ flush: () => void;
137
+ reset: () => void;
138
+ };
120
139
  declare const DEFAULT_MNEMONIC: string;
121
140
  declare function fetchNonce(rpc: string | string[], ss58Address: string): Promise<number>;
122
141
  declare function verifyNonceAdvanced(endpoints: string[], ss58Address: string, originalNonce: number): Promise<{
@@ -257,6 +276,7 @@ declare class ReviveClientWrapper {
257
276
  getEvmAddress(substrateAddress: string): Promise<string>;
258
277
  performDryRunCall(originSubstrateAddress: string, contractAddress: string, value: bigint, encodedData: string): Promise<any>;
259
278
  estimateGasForCall(originSubstrateAddress: string, contractAddress: string, value: bigint, encodedData: string): Promise<any>;
279
+ hasContractCode(address: string): Promise<boolean | null>;
260
280
  checkIfAccountMapped(substrateAddress: string): Promise<boolean>;
261
281
  ensureAccountMapped(substrateAddress: string, signer: PolkadotSigner): Promise<void>;
262
282
  signAndSubmitExtrinsic(extrinsic: any, signer: PolkadotSigner, statusCallback: (status: string) => void, opts?: {
@@ -392,6 +412,17 @@ declare class DotNS {
392
412
  ensureMappedAccountReady(autoAccountMapping?: boolean): Promise<void>;
393
413
  ensureAutoMappedAccountReady(): Promise<void>;
394
414
  ensureConnected(): void;
415
+ /**
416
+ * Resolve the authoritative nativeToEthRatio for this session.
417
+ *
418
+ * Priority: chain constant (Revive.NativeToEthRatio) > options.nativeToEthRatio > default.
419
+ * On mismatch between the env-configured value and the chain value, logs a WARNING naming
420
+ * both values and proceeds with the chain value (it is the source of truth).
421
+ * On query failure, falls back to the configured/default value without throwing.
422
+ *
423
+ * Must be called after clientWrapper is established (i.e. inside connect()).
424
+ */
425
+ resolveNativeToEthRatio(options: DotNSConnectOptions): Promise<void>;
395
426
  private _testnetCache;
396
427
  isTestnet(): Promise<boolean>;
397
428
  /**
@@ -529,4 +560,4 @@ declare class DotNS {
529
560
  }
530
561
  declare const dotns: DotNS;
531
562
 
532
- export { ATTR_TX_RESOLUTION_KIND, type AliasAccountClassification, type AliasAccountState, CONNECTION_TIMEOUT_MS, CONTRACTS, ContractDryRunRevertError, DECIMALS, DEFAULT_MNEMONIC, DOTNS_TX_MAX_ATTEMPTS, DOT_NODE, DotNS, type DotNSConnectOptions, type DotnsPreflightResult, type DotnsSuccessAction, MINIMUM_REGISTER_STORAGE_DEPOSIT, NATIVE_TO_ETH_RATIO, OPERATION_TIMEOUT_MS, type OwnershipResult, PUBLISHER_ABI, type ParsedDomainName, type PriceValidationResult, ProofOfPersonhoodStatus, PublisherNotSupportedError, RPC_ENDPOINTS, TX_CHAIN_TIME_BUDGET_MS, TX_KIND_HASH, TX_KIND_NONCE_ADVANCED, TX_NO_PROGRESS_MS, TX_TIMEOUT_MS, TX_WALL_CLOCK_CEILING_MS, type TxResolution, WS_HEARTBEAT_TIMEOUT_MS, __formatContractDryRunFailureForTest, canRegister, classifyAliasAccountRow, classifyDotnsLabel, classifyTxRetryDecision, computeDomainTokenId, convertToHexString, convertWeiToNative, countTrailingDigits, decodePublisherRevert, dotns, feeFloorFor, fetchNonce, fmtPas, formatDispatchError, formatPersonhoodRemediation, formatPopShortfallReason, isCommitmentMature, isCommitmentTimingBarerevert, parseDomainName, parseProofOfPersonhoodStatus, popStatusName, sanitizeDomainLabel, stripTrailingDigits, validateDomainLabel, verifyNonceAdvanced };
563
+ export { ATTR_TX_RESOLUTION_KIND, type AliasAccountClassification, type AliasAccountState, CONNECTION_TIMEOUT_MS, CONTRACTS, ContractDryRunRevertError, DECIMALS, DEFAULT_MNEMONIC, DOTNS_TX_MAX_ATTEMPTS, DOT_NODE, DotNS, type DotNSConnectOptions, type DotnsPreflightResult, type DotnsSuccessAction, MINIMUM_REGISTER_STORAGE_DEPOSIT, NATIVE_TO_ETH_RATIO, OPERATION_TIMEOUT_MS, type OwnershipResult, PUBLISHER_ABI, type ParsedDomainName, type PriceValidationResult, ProofOfPersonhoodStatus, PublisherNotSupportedError, RPC_ENDPOINTS, TX_CHAIN_TIME_BUDGET_MS, TX_KIND_HASH, TX_KIND_NONCE_ADVANCED, TX_NO_PROGRESS_MS, TX_TIMEOUT_MS, TX_WALL_CLOCK_CEILING_MS, type TxResolution, WS_HEARTBEAT_TIMEOUT_MS, __formatContractDryRunFailureForTest, canRegister, classifyAliasAccountRow, classifyDotnsLabel, classifyTxRetryDecision, computeDomainTokenId, convertToHexString, convertWeiToNative, countTrailingDigits, decodePublisherRevert, dotns, dotnsRetryBackoffMs, feeFloorFor, fetchNonce, fmtPas, formatDispatchError, formatPersonhoodRemediation, formatPopShortfallReason, isCommitmentMature, isCommitmentTimingBarerevert, makeRetryStatusFilter, parseDomainName, parseProofOfPersonhoodStatus, popStatusName, sanitizeDomainLabel, stripTrailingDigits, validateDomainLabel, verifyNonceAdvanced };
package/dist/dotns.js CHANGED
@@ -33,6 +33,7 @@ import {
33
33
  countTrailingDigits,
34
34
  decodePublisherRevert,
35
35
  dotns,
36
+ dotnsRetryBackoffMs,
36
37
  feeFloorFor,
37
38
  fetchNonce,
38
39
  fmtPas,
@@ -41,6 +42,7 @@ import {
41
42
  formatPopShortfallReason,
42
43
  isCommitmentMature,
43
44
  isCommitmentTimingBarerevert,
45
+ makeRetryStatusFilter,
44
46
  parseDomainName,
45
47
  parseProofOfPersonhoodStatus,
46
48
  popStatusName,
@@ -48,11 +50,11 @@ import {
48
50
  stripTrailingDigits,
49
51
  validateDomainLabel,
50
52
  verifyNonceAdvanced
51
- } from "./chunk-IRT4A7OO.js";
52
- import "./chunk-RW3GWDGI.js";
53
- import "./chunk-INVA3XGG.js";
54
- import "./chunk-V5N5EYNV.js";
55
- import "./chunk-CNPB4VAM.js";
53
+ } from "./chunk-7GYCJPFI.js";
54
+ import "./chunk-JQ5X3VMP.js";
55
+ import "./chunk-5EJ247OO.js";
56
+ import "./chunk-N27JUWU2.js";
57
+ import "./chunk-XFX4VODU.js";
56
58
  import "./chunk-ZOC4GITL.js";
57
59
  export {
58
60
  ATTR_TX_RESOLUTION_KIND,
@@ -89,6 +91,7 @@ export {
89
91
  countTrailingDigits,
90
92
  decodePublisherRevert,
91
93
  dotns,
94
+ dotnsRetryBackoffMs,
92
95
  feeFloorFor,
93
96
  fetchNonce,
94
97
  fmtPas,
@@ -97,6 +100,7 @@ export {
97
100
  formatPopShortfallReason,
98
101
  isCommitmentMature,
99
102
  isCommitmentTimingBarerevert,
103
+ makeRetryStatusFilter,
100
104
  parseDomainName,
101
105
  parseProofOfPersonhoodStatus,
102
106
  popStatusName,
@@ -44,13 +44,15 @@ interface EnvironmentsDoc {
44
44
  environments: Environment[];
45
45
  chains: Chain[];
46
46
  }
47
- type EnvironmentsSource = "bundled" | "hardcoded-fallback";
47
+ type EnvironmentsSource = "bundled" | "hardcoded-fallback" | "file";
48
48
  interface LoadResult {
49
49
  doc: EnvironmentsDoc;
50
50
  source: EnvironmentsSource;
51
51
  }
52
52
  interface LoadOptions {
53
53
  bundledPath?: string;
54
+ /** Explicit path to a user-supplied environments JSON file (deep-merged over bundled). */
55
+ userFilePath?: string;
54
56
  warn?: (msg: string) => void;
55
57
  capture?: (err: unknown) => void;
56
58
  }
@@ -86,6 +88,17 @@ declare function isValidContractAddress(addr: unknown): boolean;
86
88
  * @param envId The environment ID, used in the error message.
87
89
  */
88
90
  declare function validateContractAddresses(contracts: Record<string, string>, envId: string): void;
91
+ /**
92
+ * Deep-merges a user-supplied partial EnvironmentsDoc over a base doc.
93
+ *
94
+ * - `environments` and `chains` arrays are merged by `id`: a user entry with a
95
+ * matching id is recursively merged into the base entry; unmatched user entries
96
+ * are appended.
97
+ * - Within each entry, nested plain objects (e.g. `contracts`, `endpoints`) are
98
+ * merged key-by-key; scalars and arrays (e.g. `wss`) replace wholesale.
99
+ * - Top-level fields not present in the user doc are kept from base.
100
+ */
101
+ declare function deepMergeEnvironments(base: EnvironmentsDoc, override: Partial<EnvironmentsDoc>): EnvironmentsDoc;
89
102
  declare function loadEnvironments(opts?: LoadOptions): Promise<LoadResult>;
90
103
  declare function resolveEndpoints(doc: EnvironmentsDoc, envId: string): ResolvedEndpoints;
91
104
  declare function getPopSelfServeConfig(doc: EnvironmentsDoc, envId: string): PopSelfServeConfig | null;
@@ -99,4 +112,4 @@ interface EnvironmentListing {
99
112
  declare function listEnvironments(doc: EnvironmentsDoc): EnvironmentListing[];
100
113
  declare function formatEnvironmentTable(rows: EnvironmentListing[]): string;
101
114
 
102
- export { type Chain, type ChainEndpoint, DEFAULT_ENV_ID, type Environment, type EnvironmentListing, type EnvironmentsDoc, type EnvironmentsSource, type LoadOptions, type LoadResult, type PopSelfServeConfig, type ResolvedEndpoints, defaultBundledPath, formatEnvironmentTable, getPopSelfServeConfig, isValidContractAddress, listEnvironments, loadEnvironments, resolveEndpoints, validateContractAddresses };
115
+ export { type Chain, type ChainEndpoint, DEFAULT_ENV_ID, type Environment, type EnvironmentListing, type EnvironmentsDoc, type EnvironmentsSource, type LoadOptions, type LoadResult, type PopSelfServeConfig, type ResolvedEndpoints, deepMergeEnvironments, defaultBundledPath, formatEnvironmentTable, getPopSelfServeConfig, isValidContractAddress, listEnvironments, loadEnvironments, resolveEndpoints, validateContractAddresses };
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  DEFAULT_ENV_ID,
3
+ deepMergeEnvironments,
3
4
  defaultBundledPath,
4
5
  formatEnvironmentTable,
5
6
  getPopSelfServeConfig,
@@ -8,10 +9,11 @@ import {
8
9
  loadEnvironments,
9
10
  resolveEndpoints,
10
11
  validateContractAddresses
11
- } from "./chunk-CNPB4VAM.js";
12
+ } from "./chunk-XFX4VODU.js";
12
13
  import "./chunk-ZOC4GITL.js";
13
14
  export {
14
15
  DEFAULT_ENV_ID,
16
+ deepMergeEnvironments,
15
17
  defaultBundledPath,
16
18
  formatEnvironmentTable,
17
19
  getPopSelfServeConfig,
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@ export { ChainProbeOptions, ChunkProbeResult, probeChunks } from './chunk-probe.
7
7
  export { finaliseEmbeddedManifest, writeEmbeddedManifestPlaceholder } from './manifest-embed.js';
8
8
  export { FetchOptions, FetchOutcome, fetchPreviousManifest } from './manifest-fetch.js';
9
9
  export { ComputeStatsInput, IncrementalStats, computeStats, renderSummary, telemetryAttributes } from './incremental-stats.js';
10
- export { Chain, ChainEndpoint, DEFAULT_ENV_ID, Environment, EnvironmentListing, EnvironmentsDoc, EnvironmentsSource, LoadOptions, LoadResult, ResolvedEndpoints, defaultBundledPath, formatEnvironmentTable, isValidContractAddress, listEnvironments, loadEnvironments, resolveEndpoints, validateContractAddresses } from './environments.js';
10
+ export { Chain, ChainEndpoint, DEFAULT_ENV_ID, Environment, EnvironmentListing, EnvironmentsDoc, EnvironmentsSource, LoadOptions, LoadResult, ResolvedEndpoints, deepMergeEnvironments, defaultBundledPath, formatEnvironmentTable, isValidContractAddress, listEnvironments, loadEnvironments, resolveEndpoints, validateContractAddresses } from './environments.js';
11
11
  export { RunState, RunStatus, VERSION, loadRunState, probablyOomRssMb, resolveStateDir, shouldShowOomHint, shouldSkipStaleWarning, stateFilePath, writeRunState } from './run-state.js';
12
12
  export { AppExecutableConfig, AppManifest, AppVersion, ExecutableConfig, ExecutableKind, ExecutableManifest, Icon, IconConfig, IconFormat, ProductConfig, RootManifest, WidgetDimensions, WidgetExecutableConfig, WidgetManifest, WorkerExecutableConfig, WorkerIncludes, WorkerManifest, defineConfig } from './manifest/types.js';
13
13
  export { ValidationErr, ValidationOk, ValidationResult, validateExecutableManifest, validateProductConfig, validateRootManifest } from './manifest/schema.js';