bulletin-deploy 0.7.22 → 0.7.23-rc.2

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.
@@ -56,6 +56,13 @@
56
56
  "autoAccountMapping": true,
57
57
  "bulletinAuthorizeV2": true,
58
58
  "nativeToEthRatio": 100000000,
59
+ "popSelfServe": {
60
+ "sudoEnvLabel": "Next V2",
61
+ "faucetUrl": "https://faucet.polkadot.io/?parachain=1500",
62
+ "personhoodFaucetUrl": "https://sudo.personhood.dev/personhood-faucet",
63
+ "dotnsBootstrapUrl": "https://sudo.personhood.dev/dotns-bootstrap",
64
+ "stateAwareGuidance": true
65
+ },
59
66
  "contracts": {
60
67
  "DOTNS_PROTOCOL_REGISTRY": "0x5Caef84563fc980178e28417414aa65bA32f6B4e",
61
68
  "DOTNS_REGISTRAR": "0x885b8085bA92A31c4ef52076f77379E647ECC399",
@@ -9,10 +9,10 @@ import {
9
9
  offerBugReport,
10
10
  scrubSecrets,
11
11
  setDeployContext
12
- } from "./chunk-LMCKLAA5.js";
13
- import "./chunk-V7KIUORT.js";
14
- import "./chunk-PNZCJPI6.js";
15
- import "./chunk-VQPLU5FL.js";
12
+ } from "./chunk-3T3AWNCO.js";
13
+ import "./chunk-2C424N5I.js";
14
+ import "./chunk-3PUOXYBI.js";
15
+ import "./chunk-SAMFZEX3.js";
16
16
  export {
17
17
  buildCliFlagsSummary,
18
18
  buildLabels,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-PNZCJPI6.js";
3
+ } from "./chunk-3PUOXYBI.js";
4
4
 
5
5
  // src/version-check.ts
6
6
  import { execSync, execFileSync } from "child_process";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  package_default,
3
3
  writeRunState
4
- } from "./chunk-VQPLU5FL.js";
4
+ } from "./chunk-SAMFZEX3.js";
5
5
 
6
6
  // src/memory-report.ts
7
7
  import * as fs2 from "fs";
@@ -212,7 +212,14 @@ function getDeployAttributes(domain) {
212
212
  "deploy.dotns.signer_below_floor": "false",
213
213
  "deploy.dotns.toppedup": "false",
214
214
  // Flipped to "true" by deploy() when options.automatedMirror is set.
215
- "deploy.automated_mirror": "false"
215
+ "deploy.automated_mirror": "false",
216
+ // Seeded "false" so every span carries the attribute for ratio queries.
217
+ // Flipped by deploy.ts storage phase when content is encrypted.
218
+ "deploy.encrypted": "false",
219
+ // Flipped by deploy.ts after parseDomainName resolves isSubdomain.
220
+ "deploy.subdomain": "false",
221
+ // Flipped by deploy.ts after readPreviousContenthashSafe when a prior CID is found.
222
+ "deploy.incremental": "false"
216
223
  };
217
224
  if (hostApp) attrs["deploy.host_app"] = hostApp;
218
225
  const hostAppVersion = process.env.BULLETIN_DEPLOY_HOST_APP_VERSION;
@@ -2,11 +2,11 @@ import {
2
2
  classifyErrorArea,
3
3
  isInteractive,
4
4
  promptYesNo
5
- } from "./chunk-V7KIUORT.js";
5
+ } from "./chunk-2C424N5I.js";
6
6
  import {
7
7
  VERSION,
8
8
  getCurrentSentryTraceId
9
- } from "./chunk-PNZCJPI6.js";
9
+ } from "./chunk-3PUOXYBI.js";
10
10
 
11
11
  // src/bug-report.ts
12
12
  import { execSync, execFileSync } from "child_process";
