bulletin-deploy 0.7.24 → 0.7.25-rc.1

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 (36) hide show
  1. package/assets/environments.json +2 -0
  2. package/bin/bulletin-deploy +10 -1
  3. package/dist/bug-report.js +4 -4
  4. package/dist/{chunk-4V6HI3TA.js → chunk-4HFR7OYX.js} +1 -1
  5. package/dist/{chunk-N6AK5PSE.js → chunk-5BYKEV2D.js} +2 -2
  6. package/dist/{chunk-LEYQOOWC.js → chunk-BO22ZDUF.js} +15 -10
  7. package/dist/{chunk-5VEZVLDN.js → chunk-DJYVUPAF.js} +1 -1
  8. package/dist/{chunk-5MRZ3V4A.js → chunk-JOHTUV2D.js} +99 -2
  9. package/dist/{chunk-K6RIDRB7.js → chunk-LE62ANFY.js} +2 -2
  10. package/dist/{chunk-R6NLVFXP.js → chunk-O3S64AGV.js} +94 -41
  11. package/dist/{chunk-RZOZQ2AP.js → chunk-ODVRROGV.js} +114 -89
  12. package/dist/{chunk-5JHQZDWQ.js → chunk-Q7JW7TGW.js} +20 -1
  13. package/dist/{chunk-L7KXFYLH.js → chunk-Z2ZZKED3.js} +35 -6
  14. package/dist/chunk-probe.js +3 -3
  15. package/dist/deploy.d.ts +18 -2
  16. package/dist/deploy.js +10 -10
  17. package/dist/dotns.d.ts +7 -8
  18. package/dist/dotns.js +9 -5
  19. package/dist/environments.d.ts +23 -1
  20. package/dist/environments.js +7 -3
  21. package/dist/incremental-stats.d.ts +2 -0
  22. package/dist/incremental-stats.js +1 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +15 -11
  25. package/dist/manifest-fetch.d.ts +10 -1
  26. package/dist/manifest-fetch.js +8 -3
  27. package/dist/manifest-roundtrip.js +2 -1
  28. package/dist/memory-report.js +2 -2
  29. package/dist/merkle.js +10 -10
  30. package/dist/personhood/bootstrap.js +4 -4
  31. package/dist/personhood/people-client.js +4 -4
  32. package/dist/run-state.js +1 -1
  33. package/dist/telemetry.js +2 -2
  34. package/dist/version-check.d.ts +4 -2
  35. package/dist/version-check.js +7 -3
  36. package/package.json +3 -3
@@ -9,6 +9,7 @@
9
9
  "ipfs": "https://previewnet.substrate.dev/ipfs/",
10
10
  "autoAccountMapping": true,
11
11
  "bulletinAuthorizeV2": true,