@@ -65,6 +65,13 @@ var environments_default = {
65
65
  autoAccountMapping: true,
66
66
  bulletinAuthorizeV2: true,
67
67
  nativeToEthRatio: 1e8,
68
+ popSelfServe: {
69
+ sudoEnvLabel: "Next V2",
70
+ faucetUrl: "https://faucet.polkadot.io/?parachain=1500",
71
+ personhoodFaucetUrl: "https://sudo.personhood.dev/personhood-faucet",
72
+ dotnsBootstrapUrl: "https://sudo.personhood.dev/dotns-bootstrap",
73
+ stateAwareGuidance: true
74
+ },
68
75
  contracts: {
69
76
  DOTNS_PROTOCOL_REGISTRY: "0x5Caef84563fc980178e28417414aa65bA32f6B4e",
70
77
  DOTNS_REGISTRAR: "0x885b8085bA92A31c4ef52076f77379E647ECC399",
@@ -319,7 +326,14 @@ var HARDCODED_FALLBACK = {
319
326
  id: "paseo-next-v2",
320
327
  name: "Paseo Next v2",
321
328
  network: "testnet",
322
- description: "Next iteration of the Paseo Next testnet (hardcoded fallback)"
329
+ description: "Next iteration of the Paseo Next testnet (hardcoded fallback)",
330
+ popSelfServe: {
331
+ sudoEnvLabel: "Next V2",
332
+ faucetUrl: "https://faucet.polkadot.io/?parachain=1500",
333
+ personhoodFaucetUrl: "https://sudo.personhood.dev/personhood-faucet",
334
+ dotnsBootstrapUrl: "https://sudo.personhood.dev/dotns-bootstrap",
335
+ stateAwareGuidance: true
336
+ }
323
337
  }
324
338
  ],
325
339
  chains: [
@@ -444,6 +458,9 @@ function resolveEndpoints(doc, envId) {
444
458
  nativeToEthRatio: BigInt(env.nativeToEthRatio ?? 1e6)
445
459
  };
446
460
  }
461
+ function getPopSelfServeConfig(doc, envId) {
462
+ return doc.environments.find((e) => e.id === envId)?.popSelfServe ?? null;
463
+ }
447
464
  function listEnvironments(doc) {
448
465
  const bulletinChain = doc.chains.find((c) => c.id === "bulletin");
449
466
  return doc.environments.map((env) => {
@@ -480,6 +497,7 @@ export {
480
497
  defaultBundledPath,
481
498
  loadEnvironments,
482
499
  resolveEndpoints,
500
+ getPopSelfServeConfig,
483
501
  listEnvironments,
484
502
  formatEnvironmentTable
485
503
  };
@@ -4,8 +4,10 @@ import {
4
4
  import {
5
5
  captureWarning,
6
6
  setDeployAttribute,
7
+ setDeploySentryTag,
8
+ truncateAddress,
7
9
  withSpan
8
- } from "./chunk-PNZCJPI6.js";
10
+ } from "./chunk-3PUOXYBI.js";
9
11
 
10
12
  // src/dotns.ts
11
13
  import crypto from "crypto";
@@ -35,11 +37,20 @@ var FEE_FLOOR_OWNED = ONE_PAS / 100n;
35
37
  var FEE_FLOOR_REGISTER = ONE_PAS / 10n;
36
38
  var TOP_UP_TARGET = ONE_PAS / 2n;
37
39
  var SOURCE_BUFFER = ONE_PAS;
40
+ var REPROVE_FEE_ESTIMATE = ONE_PAS / 100n;
41
+ var REPROVE_FEE_SAFETY_MARGIN_PCT = 110n;
38
42
  var TOP_UP_TRANSFER_TIMEOUT_MS = 6e4;
39
43
  var PASEO_FAUCET_URL = "https://faucet.polkadot.io";
40
44
  function fmtPas(plancks) {
41
45
  return (Number(plancks) / Number(ONE_PAS)).toFixed(4);
42
46
  }
47
+ function resolveNativeTokenSymbol(envId) {
48
+ if (!envId) return "PAS";
49
+ if (envId.includes("paseo")) return "PAS";
50
+ if (envId.includes("westend")) return "WND";
51
+ if (envId.includes("rococo")) return "ROC";
52
+ return "PAS";
53
+ }
43
54
  function feeFloorFor(plannedAction) {
44
55
  return plannedAction === "already-owned-by-us" ? FEE_FLOOR_OWNED : FEE_FLOOR_REGISTER;
45
56
  }
@@ -258,12 +269,12 @@ function formatContractDryRunFailure(gasEstimate, context) {
258
269
  const isBareRevert = (revertData === void 0 || revertData.trim() === "0x") && gasEstimate.revertFlags === 1n;
259
270
  if (isBareRevert && BARE_REVERT_DIAGNOSTIC_FUNCTIONS.has(functionName)) {
260
271
  lines.push(
261
- ` diagnostic: bare-revert (empty 0x). Most common causes:`,
262
- ` 1. Origin not actually mapped on-chain. Try BULLETIN_DEPLOY_DEBUG_DOTNS_TX=1 to log calldata, then run`,
263
- ` \`node tools/dotns-dry-run.mjs <label>\` against the env's Asset Hub RPC to reproduce in isolation.`,
264
- ` 2. Contract precondition (e.g. commitment too new). dotns-dry-run.mjs will surface the contract-side state.`,
265
- ` hint: prepend --fresh to dotns-dry-run.mjs to use a brand-new unmapped substrate origin \u2014`,
266
- ` if the revert ONLY reproduces with --fresh, it's the account-mapping bug.`
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.`
267
278
  );
268
279
  }
269
280
  return lines.join("\n");
@@ -636,21 +647,45 @@ var ReviveClientWrapper = class _ReviveClientWrapper {
636
647
  }
637
648
  };
638
649
  var DOTNS_CONTEXT_HEX_LOWER = "0x646f746e73000000000000000000000000000000000000000000000000000000";
639
- function formatPersonhoodRemediation(state, environmentId) {
640
- if (environmentId !== "paseo-next-v2") {
650
+ function formatPersonhoodRemediation(state, popSelfServe, environmentId) {
651
+ if (!popSelfServe?.stateAwareGuidance) {
641
652
  return "Self-attestation is no longer available. Contact the DotNS team for whitelisting / Personhood status help.";
642
653
  }
643
654
  switch (state.state) {
644
655
  case "not-bound":
645
- return "Your account has no DotNS alias binding on paseo-next-v2. Visit https://sudo.personhood.dev/dotns-bootstrap to complete the recognition and binding steps, or run `tools/personhood-bootstrap.mjs --mnemonic <your-mnemonic>` once Stage 2 of the bootstrap tooling lands.";
656
+ return `Your account has no DotNS alias binding on ${environmentId ?? "this environment"}. Visit ${popSelfServe.dotnsBootstrapUrl} to complete the recognition and binding steps, or run \`tools/personhood-bootstrap.mjs --mnemonic <your-mnemonic>\` once Stage 2 of the bootstrap tooling lands.`;
646
657
  case "bound-likely-stale":
647
- return "Your alias binding exists but may have a stale ring revision. Run `node tools/reprove-alias.mjs --mnemonic <your-mnemonic> --env paseo-next-v2` to refresh the proof, then retry the registration.";
658
+ return `Your alias binding exists but may have a stale ring revision. Run \`node tools/reprove-alias.mjs --mnemonic <your-mnemonic> --env ${environmentId ?? "this environment"}\` to refresh the proof, then retry the registration.`;
648
659
  case "wrong-context":
649
- return "Your alias binding exists but is for a different application context" + (state.storedContextHex ? ` (context: ${state.storedContextHex})` : "") + ". Re-bind under the 'dotns' context using the bootstrap flow: https://sudo.personhood.dev/dotns-bootstrap";
660
+ return "Your alias binding exists but is for a different application context" + (state.storedContextHex ? ` (context: ${state.storedContextHex})` : "") + ". Re-bind under the 'dotns' context using the bootstrap flow: " + popSelfServe.dotnsBootstrapUrl;
650
661
  case "bound-fresh":
651
662
  return "Self-attestation is no longer available. Contact the DotNS team for whitelisting / Personhood status help.";
652
663
  }
653
664
  }
665
+ function formatPopShortfallReason(opts) {
666
+ const { label, requiredName, currentName, isTestnet, environmentId, popSelfServe, aliasState, exampleNoStatusLabel: noStatusEx } = opts;
667
+ const leadIn = `${label}.dot requires ${requiredName}, but this signer is ${currentName}.`;
668
+ let testnetBlock = "";
669
+ if (isTestnet && popSelfServe != null) {
670
+ if (popSelfServe.stateAwareGuidance) {
671
+ const state = aliasState ?? { state: "not-bound" };
672
+ testnetBlock = "\n\n" + formatPersonhoodRemediation(state, popSelfServe, environmentId);
673
+ } else {
674
+ testnetBlock = `
675
+
676
+ On testnets you can self-serve:
677
+ 1. Fund the service account mnemonic via ${popSelfServe.faucetUrl}
678
+ 2. Go to ${popSelfServe.personhoodFaucetUrl}, pick your env (e.g. ${popSelfServe.sudoEnvLabel}), and paste the mnemonic
679
+ 3. Go to ${popSelfServe.dotnsBootstrapUrl} and follow each step (first and last can probably be skipped)`;
680
+ }
681
+ }
682
+ const alternativesBlock = `
683
+
684
+ Alternatively:
685
+ - Use a NoStatus-compatible label (base length >= 9 with exactly two trailing digits, e.g. ${noStatusEx})
686
+ - Raise a whitelist issue at https://github.com/paritytech/dotns/`;
687
+ return leadIn + testnetBlock + alternativesBlock;
688
+ }
654
689
  var DotNS = class {
655
690
  client;
656
691
  clientWrapper;
@@ -664,9 +699,24 @@ var DotNS = class {
664
699
  // back to the legacy paseo-only RPC_ENDPOINTS for direct library callers.
665
700
  assetHubEndpoints;
666
701
  _usesExternalSigner = false;
702
+ _localMnemonic = null;
667
703
  _contracts = CONTRACTS;
668
704
  _nativeToEthRatio = NATIVE_TO_ETH_RATIO;
669
705
  _environmentId = null;
706
+ _popSelfServe = null;
707
+ // Test-only seam: consumed once by classifyAliasAccountState() then cleared.
708
+ // Mirrors the __setDeployRootSpanForTest / __setSentryForTest pattern.
709
+ _classifyOverrideForTest = null;
710
+ /** Test-only: inject a fixed classifyAliasAccountState return value for the next call. Consumed once. */
711
+ __setClassifyOverrideForTest(state) {
712
+ this._classifyOverrideForTest = { state, revision: 0 };
713
+ }
714
+ // Test-only seam: consumed once by getUserPopStatus() then cleared.
715
+ _userPopStatusOverrideForTest = null;
716
+ /** Test-only: inject a fixed getUserPopStatus return value for the next call. Consumed once. */
717
+ __setUserPopStatusForTest(status) {
718
+ this._userPopStatusOverrideForTest = status;
719
+ }
670
720
  constructor() {
671
721
  this.client = null;
672
722
  this.clientWrapper = null;
@@ -687,6 +737,9 @@ var DotNS = class {
687
737
  if (options.environmentId) {
688
738
  this._environmentId = options.environmentId;
689
739
  }
740
+ if (options.popSelfServe !== void 0) {
741
+ this._popSelfServe = options.popSelfServe ?? null;
742
+ }
690
743
  const rpc = options.rpc || process.env.DOTNS_RPC || this.assetHubEndpoints[0];
691
744
  this.rpc = rpc;
692
745
  this._usesExternalSigner = Boolean(options.signer && options.signerAddress);
@@ -698,6 +751,9 @@ var DotNS = class {
698
751
  const keyUriArg = options.keyUri || process.env.DOTNS_KEY_URI;
699
752
  let source = keyUriArg || mnemonicArg || DEFAULT_MNEMONIC;
700
753
  const isKeyUri = Boolean(keyUriArg);
754
+ if (!isKeyUri && !options.derivationPath) {
755
+ this._localMnemonic = mnemonicArg || DEFAULT_MNEMONIC;
756
+ }
701
757
  if (options.derivationPath && !isKeyUri && source) {
702
758
  source = `${source}${options.derivationPath}`;
703
759
  }
@@ -708,28 +764,34 @@ var DotNS = class {
708
764
  this.substrateAddress = account.address;
709
765
  }
710
766
  console.log(` SS58 Address: ${this.substrateAddress}`);
711
- try {
712
- this.client = createClient(getWsProvider(rpc, { heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS }));
713
- const unsafeApi = this.client.getUnsafeApi();
714
- this.clientWrapper = new ReviveClientWrapper(unsafeApi);
715
- this.evmAddress = await withTimeout(
716
- this.clientWrapper.getEvmAddress(this.substrateAddress),
717
- CONNECTION_TIMEOUT_MS,
718
- "ReviveApi.address"
719
- );
720
- console.log(` H160 Address: ${this.evmAddress}`);
721
- } catch (e) {
722
- throw new Error(`DotNS connect: failed to resolve EVM address from ${this.substrateAddress} via ReviveApi.address (${e.message?.slice(0, 200)})`);
723
- }
724
- this.connected = true;
725
- if (options.nativeToEthRatio) this._nativeToEthRatio = options.nativeToEthRatio;
726
- try {
727
- await this.ensureMappedAccountReady(options.autoAccountMapping ?? false);
728
- } catch (e) {
729
- this.connected = false;
730
- throw e;
731
- }
732
- return this;
767
+ setDeployAttribute("deploy.dotns.signer", truncateAddress(this.substrateAddress));
768
+ setDeploySentryTag("deploy.dotns.signer", truncateAddress(this.substrateAddress));
769
+ return withSpan("deploy.dotns.connect", "dotns connect", {}, async () => {
770
+ try {
771
+ this.client = createClient(getWsProvider(rpc, { heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS }));
772
+ const unsafeApi = this.client.getUnsafeApi();
773
+ this.clientWrapper = new ReviveClientWrapper(unsafeApi);
774
+ this.evmAddress = await withTimeout(
775
+ this.clientWrapper.getEvmAddress(this.substrateAddress),
776
+ CONNECTION_TIMEOUT_MS,
777
+ "ReviveApi.address"
778
+ );
779
+ console.log(` H160 Address: ${this.evmAddress}`);
780
+ } catch (e) {
781
+ throw new Error(`DotNS connect: failed to resolve EVM address from ${this.substrateAddress} via ReviveApi.address (${e.message?.slice(0, 200)})`);
782
+ }
783
+ setDeployAttribute("deploy.dotns.rpc_used", rpc);
784
+ setDeployAttribute("deploy.dotns.evm_address", this.evmAddress);
785
+ this.connected = true;
786
+ if (options.nativeToEthRatio) this._nativeToEthRatio = options.nativeToEthRatio;
787
+ try {
788
+ await this.ensureMappedAccountReady(options.autoAccountMapping ?? false);
789
+ } catch (e) {
790
+ this.connected = false;
791
+ throw e;
792
+ }
793
+ return this;
794
+ });
733
795
  }
734
796
  async ensureMappedAccountReady(autoAccountMapping = false) {
735
797
  this.ensureConnected();
@@ -737,10 +799,12 @@ var DotNS = class {
737
799
  throw new Error("Account mapping unavailable before DotNS signer is initialized");
738
800
  }
739
801
  if (autoAccountMapping) {
802
+ setDeployAttribute("deploy.dotns.mapping_source", "auto-account-mapping");
740
803
  await this.ensureAutoMappedAccountReady();
741
804
  return;
742
805
  }
743
806
  if (await this.clientWrapper.checkIfAccountMapped(this.substrateAddress)) {
807
+ setDeployAttribute("deploy.dotns.mapping_source", "already-mapped");
744
808
  console.log(` Account: mapped`);
745
809
  return;
746
810
  }
@@ -749,6 +813,7 @@ var DotNS = class {
749
813
  await this.clientWrapper.ensureAccountMapped(this.substrateAddress, this.signer);
750
814
  } catch (e) {
751
815
  if (await this.clientWrapper.checkIfAccountMapped(this.substrateAddress)) {
816
+ setDeployAttribute("deploy.dotns.mapping_source", "direct-mapped");
752
817
  console.log(` Account: mapped`);
753
818
  return;
754
819
  }
@@ -756,9 +821,11 @@ var DotNS = class {
756
821
  signer: this.substrateAddress,
757
822
  error: e?.message?.slice?.(0, 200) ?? String(e).slice(0, 200)
758
823
  });
824
+ setDeployAttribute("deploy.dotns.mapping_source", "auto-map-fallback");
759
825
  await this.ensureAutoMappedAccountReady();
760
826
  return;
761
827
  }
828
+ setDeployAttribute("deploy.dotns.mapping_source", "direct-mapped");
762
829
  console.log(` Account: mapped`);
763
830
  }
764
831
  async ensureAutoMappedAccountReady() {
@@ -841,6 +908,11 @@ var DotNS = class {
841
908
  * Returns "not-bound" if the chain is unreachable (safe fallback to generic advice).
842
909
  */
843
910
  async classifyAliasAccountState(ss58) {
911
+ if (this._classifyOverrideForTest !== null) {
912
+ const result = this._classifyOverrideForTest;
913
+ this._classifyOverrideForTest = null;
914
+ return result;
915
+ }
844
916
  if (!this.clientWrapper) return { state: "not-bound" };
845
917
  try {
846
918
  const api = this.clientWrapper.client;
@@ -1034,6 +1106,11 @@ var DotNS = class {
1034
1106
  }
1035
1107
  }
1036
1108
  async getUserPopStatus(ownerAddress = null) {
1109
+ if (this._userPopStatusOverrideForTest !== null) {
1110
+ const result = this._userPopStatusOverrideForTest;
1111
+ this._userPopStatusOverrideForTest = null;
1112
+ return result;
1113
+ }
1037
1114
  this.ensureConnected();
1038
1115
  const checkAddress = ownerAddress || this.evmAddress;
1039
1116
  try {
@@ -1264,7 +1341,12 @@ var DotNS = class {
1264
1341
  }
1265
1342
  }
1266
1343
  const priceMeta = await withTimeout(this.contractCall(this._contracts.POP_RULES, POP_RULES_ABI, "priceWithCheck", [label, this.evmAddress]), 3e4, "priceWithCheck");
1267
- const priceRaw = priceMeta.price ?? (Array.isArray(priceMeta) ? priceMeta[0] : 0n);
1344
+ const priceRaw = priceMeta?.price;
1345
+ if (priceRaw == null) {
1346
+ throw new Error(
1347
+ `priceWithCheck returned unexpected shape (expected object with .price): ` + JSON.stringify(priceMeta, (_, v) => typeof v === "bigint" ? v.toString() : v)
1348
+ );
1349
+ }
1268
1350
  const priceWei = typeof priceRaw === "bigint" ? priceRaw : BigInt(priceRaw);
1269
1351
  console.log(` Required status: ${popStatusName(requiredStatus)}`);
1270
1352
  console.log(` User status: ${popStatusName(userStatus)}`);
@@ -1277,6 +1359,12 @@ var DotNS = class {
1277
1359
  Finalizing registration for ${registration.label}.dot...`);
1278
1360
  const bufferedPaymentWei = priceWei * 110n / 100n;
1279
1361
  const bufferedPaymentNative = bufferedPaymentWei / this._nativeToEthRatio;
1362
+ if (priceWei > 0n && bufferedPaymentNative === 0n) {
1363
+ throw new Error(
1364
+ `Payment conversion underflow: priceWei=${priceWei} rounds to 0 native units (nativeToEthRatio=${this._nativeToEthRatio}). Cannot call register with zero payment.`
1365
+ );
1366
+ }
1367
+ setDeployAttribute("deploy.payment_wei", priceWei.toString());
1280
1368
  console.log(` Oracle price: ${formatEther(priceWei)} PAS`);
1281
1369
  console.log(` Paying: ${formatEther(bufferedPaymentWei)} PAS`);
1282
1370
  const txHash = await this.contractTransaction(this._contracts.DOTNS_REGISTRAR_CONTROLLER, bufferedPaymentNative, DOTNS_REGISTRAR_CONTROLLER_ABI, "register", [registration], (s) => console.log(` ${s}`));
@@ -1299,7 +1387,11 @@ var DotNS = class {
1299
1387
  // `register(label)` will succeed, so the caller can fail-fast BEFORE the
1300
1388
  // Bulletin chunk upload. Never writes to chain. See issue #100.
1301
1389
  async preflight(label) {
1390
+ return this._preflightInternal(label, false);
1391
+ }
1392
+ async _preflightInternal(label, reproveAttempted) {
1302
1393
  return withSpan("deploy.dotns.preflight", `preflight ${label}.dot`, {}, async () => {
1394
+ setDeployAttribute("deploy.dotns.reprove.auto", "false");
1303
1395
  this.ensureConnected();
1304
1396
  const validated = validateDomainLabel(label);
1305
1397
  const trailingDigits = countTrailingDigits(validated);
@@ -1414,9 +1506,59 @@ var DotNS = class {
1414
1506
  signerFreeBalance
1415
1507
  };
1416
1508
  }
1417
- if (userStatus === ProofOfPersonhoodStatus.NoStatus && isTestnet && this._environmentId === "paseo-next-v2" && this.substrateAddress) {
1509
+ if (userStatus === ProofOfPersonhoodStatus.NoStatus && isTestnet && this._popSelfServe?.stateAwareGuidance === true && this.substrateAddress) {
1418
1510
  const aliasState = await this.classifyAliasAccountState(this.substrateAddress);
1419
- const remediationMessage = formatPersonhoodRemediation(aliasState, this._environmentId);
1511
+ if (aliasState.state === "bound-likely-stale" && !this._usesExternalSigner && this._localMnemonic && !reproveAttempted) {
1512
+ const minBalance = REPROVE_FEE_ESTIMATE * REPROVE_FEE_SAFETY_MARGIN_PCT / 100n;
1513
+ const symbol = resolveNativeTokenSymbol(this._environmentId);
1514
+ if (signerFreeBalance < minBalance) {
1515
+ setDeployAttribute("deploy.dotns.reprove.auto", "true");
1516
+ setDeployAttribute("deploy.dotns.reprove.outcome", "insufficient_funds");
1517
+ return {
1518
+ label: validated,
1519
+ classification,
1520
+ userStatus,
1521
+ trailingDigits,
1522
+ baselength,
1523
+ isAvailable: true,
1524
+ existingOwner: null,
1525
+ isBaseNameReserved: isReserved,
1526
+ reservationOwner,
1527
+ isTestnet,
1528
+ canProceed: false,
1529
+ reason: `Cannot auto-refresh: signer balance ${fmtPas(signerFreeBalance)} ${symbol} < estimated fee ${fmtPas(REPROVE_FEE_ESTIMATE)} ${symbol}. Top up via the testnet faucet or run \`tools/reprove-alias.mjs --mnemonic <your-mnemonic> --env ${this._environmentId}\` manually.`,
1530
+ plannedAction: "abort",
1531
+ needsPopUpgrade: false,
1532
+ targetPopStatus,
1533
+ signerFreeBalance
1534
+ };
1535
+ }
1536
+ console.log(`
1537
+ [stage] Personhood preflight \u2014 auto-refresh`);
1538
+ console.log(` Alias binding exists but ring revision is stale (stored=${aliasState.revision ?? "unknown"}).`);
1539
+ console.log(` Refreshing automatically on testnet\u2026`);
1540
+ console.log(` Estimated reprove fee: ${fmtPas(REPROVE_FEE_ESTIMATE)} ${symbol}. Signer balance: ${fmtPas(signerFreeBalance)} ${symbol} \u2014 proceeding.`);
1541
+ setDeployAttribute("deploy.dotns.reprove.auto", "true");
1542
+ let reproveSucceeded = false;
1543
+ try {
1544
+ console.log(` Submitting reprove_alias_account\u2026`);
1545
+ const reproveResult = await this.reprove(this._localMnemonic);
1546
+ console.log(` Refresh complete (old_revision=${reproveResult.oldRevision}, new_revision=${reproveResult.newRevision}, block=${reproveResult.blockHash}).`);
1547
+ setDeployAttribute("deploy.dotns.reprove.outcome", "success");
1548
+ setDeployAttribute("deploy.dotns.reprove.old_revision", String(reproveResult.oldRevision));
1549
+ setDeployAttribute("deploy.dotns.reprove.new_revision", String(reproveResult.newRevision));
1550
+ reproveSucceeded = true;
1551
+ } catch (e) {
1552
+ const msg = e?.message ?? String(e);
1553
+ console.log(` Auto-reprove failed: ${msg}`);
1554
+ setDeployAttribute("deploy.dotns.reprove.outcome", "failed_submission");
1555
+ }
1556
+ if (reproveSucceeded) {
1557
+ console.log(` Continuing with registration of ${validated}.dot.`);
1558
+ return this._preflightInternal(label, true);
1559
+ }
1560
+ }
1561
+ const remediationMessage = formatPersonhoodRemediation(aliasState, this._popSelfServe, this._environmentId);
1420
1562
  const currentName2 = popStatusName(userStatus);
1421
1563
  const requiredName2 = popStatusName(classification.status);
1422
1564
  return {
@@ -1452,7 +1594,16 @@ var DotNS = class {
1452
1594
  reservationOwner,
1453
1595
  isTestnet,
1454
1596
  canProceed: false,
1455
- reason: `${validated}.dot requires ${requiredName}, but this signer is ${currentName}. Self-attestation is no longer available. Choose a NoStatus-compatible name (base length >= 9 with exactly two trailing digits, for example ${exampleNoStatusLabel(validated)}) or contact the DotNS team for whitelisting / Personhood status.`,
1597
+ reason: formatPopShortfallReason({
1598
+ label: validated,
1599
+ requiredName,
1600
+ currentName,
1601
+ isTestnet,
1602
+ environmentId: this._environmentId,
1603
+ popSelfServe: this._popSelfServe,
1604
+ aliasState: null,
1605
+ exampleNoStatusLabel: exampleNoStatusLabel(validated)
1606
+ }),
1456
1607
  plannedAction: "abort",
1457
1608
  needsPopUpgrade: false,
1458
1609
  targetPopStatus,
@@ -1533,6 +1684,8 @@ var DotNS = class {
1533
1684
  if (preRequiredStatus === ProofOfPersonhoodStatus.Reserved) {
1534
1685
  throw new Error(preClassification.message);
1535
1686
  }
1687
+ const isTestnet = await this.isTestnet();
1688
+ const registerAliasState = isTestnet && this._popSelfServe?.stateAwareGuidance === true && this.substrateAddress ? await this.classifyAliasAccountState(this.substrateAddress) : null;
1536
1689
  const rejectIneligible = (statusRequired, userStatus2) => {
1537
1690
  if (statusRequired === ProofOfPersonhoodStatus.NoStatus && userStatus2 === ProofOfPersonhoodStatus.ProofOfPersonhoodLite) {
1538
1691
  throw new Error(
@@ -1540,7 +1693,16 @@ var DotNS = class {
1540
1693
  );
1541
1694
  }
1542
1695
  throw new Error(
1543
- `${label}.dot requires ${popStatusName(statusRequired)}, but this signer is ${popStatusName(userStatus2)}. Self-attestation is no longer available. Choose a NoStatus-compatible name (base length >= 9 with exactly two trailing digits, for example ${exampleNoStatusLabel(label)}) or contact the DotNS team for whitelisting / Personhood status.`
1696
+ formatPopShortfallReason({
1697
+ label,
1698
+ requiredName: popStatusName(statusRequired),
1699
+ currentName: popStatusName(userStatus2),
1700
+ isTestnet,
1701
+ environmentId: this._environmentId,
1702
+ popSelfServe: this._popSelfServe,
1703
+ aliasState: registerAliasState,
1704
+ exampleNoStatusLabel: exampleNoStatusLabel(label)
1705
+ })
1544
1706
  );
1545
1707
  };
1546
1708
  const reverse = options.reverse ?? (process.env.DOTNS_REVERSE ?? "false").toLowerCase() === "true";
@@ -1666,6 +1828,7 @@ export {
1666
1828
  parseProofOfPersonhoodStatus,
1667
1829
  popStatusName,
1668
1830
  formatPersonhoodRemediation,
1831
+ formatPopShortfallReason,
1669
1832
  DotNS,
1670
1833
  dotns
1671
1834
  };
@@ -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.22",
9
+ version: "0.7.23-rc.2",
10
10
  private: false,
11
11
  repository: {
12
12
  type: "git",
@@ -27,6 +27,14 @@ var package_default = {
27
27
  ".": {
28
28
  types: "./dist/index.d.ts",
29
29
  import: "./dist/index.js"
30
+ },
31
+ "./deploy": {
32
+ types: "./dist/deploy.d.ts",
33
+ import: "./dist/deploy.js"
34
+ },
35
+ "./manifest-roundtrip": {
36
+ types: "./dist/manifest-roundtrip.d.ts",
37
+ import: "./dist/manifest-roundtrip.js"
30
38
  }
31
39
  },
32
40
  files: [
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  captureWarning
3
- } from "./chunk-PNZCJPI6.js";
3
+ } from "./chunk-3PUOXYBI.js";
4
4
 
5
5
  // src/chunk-probe.ts
6
6
  import { Twox128, Blake2128Concat, decAnyMetadata, unifyMetadata } from "@polkadot-api/substrate-bindings";