12
+ "registerStorageDeposit": 2000000000000,
12
13
  "contracts": {
13
14
  "DOTNS_PROTOCOL_REGISTRY": "0xc07A2F24387DA27283CD87b9F24573b74C9e0c9b",
14
15
  "DOTNS_REGISTRAR": "0x6c40817cdb96Ab57A4d9E9fa21D0eEa8307BDDE8",
@@ -56,6 +57,7 @@
56
57
  "autoAccountMapping": true,
57
58
  "bulletinAuthorizeV2": true,
58
59
  "nativeToEthRatio": 100000000,
60
+ "registerStorageDeposit": 2000000000000,
59
61
  "popSelfServe": {
60
62
  "sudoEnvLabel": "Next V2",
61
63
  "faucetUrl": "https://faucet.polkadot.io/?parachain=1500",
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { deploy, DEFAULT_BULLETIN_RPC, DEFAULT_POOL_SIZE, NonRetryableError, EXIT_CODE_NO_RETRY, isConnectionError } from "../dist/deploy.js";
4
4
  import { VERSION, setDeployAttribute, captureWarning, closeTelemetry, setRunStateActive, markRelaunchOomHintShown } from "../dist/telemetry.js";
5
- import { handleFailedDeploy, preReleaseWarning, checkNodeVersion } from "../dist/version-check.js";
5
+ import { handleFailedDeploy, handlePreflightVersionCheck, fetchVersionInfo, preReleaseWarning, checkNodeVersion } from "../dist/version-check.js";
6
6
  import { setDeployContext, installLogCapture, buildCliFlagsSummary } from "../dist/bug-report.js";
7
7
  import { loadRunState, writeRunState, shouldSkipStaleWarning, shouldShowOomHint, probablyOomRssMb } from "../dist/run-state.js";
8
8
  import { loadEnvironments, listEnvironments, formatEnvironmentTable, DEFAULT_ENV_ID } from "../dist/environments.js";
@@ -113,6 +113,11 @@ Options:
113
113
  const rcWarning = preReleaseWarning(VERSION);
114
114
  if (rcWarning) console.error(rcWarning);
115
115
 
116
+ // Fire in background immediately; await just before deploy() to stay non-blocking during flag-parse.
117
+ const _versionCheckPromise = process.env.BULLETIN_DEPLOY_UPDATE_CHECK !== "0"
118
+ ? fetchVersionInfo()
119
+ : Promise.resolve(null);
120
+
116
121
  // ── Crash capture (issue #154) ───────────────────────────────────
117
122
  // Only wire crash capture for actual deploy runs — skip for --help / --version
118
123
  // (which exit above).
@@ -251,6 +256,10 @@ try {
251
256
  ci,
252
257
  });
253
258
 
259
+ // Await the background version check started at startup.
260
+ const _versionInfo = await _versionCheckPromise;
261
+ if (handlePreflightVersionCheck(_versionInfo) === "abort") process.exit(1);
262
+
254
263
  const result = await deploy(buildDir, domain, {
255
264
  mnemonic: flags.mnemonic,
256
265
  derivationPath: flags.derivationPath,
@@ -9,10 +9,10 @@ import {
9
9
  offerBugReport,
10
10
  scrubSecrets,
11
11
  setDeployContext
12
- } from "./chunk-K6RIDRB7.js";
13
- import "./chunk-L7KXFYLH.js";
14
- import "./chunk-5VEZVLDN.js";
15
- import "./chunk-N6AK5PSE.js";
12
+ } from "./chunk-LE62ANFY.js";
13
+ import "./chunk-Z2ZZKED3.js";
14
+ import "./chunk-DJYVUPAF.js";
15
+ import "./chunk-5BYKEV2D.js";
16
16
  export {
17
17
  buildCliFlagsSummary,
18
18
  buildLabels,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  captureWarning
3
- } from "./chunk-5VEZVLDN.js";
3
+ } from "./chunk-DJYVUPAF.js";
4
4
 
5
5
  // src/chunk-probe.ts
6
6
  import { Twox128, Blake2128Concat, decAnyMetadata, unifyMetadata } from "@polkadot-api/substrate-bindings";
@@ -6,7 +6,7 @@ import * as path from "path";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "bulletin-deploy",
9
- version: "0.7.24",
9
+ version: "0.7.25-rc.1",
10
10
  private: false,
11
11
  repository: {
12
12
  type: "git",
@@ -77,7 +77,7 @@ var package_default = {
77
77
  "@types/node": "^22.0.0",
78
78
  tsup: "^8.5.0",
79
79
  typescript: "^5.9.3",
80
- ws: "^8.20.0"
80
+ ws: "^8.20.1"
81
81
  },
82
82
  minimumVersion: "0.5.6",
83
83
  engines: {
@@ -25,6 +25,7 @@ function computeStats(input) {
25
25
  recycledCids: recycled.length,
26
26
  retentionPeriodBlocks: input.retentionPeriodBlocks,
27
27
  bytesProbePresent: input.bytesProbePresent,
28
+ bytesProbeAbsent: input.bytesProbeAbsent ?? 0,
28
29
  bytesSkipped: input.bytesSkipped,
29
30
  bytesUploaded: input.bytesUploaded,
30
31
  chunksTotal: input.chunksTotal,
@@ -64,6 +65,7 @@ function telemetryAttributes(s) {
64
65
  "deploy.cache.chunks_uploaded": String(s.chunksUploaded),
65
66
  "deploy.cache.chunks_skipped": String(s.chunksSkipped),
66
67
  "deploy.cache.bytes_probe_present": String(s.bytesProbePresent),
68
+ "deploy.cache.bytes_probe_absent": String(s.bytesProbeAbsent),
67
69
  "deploy.cache.bytes_skipped": String(s.bytesSkipped),
68
70
  "deploy.cache.bytes_uploaded": String(s.bytesUploaded),
69
71
  "deploy.cache.car_bytes": String(s.carBytes),
@@ -84,8 +86,9 @@ function fmtKb(bytes) {
84
86
  }
85
87
  function renderSummary(s) {
86
88
  const lines = [];
89
+ const attemptsWord = s.manifestFetchAttempts === 1 ? "attempt" : "attempts";
87
90
  if (s.manifestSource === "heuristic_fallback") {
88
- lines.push(` \u26A0 Previous manifest fetch failed after ${s.manifestFetchAttempts} attempts (gateway timeout).`);
91
+ lines.push(` \u26A0 Previous manifest fetch failed after ${s.manifestFetchAttempts} ${attemptsWord} (gateway timeout).`);
89
92
  lines.push(` Using heuristic classification \u2014 hit rate may be lower this run.`);
90
93
  lines.push(` Subsequent deploys recover automatically.`);
91
94
  lines.push("");
@@ -94,15 +97,15 @@ function renderSummary(s) {
94
97
  if (s.manifestSource === "none") {
95
98
  lines.push(` Manifest: first deploy (no previous manifest)`);
96
99
  } else if (s.manifestSource === "embedded") {
97
- const attemptsStr = `${s.manifestFetchAttempts} attempt${s.manifestFetchAttempts === 1 ? "" : "s"}`;
98
100
  const sizeStr = s.manifestBytes > 0 ? `, ${fmtKb(s.manifestBytes)} KB Range hit` : "";
99
- lines.push(` Manifest: embedded (${attemptsStr}${sizeStr})`);
101
+ lines.push(` Manifest: embedded (${s.manifestFetchAttempts} ${attemptsWord}${sizeStr})`);
100
102
  } else {
101
- lines.push(` Manifest: heuristic_fallback (${s.manifestFetchAttempts} attempts)`);
103
+ lines.push(` Manifest: heuristic_fallback (${s.manifestFetchAttempts} ${attemptsWord})`);
102
104
  }
103
105
  if (s.filesTotal > 0 && s.manifestSource !== "none") {
104
106
  const pct = s.filesTotal === 0 ? 0 : Math.round(s.filesStable / s.filesTotal * 100);
105
- lines.push(` Files: ${s.filesStable} unchanged, ${s.filesVolatile} changed (${pct} % stable)`);
107
+ const heuristicNote = s.manifestSource === "heuristic_fallback" ? " (heuristic estimate)" : "";
108
+ lines.push(` Files: ${s.filesStable} unchanged, ${s.filesVolatile} changed (${pct} % stable)${heuristicNote}`);
106
109
  }
107
110
  if (s.probedTotal > 0) {
108
111
  let probeFailedStr = "";
@@ -115,7 +118,7 @@ function renderSummary(s) {
115
118
  }
116
119
  lines.push(` Probed: ${s.probedTotal} chunks \u2192 ${s.probePresent} cached, ${s.probeAbsent} to upload${probeFailedStr}`);
117
120
  }
118
- if (s.recycledCids > 0) {
121
+ if (s.recycledCids > 0 && s.manifestSource === "embedded") {
119
122
  lines.push(` Recycled: ${s.recycledCids} CIDs found on-chain that weren't in the previous manifest`);
120
123
  }
121
124
  if (s.tier2FallbackCount > 0) {
@@ -123,10 +126,12 @@ function renderSummary(s) {
123
126
  lines.push(` Verify: ${s.tier2VerifiedCount}/${s.tier2FallbackCount} via-fallback chunks confirmed on chain${inconclusiveStr}`);
124
127
  }
125
128
  lines.push(` CAR sections: manifest ${fmtKb(s.section0Bytes)} KB \xB7 stable ${fmtMb(s.section1Bytes)} MB \xB7 volatile ${fmtMb(s.section2Bytes)} MB`);
126
- if (s.chunksUploaded > 0 && s.bytesSkipped > 0) {
127
- lines.push(` Upload: ${fmtMb(s.bytesUploaded)} MB across ${s.chunksUploaded} chunks (vs ${fmtMb(s.carBytes)} MB if full deploy)`);
128
- } else if (s.chunksUploaded > 0) {
129
- lines.push(` Upload: ${fmtMb(s.bytesUploaded)} MB across ${s.chunksUploaded} chunks`);
129
+ if (s.chunksUploaded > 0) {
130
+ if (s.bytesSkipped > 0) {
131
+ lines.push(` Upload: ${fmtMb(s.bytesUploaded)} MB across ${s.chunksUploaded} chunks (vs ${fmtMb(s.carBytes)} MB if full deploy)`);
132
+ } else {
133
+ lines.push(` Upload: ${fmtMb(s.bytesUploaded)} MB across ${s.chunksUploaded} chunks`);
134
+ }
130
135
  }
131
136
  if (s.estimatedSecondsSaved > 0 || s.bytesSkipped > 0) {
132
137
  lines.push(` Saved: ~${s.estimatedSecondsSaved} s and ${fmtMb(s.bytesSkipped)} MB`);
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  package_default,
3
3
  writeRunState
4
- } from "./chunk-N6AK5PSE.js";
4
+ } from "./chunk-5BYKEV2D.js";
5
5
 
6
6
  // src/memory-report.ts
7
7
  import * as fs2 from "fs";
@@ -1,15 +1,24 @@
1
1
  import {
2
2
  MANIFEST_DIR,
3
3
  MANIFEST_FILENAME,
4
+ MANIFEST_PATH,
4
5
  parseManifest
5
6
  } from "./chunk-S7EM5VMW.js";
7
+ import {
8
+ mirrorUrl,
9
+ normalizeDomainFilename,
10
+ resolveOwnerRepo
11
+ } from "./chunk-HOTQDYHD.js";
6
12
 
7
13
  // src/manifest-fetch.ts
14
+ import * as fs from "fs";
15
+ import * as path from "path";
8
16
  import { CarReader } from "@ipld/car/reader";
9
17
  import * as dagPB from "@ipld/dag-pb";
10
18
  import { CID } from "multiformats/cid";
11
19
  var DEFAULT_GATEWAY = "https://paseo-ipfs.polkadot.io";
12
20
  var DEFAULT_TIMEOUT_MS = 5e3;
21
+ var SIDECAR_FILENAME = ".last_deploy_cid";
13
22
  var RANGE_TIERS = [
14
23
  "bytes=0-4095",
15
24
  "bytes=0-65535",
@@ -17,6 +26,67 @@ var RANGE_TIERS = [
17
26
  void 0
18
27
  // full body
19
28
  ];
29
+ function readLocalManifest(buildDir, prevContenthash) {
30
+ const sidePath = path.join(buildDir, MANIFEST_DIR, SIDECAR_FILENAME);
31
+ try {
32
+ const lastCid = fs.readFileSync(sidePath, "utf8").trim();
33
+ if (lastCid !== prevContenthash) return null;
34
+ } catch {
35
+ return null;
36
+ }
37
+ const manifestPath = path.join(buildDir, MANIFEST_PATH);
38
+ let text;
39
+ try {
40
+ text = fs.readFileSync(manifestPath, "utf8");
41
+ } catch {
42
+ return null;
43
+ }
44
+ const parsed = parseManifest(text);
45
+ if (!parsed.ok) return null;
46
+ return {
47
+ source: "embedded",
48
+ manifest: parsed.manifest,
49
+ attempts: 0,
50
+ bytesDownloaded: text.length
51
+ };
52
+ }
53
+ async function fetchFromGitHubPages(domain, prevContenthash, options, start) {
54
+ let ownerRepo;
55
+ try {
56
+ ownerRepo = resolveOwnerRepo(process.cwd());
57
+ } catch {
58
+ return null;
59
+ }
60
+ if (!ownerRepo) return null;
61
+ const { owner, repo } = ownerRepo;
62
+ let domainFilename;
63
+ try {
64
+ domainFilename = normalizeDomainFilename(domain);
65
+ } catch {
66
+ return null;
67
+ }
68
+ const carUrl = mirrorUrl(owner, repo, domainFilename);
69
+ const sidecarUrl = carUrl.replace(/\.car$/, ".json");
70
+ const budget = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
71
+ try {
72
+ const sidecarBudget = Math.min(2e3, budget - (Date.now() - start));
73
+ if (sidecarBudget <= 0) return null;
74
+ const ctrl = new AbortController();
75
+ const timer = setTimeout(() => ctrl.abort(), sidecarBudget);
76
+ let sidecarRes;
77
+ try {
78
+ sidecarRes = await fetch(sidecarUrl, { signal: ctrl.signal });
79
+ } finally {
80
+ clearTimeout(timer);
81
+ }
82
+ if (!sidecarRes.ok) return null;
83
+ const sidecar = await sidecarRes.json();
84
+ if (sidecar.cid !== prevContenthash) return null;
85
+ } catch {
86
+ return null;
87
+ }
88
+ return fetchAcrossTiers(carUrl, budget, start);
89
+ }
20
90
  async function fetchAcrossTiers(url, budget, start) {
21
91
  let lastReason = "unknown";
22
92
  let attempts = 0;
@@ -91,9 +161,34 @@ async function fetchAcrossTiers(url, budget, start) {
91
161
  }
92
162
  async function fetchPreviousManifest(prevContenthash, options = {}) {
93
163
  if (prevContenthash === null) return { source: "none" };
94
- const gatewayList = (options.gateways ?? (options.gateway ? [options.gateway] : [DEFAULT_GATEWAY])).map((g) => g.replace(/\/$/, ""));
95
- const budget = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
164
+ if (options.buildDir) {
165
+ const local = readLocalManifest(options.buildDir, prevContenthash);
166
+ if (local) return local;
167
+ }
96
168
  const start = Date.now();
169
+ const budget = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
170
+ if (options.domain) {
171
+ const ghResult = await fetchFromGitHubPages(options.domain, prevContenthash, options, start);
172
+ if (ghResult) {
173
+ if (ghResult.outcome === "success") {
174
+ return {
175
+ source: "embedded",
176
+ manifest: ghResult.manifest,
177
+ attempts: ghResult.attempts,
178
+ bytesDownloaded: ghResult.bytesDownloaded
179
+ };
180
+ }
181
+ if (ghResult.outcome === "404" || ghResult.outcome === "parse_error") {
182
+ return {
183
+ source: "heuristic_fallback",
184
+ reason: ghResult.outcome === "404" ? "gh-pages 404" : ghResult.reason,
185
+ attempts: ghResult.attempts,
186
+ bytesDownloaded: ghResult.bytesDownloaded
187
+ };
188
+ }
189
+ }
190
+ }
191
+ const gatewayList = (options.gateways ?? (options.gateway ? [options.gateway] : [DEFAULT_GATEWAY])).map((g) => g.replace(/\/$/, ""));
97
192
  let lastReason = "unknown";
98
193
  let totalAttempts = 0;
99
194
  let bytesDownloaded = 0;
@@ -166,6 +261,8 @@ async function extractManifestFromCar(carBytes) {
166
261
  export {
167
262
  DEFAULT_GATEWAY,
168
263
  DEFAULT_TIMEOUT_MS,
264
+ SIDECAR_FILENAME,
265
+ readLocalManifest,
169
266
  fetchPreviousManifest,
170
267
  extractManifestFromCar
171
268
  };
@@ -2,11 +2,11 @@ import {
2
2
  classifyErrorArea,
3
3
  isInteractive,
4
4
  promptYesNo
5
- } from "./chunk-L7KXFYLH.js";
5
+ } from "./chunk-Z2ZZKED3.js";
6
6
  import {
7
7
  VERSION,
8
8
  getCurrentSentryTraceId
9
- } from "./chunk-5VEZVLDN.js";
9
+ } from "./chunk-DJYVUPAF.js";
10
10
 
11
11
  // src/bug-report.ts
12
12
  import { execSync, execFileSync } from "child_process";
@@ -7,7 +7,10 @@ import {
7
7
  setDeploySentryTag,
8
8
  truncateAddress,
9
9
  withSpan
10
- } from "./chunk-5VEZVLDN.js";
10
+ } from "./chunk-DJYVUPAF.js";
11
+ import {
12
+ validateContractAddresses
13
+ } from "./chunk-Q7JW7TGW.js";
11
14
 
12
15
  // src/dotns.ts
13
16
  import crypto from "crypto";
@@ -37,6 +40,7 @@ var FEE_FLOOR_OWNED = ONE_PAS / 100n;
37
40
  var FEE_FLOOR_REGISTER = ONE_PAS / 10n;
38
41
  var TOP_UP_TARGET = ONE_PAS / 2n;
39
42
  var SOURCE_BUFFER = ONE_PAS;
43
+ var MINIMUM_REGISTER_STORAGE_DEPOSIT = 2000000000000n;
40
44
  var REPROVE_FEE_ESTIMATE = ONE_PAS / 100n;
41
45
  var REPROVE_FEE_SAFETY_MARGIN_PCT = 110n;
42
46
  var TOP_UP_TRANSFER_TIMEOUT_MS = 6e4;
@@ -51,8 +55,13 @@ function resolveNativeTokenSymbol(envId) {
51
55
  if (envId.includes("rococo")) return "ROC";
52
56
  return "PAS";
53
57
  }
54
- function feeFloorFor(plannedAction) {
55
- return plannedAction === "already-owned-by-us" ? FEE_FLOOR_OWNED : FEE_FLOOR_REGISTER;
58
+ function feeFloorFor(plannedAction, storageDeposit = MINIMUM_REGISTER_STORAGE_DEPOSIT) {
59
+ if (plannedAction === "already-owned-by-us") return FEE_FLOOR_OWNED;
60
+ return FEE_FLOOR_REGISTER + storageDeposit;
61
+ }
62
+ function topUpTargetFor(plannedAction, storageDeposit = MINIMUM_REGISTER_STORAGE_DEPOSIT) {
63
+ if (plannedAction === "already-owned-by-us") return TOP_UP_TARGET;
64
+ return TOP_UP_TARGET + storageDeposit;
56
65
  }
57
66
  var RPC_ENDPOINTS = [
58
67
  "wss://asset-hub-paseo.dotters.network",
@@ -145,6 +154,7 @@ var DOTNS_REGISTRAR_CONTROLLER_ABI = [
145
154
  { inputs: [{ name: "registration", type: "tuple", components: [{ name: "label", type: "string" }, { name: "owner", type: "address" }, { name: "secret", type: "bytes32" }, { name: "reserved", type: "bool" }] }], name: "makeCommitment", outputs: [{ name: "", type: "bytes32" }], stateMutability: "view", type: "function" },
146
155
  { inputs: [{ name: "commitment", type: "bytes32" }], name: "commit", outputs: [], stateMutability: "nonpayable", type: "function" },
147
156
  { inputs: [], name: "minCommitmentAge", outputs: [{ name: "", type: "uint256" }], stateMutability: "view", type: "function" },
157
+ { inputs: [], name: "maxCommitmentAge", outputs: [{ name: "", type: "uint256" }], stateMutability: "view", type: "function" },
148
158
  { inputs: [{ name: "commitment", type: "bytes32" }], name: "commitments", outputs: [{ name: "", type: "uint256" }], stateMutability: "view", type: "function" },
149
159
  { inputs: [{ name: "registration", type: "tuple", components: [{ name: "label", type: "string" }, { name: "owner", type: "address" }, { name: "secret", type: "bytes32" }, { name: "reserved", type: "bool" }] }], name: "register", outputs: [], stateMutability: "payable", type: "function" }
150
160
  ];
@@ -156,7 +166,6 @@ var POP_RULES_ABI = [
156
166
  { inputs: [{ name: "name", type: "string" }], name: "price", outputs: [{ name: "", type: "uint256" }], stateMutability: "view", type: "function" },
157
167
  { inputs: [{ name: "name", type: "string" }, { name: "userAddress", type: "address" }], name: "priceWithCheck", outputs: [{ name: "metadata", type: "tuple", components: [{ name: "price", type: "uint256" }, { name: "status", type: "uint8" }, { name: "userStatus", type: "uint8" }, { name: "message", type: "string" }] }], stateMutability: "view", type: "function" },
158
168
  { inputs: [{ name: "name", type: "string" }, { name: "userAddress", type: "address" }], name: "priceWithoutCheck", outputs: [{ name: "metadata", type: "tuple", components: [{ name: "price", type: "uint256" }, { name: "status", type: "uint8" }, { name: "userStatus", type: "uint8" }, { name: "message", type: "string" }] }], stateMutability: "view", type: "function" },
159
- { inputs: [{ name: "status", type: "uint8" }], name: "setUserPopStatus", outputs: [], stateMutability: "nonpayable", type: "function" },
160
169
  { inputs: [{ name: "name", type: "string" }], name: "isBaseNameReserved", outputs: [{ name: "isReserved", type: "bool" }, { name: "reservationOwner", type: "address" }, { name: "expiryTimestamp", type: "uint64" }], stateMutability: "view", type: "function" }
161
170
  ];
162
171
  var PERSONHOOD_ABI = [
@@ -268,14 +277,27 @@ function formatContractDryRunFailure(gasEstimate, context) {
268
277
  const revertData = gasEstimate.revertData;
269
278
  const isBareRevert = (revertData === void 0 || revertData.trim() === "0x") && gasEstimate.revertFlags === 1n;
270
279
  if (isBareRevert && BARE_REVERT_DIAGNOSTIC_FUNCTIONS.has(functionName)) {
271
- lines.push(
272
- ` diagnostic: bare-revert (empty 0x). Account mapping was verified at connect time, so the cause is likely:`,
273
- ` 1. PoP status changed between preflight and registration (race condition).`,
274
- ` 2. Commitment timing: the revealed commitment is still too new or already expired.`,
275
- ` 3. Label was registered by someone else between preflight and register.`,
276
- ` To reproduce in isolation: \`node tools/dotns-dry-run.mjs <label>\``,
277
- ` To rule out a mapping issue: add --fresh (a brand-new unmapped origin) \u2014 if --fresh reverts but the mapped one doesn't, it's a mapping bug.`
278
- );
280
+ if (functionName === "register") {
281
+ lines.push(
282
+ ` diagnostic: bare-revert (empty 0x) during register. Most likely cause: insufficient signer balance for storage deposit.`,
283
+ ` A fresh TLD register() requires sufficient free balance to cover the chain's storage deposit (typically 200+ PAS).`,
284
+ ` Other possible causes:`,
285
+ ` 1. PoP status changed between preflight and registration (race condition).`,
286
+ ` 2. Commitment timing: the revealed commitment is still too new or already expired.`,
287
+ ` 3. Label was registered by someone else between preflight and register.`,
288
+ ` To reproduce in isolation: \`node tools/dotns-dry-run.mjs <label>\``,
289
+ ` To rule out a mapping issue: add --fresh (a brand-new unmapped origin) \u2014 if --fresh reverts but the mapped one doesn't, it's a mapping bug.`
290
+ );
291
+ } else {
292
+ lines.push(
293
+ ` diagnostic: bare-revert (empty 0x). Account mapping was verified at connect time, so the cause is likely:`,
294
+ ` 1. PoP status changed between preflight and registration (race condition).`,
295
+ ` 2. Commitment timing: the revealed commitment is still too new or already expired.`,
296
+ ` 3. Label was registered by someone else between preflight and register.`,
297
+ ` To reproduce in isolation: \`node tools/dotns-dry-run.mjs <label>\``,
298
+ ` To rule out a mapping issue: add --fresh (a brand-new unmapped origin) \u2014 if --fresh reverts but the mapped one doesn't, it's a mapping bug.`
299
+ );
300
+ }
279
301
  }
280
302
  return lines.join("\n");
281
303
  }
@@ -348,6 +370,9 @@ function validateDomainLabel(label) {
348
370
  function isCommitmentMature(chainNowSeconds, commitTimestampSeconds, minimumAgeSeconds) {
349
371
  return chainNowSeconds > commitTimestampSeconds + minimumAgeSeconds;
350
372
  }
373
+ function isCommitmentTimingBarerevert(msg) {
374
+ return /bare-revert.*\(empty 0x\)/i.test(msg) || /commitment.*too new.*expired/i.test(msg) || /expired.*commitment/i.test(msg);
375
+ }
351
376
  function classifyDotnsLabel(label) {
352
377
  const totalLength = label.length;
353
378
  const trailingDigits = countTrailingDigits(label);
@@ -385,21 +410,6 @@ function exampleNoStatusLabel(label) {
385
410
  const base = stripTrailingDigits(validateDomainLabel(label)).replace(/[^a-z0-9-]/g, "x");
386
411
  return `${base.padEnd(9, "x").slice(0, 9)}00.dot`;
387
412
  }
388
- function simulateUserStatus(currentStatus, requiredStatus, options) {
389
- const canSelfAttest = options.canSelfAttest ?? true;
390
- const max = (a, b) => a > b ? a : b;
391
- if (options.explicitStatus !== void 0) {
392
- if (options.isTestnet && canSelfAttest) return max(currentStatus, options.explicitStatus);
393
- return currentStatus;
394
- }
395
- if (requiredStatus === ProofOfPersonhoodStatus.NoStatus && currentStatus === ProofOfPersonhoodStatus.ProofOfPersonhoodLite && options.isTestnet && canSelfAttest) {
396
- return ProofOfPersonhoodStatus.ProofOfPersonhoodFull;
397
- }
398
- if (requiredStatus !== ProofOfPersonhoodStatus.NoStatus && options.isTestnet && canSelfAttest) {
399
- return max(currentStatus, requiredStatus);
400
- }
401
- return currentStatus;
402
- }
403
413
  function parseDomainName(input) {
404
414
  const name = input.replace(/\.dot$/, "");
405
415
  const parts = name.split(".");
@@ -707,6 +717,7 @@ var DotNS = class {
707
717
  _nativeToEthRatio = NATIVE_TO_ETH_RATIO;
708
718
  _environmentId = null;
709
719
  _popSelfServe = null;
720
+ _registerStorageDeposit = MINIMUM_REGISTER_STORAGE_DEPOSIT;
710
721
  // Test-only seam: consumed once by classifyAliasAccountState() then cleared.
711
722
  // Mirrors the __setDeployRootSpanForTest / __setSentryForTest pattern.
712
723
  _classifyOverrideForTest = null;
@@ -742,6 +753,7 @@ var DotNS = class {
742
753
  this.assetHubEndpoints = options.assetHubEndpoints;
743
754
  }
744
755
  if (options.contracts && Object.keys(options.contracts).length > 0) {
756
+ validateContractAddresses(options.contracts, options.environmentId ?? "unknown");
745
757
  this._contracts = { ...CONTRACTS, ...options.contracts };
746
758
  }
747
759
  if (options.environmentId) {
@@ -750,6 +762,9 @@ var DotNS = class {
750
762
  if (options.popSelfServe !== void 0) {
751
763
  this._popSelfServe = options.popSelfServe ?? null;
752
764
  }
765
+ if (options.registerStorageDeposit !== void 0) {
766
+ this._registerStorageDeposit = options.registerStorageDeposit;
767
+ }
753
768
  const rpc = options.rpc || process.env.DOTNS_RPC || this.assetHubEndpoints[0];
754
769
  this.rpc = rpc;
755
770
  this._usesExternalSigner = Boolean(options.signer && options.signerAddress);
@@ -1136,9 +1151,6 @@ var DotNS = class {
1136
1151
  );
1137
1152
  }
1138
1153
  }
1139
- async setUserPopStatus(_status) {
1140
- throw new Error("DotNS self-attestation is no longer available. Personhood status is read from the Personhood precompile; contact the DotNS team for whitelisting / status changes.");
1141
- }
1142
1154
  async checkSubdomainOwnership(sublabel, parentLabel) {
1143
1155
  this.ensureConnected();
1144
1156
  if (!this.clientWrapper) return { owned: false, owner: null };
@@ -1183,9 +1195,20 @@ var DotNS = class {
1183
1195
  }
1184
1196
  if (!ipfsCid) throw new Error(`setContenthash: cannot decode contenthash ${contenthashHex} to an IPFS CID`);
1185
1197
  console.log(` Setting contenthash: ${ipfsCid}`);
1198
+ const expected = contenthashHex.toLowerCase();
1199
+ try {
1200
+ const current = (await this.getContenthash(domainName) || "0x").toLowerCase();
1201
+ if (current === expected) {
1202
+ console.log(` Contenthash already set: ${ipfsCid} \u2014 skipping tx`);
1203
+ setDeployAttribute("deploy.dotns.contenthash_unchanged", "true");
1204
+ return { node };
1205
+ }
1206
+ } catch (_) {
1207
+ }
1208
+ setDeployAttribute("deploy.dotns.contenthash_unchanged", "false");
1186
1209
  const txHash = await this.contractTransaction(this._contracts.DOTNS_CONTENT_RESOLVER, 0n, DOTNS_CONTENT_RESOLVER_ABI, "setContenthash", [node, contenthashHex], (s) => console.log(` ${s}`), { useNoncePolling: true });
1187
1210
  console.log(` Tx: ${txHash}`);
1188
- const expected = contenthashHex.toLowerCase();
1211
+ let txResolution = txHash.startsWith("nonce-advanced:") ? "nonce-advanced" : "finalized";
1189
1212
  const MAX_CHAIN_WAIT_SECONDS = 90;
1190
1213
  const POLL_INTERVAL_MS = 2e3;
1191
1214
  const startChainMs = Number(await this.clientWrapper.client.query.Timestamp.Now.getValue());
@@ -1199,6 +1222,8 @@ var DotNS = class {
1199
1222
  console.log(` Awaiting finalization (chain time +${Math.floor(chainElapsed)}s / ${MAX_CHAIN_WAIT_SECONDS}s)...`);
1200
1223
  await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
1201
1224
  }
1225
+ if (onChain !== expected) txResolution = "timeout";
1226
+ setDeployAttribute("deploy.dotns.tx_resolution", txResolution);
1202
1227
  if (onChain !== expected) {
1203
1228
  throw new Error(
1204
1229
  `Post-deploy verification failed for ${domainName}.dot: on-chain contenthash is ${onChain}, not the ${expected} we just wrote. The setContenthash tx may have silently failed, or another party overwrote the domain. Re-run the deploy to retry.`
@@ -1300,16 +1325,19 @@ var DotNS = class {
1300
1325
  const POLL_INTERVAL_MS = 3e3;
1301
1326
  console.log(`
1302
1327
  Reading minimum commitment age...`);
1303
- const [minimumAge, initialCommitTimestamp] = await Promise.all([
1328
+ const [minimumAge, maximumAge, initialCommitTimestamp] = await Promise.all([
1304
1329
  withTimeout(this.contractCall(this._contracts.DOTNS_REGISTRAR_CONTROLLER, DOTNS_REGISTRAR_CONTROLLER_ABI, "minCommitmentAge", []), 3e4, "minCommitmentAge"),
1330
+ withTimeout(this.contractCall(this._contracts.DOTNS_REGISTRAR_CONTROLLER, DOTNS_REGISTRAR_CONTROLLER_ABI, "maxCommitmentAge", []), 3e4, "maxCommitmentAge"),
1305
1331
  withTimeout(this.contractCall(this._contracts.DOTNS_REGISTRAR_CONTROLLER, DOTNS_REGISTRAR_CONTROLLER_ABI, "commitments", [commitment]), 3e4, "commitments")
1306
1332
  ]);
1307
1333
  const minimumAgeSeconds = typeof minimumAge === "bigint" ? Number(minimumAge) : minimumAge;
1334
+ const maximumAgeSeconds = typeof maximumAge === "bigint" ? Number(maximumAge) : maximumAge ?? 86400;
1308
1335
  const commitTimestamp = typeof initialCommitTimestamp === "bigint" ? Number(initialCommitTimestamp) : initialCommitTimestamp;
1309
1336
  if (commitTimestamp === 0) {
1310
1337
  throw new Error("Commitment not found on-chain. It may not have been included in a block yet.");
1311
1338
  }
1312
- console.log(` Minimum commitment age: ${minimumAgeSeconds}s`);
1339
+ console.log(` Minimum commitment age: ${minimumAgeSeconds}s, maximum: ${maximumAgeSeconds}s`);
1340
+ console.log(` Commitment valid window: ${commitTimestamp + minimumAgeSeconds} \u2013 ${commitTimestamp + maximumAgeSeconds}`);
1313
1341
  console.log(` Commitment stored on-chain (timestamp: ${commitTimestamp})`);
1314
1342
  console.log(` Waiting for on-chain block.timestamp > ${commitTimestamp + minimumAgeSeconds}...`);
1315
1343
  const pollDeadline = Date.now() + POLL_TIMEOUT_MS;
@@ -1318,6 +1346,17 @@ var DotNS = class {
1318
1346
  const chainNowSeconds = Math.floor(Number(nowMs) / 1e3);
1319
1347
  if (isCommitmentMature(chainNowSeconds, commitTimestamp, minimumAgeSeconds)) {
1320
1348
  console.log(` Commitment age requirement met (chain.now=${chainNowSeconds}, target>${commitTimestamp + minimumAgeSeconds})`);
1349
+ console.log(` Buffering ${POLL_INTERVAL_MS / 1e3}s for block propagation (guard against node lag after maturity)...`);
1350
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
1351
+ const nowAfterBuffer = Math.floor(Number(await this.clientWrapper.client.query.Timestamp.Now.getValue()) / 1e3);
1352
+ const expiresAt = commitTimestamp + maximumAgeSeconds;
1353
+ const remainingSecs = expiresAt - nowAfterBuffer;
1354
+ if (remainingSecs <= 0) {
1355
+ throw new Error(`Commitment has expired (chain.now=${nowAfterBuffer}, expired at=${expiresAt}). A fresh commit cycle is needed.`);
1356
+ }
1357
+ if (remainingSecs < 30) {
1358
+ console.log(` Warning: commitment expires in ${remainingSecs}s \u2014 proceeding immediately.`);
1359
+ }
1321
1360
  return;
1322
1361
  }
1323
1362
  const remaining = Math.ceil((pollDeadline - Date.now()) / 1e3);
@@ -1645,13 +1684,13 @@ var DotNS = class {
1645
1684
  // canProceed:true result with an actionable canProceed:false when even the
1646
1685
  // top-up can't get the signer above the threshold.
1647
1686
  async gateOnFeeBalance(candidate, signerFreeBalance, isTestnet) {
1648
- const feeFloor = feeFloorFor(candidate.plannedAction);
1687
+ const feeFloor = feeFloorFor(candidate.plannedAction, this._registerStorageDeposit);
1649
1688
  let effectiveBalance = signerFreeBalance;
1650
1689
  let toppedUp;
1651
1690
  if (effectiveBalance < feeFloor && isTestnet) {
1652
1691
  setDeployAttribute("deploy.dotns.signer_below_floor", "true");
1653
1692
  console.log(` DotNS signer ${this.substrateAddress?.slice(0, 8)}... balance ${fmtPas(effectiveBalance)} PAS < ${fmtPas(feeFloor)} PAS floor \u2014 attempting testnet auto top-up...`);
1654
- const result = await this.attemptTestnetTopUp(this.substrateAddress, TOP_UP_TARGET);
1693
+ const result = await this.attemptTestnetTopUp(this.substrateAddress, topUpTargetFor(candidate.plannedAction, this._registerStorageDeposit));
1655
1694
  if (result) {
1656
1695
  console.log(` Topped up ${fmtPas(result.transferred)} PAS from ${result.source}`);
1657
1696
  effectiveBalance += result.transferred;
@@ -1728,11 +1767,24 @@ var DotNS = class {
1728
1767
  if (!canRegister(requiredStatus, userStatus, trailingDigitCount)) {
1729
1768
  rejectIneligible(requiredStatus, userStatus);
1730
1769
  }
1731
- const { commitment, registration } = await this.generateCommitment(label, reverse);
1732
- await withSpan("deploy.dotns.submit-commitment", "2a-i. submit-commitment", {}, () => this.submitCommitment(commitment));
1733
- await withSpan("deploy.dotns.wait-commitment-age", "2a-ii. wait-commitment-age", {}, () => this.waitForCommitmentAge(commitment));
1734
- const pricing = await withSpan("deploy.dotns.price-validation", "2a-iii. price-validation", {}, () => this.getPriceAndValidate(label));
1735
- await withSpan("deploy.dotns.finalize-registration", "2a-iv. finalize-registration", {}, () => this.finalizeRegistration(registration, pricing.priceWei));
1770
+ const doCommitAndRegister = async () => {
1771
+ const { commitment, registration } = await this.generateCommitment(label, reverse);
1772
+ await withSpan("deploy.dotns.submit-commitment", "2a-i. submit-commitment", {}, () => this.submitCommitment(commitment));
1773
+ await withSpan("deploy.dotns.wait-commitment-age", "2a-ii. wait-commitment-age", {}, () => this.waitForCommitmentAge(commitment));
1774
+ const pricing = await withSpan("deploy.dotns.price-validation", "2a-iii. price-validation", {}, () => this.getPriceAndValidate(label));
1775
+ await withSpan("deploy.dotns.finalize-registration", "2a-iv. finalize-registration", {}, () => this.finalizeRegistration(registration, pricing.priceWei));
1776
+ };
1777
+ try {
1778
+ await doCommitAndRegister();
1779
+ } catch (err) {
1780
+ const msg = err.message ?? "";
1781
+ if (!isCommitmentTimingBarerevert(msg)) throw err;
1782
+ console.log(`
1783
+ Register bare-reverted (commitment timing race \u2014 node saw a block where commitment was too new or expired).`);
1784
+ console.log(` Retrying with a fresh commitment. This usually resolves in one block.
1785
+ `);
1786
+ await doCommitAndRegister();
1787
+ }
1736
1788
  await this.verifyOwnership(label);
1737
1789
  console.log(`
1738
1790
  Registration complete!`);
@@ -1810,6 +1862,7 @@ var DotNS = class {
1810
1862
  var dotns = new DotNS();
1811
1863
 
1812
1864
  export {
1865
+ MINIMUM_REGISTER_STORAGE_DEPOSIT,
1813
1866
  fmtPas,
1814
1867
  feeFloorFor,
1815
1868
  RPC_ENDPOINTS,
@@ -1838,9 +1891,9 @@ export {
1838
1891
  sanitizeDomainLabel,
1839
1892
  validateDomainLabel,
1840
1893
  isCommitmentMature,
1894
+ isCommitmentTimingBarerevert,
1841
1895
  classifyDotnsLabel,
1842
1896
  canRegister,
1843
- simulateUserStatus,
1844
1897
  parseDomainName,
1845
1898
  parseProofOfPersonhoodStatus,
1846
1899
  popStatusName,