bulletin-deploy 0.13.2 → 0.14.0-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.
- package/dist/auth-config.js +3 -3
- package/dist/bug-report.js +4 -4
- package/dist/{chunk-YKWQMXWJ.js → chunk-447MJ2ZY.js} +64 -23
- package/dist/{chunk-7PDAOD6W.js → chunk-4IPJMCP5.js} +48 -2
- package/dist/{chunk-RVSHGN74.js → chunk-7XC2U7R4.js} +1 -1
- package/dist/{chunk-64FAP55R.js → chunk-BB5EMKB5.js} +1 -1
- package/dist/{chunk-2E2KHTF2.js → chunk-KMDZ3ZV6.js} +1 -1
- package/dist/{chunk-KQOQN5CN.js → chunk-S2NLQSLC.js} +1 -1
- package/dist/{chunk-3QQ3KC6Q.js → chunk-URSX5FBI.js} +200 -70
- package/dist/{chunk-HAFE24QD.js → chunk-VDH5EBI2.js} +3 -3
- package/dist/{chunk-TLANVKWT.js → chunk-VWCSL736.js} +2 -2
- package/dist/{chunk-SRCUUO5A.js → chunk-WVDMUB6R.js} +21 -8
- package/dist/{chunk-RRBDBTOF.js → chunk-ZPJAJJBD.js} +1 -1
- package/dist/chunk-probe.js +3 -3
- package/dist/commands/login.js +10 -10
- package/dist/commands/logout.js +4 -4
- package/dist/commands/transfer.js +3 -3
- package/dist/commands/whoami.js +3 -3
- package/dist/deploy-actors.js +5 -5
- package/dist/deploy.d.ts +13 -1
- package/dist/deploy.js +12 -10
- package/dist/dotns.d.ts +35 -4
- package/dist/dotns.js +11 -3
- package/dist/index.js +11 -11
- package/dist/manifest/publish.d.ts +41 -1
- package/dist/manifest/publish.js +15 -13
- package/dist/memory-report.js +2 -2
- package/dist/merkle.js +10 -10
- package/dist/personhood/bootstrap.js +3 -3
- package/dist/personhood/people-client.js +3 -3
- package/dist/run-state.js +1 -1
- package/dist/sss-allowance-cache.js +4 -4
- package/dist/storage-signer.js +10 -10
- package/dist/telemetry.d.ts +3 -2
- package/dist/telemetry.js +4 -2
- package/dist/version-check.js +3 -3
- package/package.json +1 -1
package/dist/auth-config.js
CHANGED
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
getPeopleChainEndpoints,
|
|
10
10
|
hasPersistedSession,
|
|
11
11
|
resolveBulletinEndpoints
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-7XC2U7R4.js";
|
|
13
13
|
import "./chunk-TSPERKUS.js";
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-4IPJMCP5.js";
|
|
15
|
+
import "./chunk-S2NLQSLC.js";
|
|
16
16
|
import "./chunk-3ZEMX4WD.js";
|
|
17
17
|
import "./chunk-ZOC4GITL.js";
|
|
18
18
|
export {
|
package/dist/bug-report.js
CHANGED
|
@@ -10,10 +10,10 @@ import {
|
|
|
10
10
|
offerBugReport,
|
|
11
11
|
scrubSecrets,
|
|
12
12
|
setDeployContext
|
|
13
|
-
} from "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
13
|
+
} from "./chunk-VDH5EBI2.js";
|
|
14
|
+
import "./chunk-ZPJAJJBD.js";
|
|
15
|
+
import "./chunk-4IPJMCP5.js";
|
|
16
|
+
import "./chunk-S2NLQSLC.js";
|
|
17
17
|
export {
|
|
18
18
|
buildCliFlagsSummary,
|
|
19
19
|
buildLabels,
|
|
@@ -9,10 +9,16 @@ import {
|
|
|
9
9
|
setBulletinEndpoints,
|
|
10
10
|
storeDirectory,
|
|
11
11
|
storeFile
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-WVDMUB6R.js";
|
|
13
13
|
import {
|
|
14
|
-
DotNS
|
|
15
|
-
|
|
14
|
+
DotNS,
|
|
15
|
+
TX_KIND_SKIPPED
|
|
16
|
+
} from "./chunk-URSX5FBI.js";
|
|
17
|
+
import {
|
|
18
|
+
setDeployAttribute,
|
|
19
|
+
withManifestSpan,
|
|
20
|
+
withSpan
|
|
21
|
+
} from "./chunk-4IPJMCP5.js";
|
|
16
22
|
import {
|
|
17
23
|
DEFAULT_ENV_ID,
|
|
18
24
|
getPopSelfServeConfig,
|
|
@@ -40,19 +46,29 @@ async function publishManifest(opts) {
|
|
|
40
46
|
`Manifest size preflight failed: ${failing}. Shrink displayName / description / paths or override BULLETIN_TEXT_BUDGET.`
|
|
41
47
|
);
|
|
42
48
|
}
|
|
49
|
+
return withManifestSpan(config.domain, () => publishManifestBody(opts, config, sourcePath));
|
|
50
|
+
}
|
|
51
|
+
async function publishManifestBody(opts, config, sourcePath) {
|
|
43
52
|
const configDir = path.dirname(sourcePath);
|
|
44
53
|
const envId = opts.env ?? DEFAULT_ENV_ID;
|
|
45
54
|
const { doc } = await loadEnvironments();
|
|
46
55
|
const resolved = resolveEndpoints(doc, envId);
|
|
47
56
|
const popSelfServe = getPopSelfServeConfig(doc, envId);
|
|
48
57
|
setBulletinEndpoints(resolveBulletinEndpoints(resolved.bulletin, opts.rpc));
|
|
58
|
+
setDeployAttribute("deploy.manifest.executable_count", String(config.executables.length));
|
|
49
59
|
const iconAbs = path.resolve(configDir, config.icon.path);
|
|
50
60
|
const iconBytes = await readFileOrThrow(iconAbs, "icon");
|
|
51
61
|
console.log(`
|
|
52
62
|
Manifest publish \u2014 ${config.domain}`);
|
|
53
63
|
console.log(` Loaded config: ${sourcePath}`);
|
|
54
64
|
console.log(` Uploading icon (${iconBytes.length} B)\u2026`);
|
|
55
|
-
|
|
65
|
+
setDeployAttribute("deploy.manifest.icon_bytes", String(iconBytes.length));
|
|
66
|
+
const iconCid = await withSpan(
|
|
67
|
+
"deploy.manifest.icon-upload",
|
|
68
|
+
"manifest icon upload",
|
|
69
|
+
{ "deploy.manifest.icon_bytes": String(iconBytes.length) },
|
|
70
|
+
() => storeFile(iconBytes, { hashCode: BLAKE2B_256_MULTIHASH_CODE })
|
|
71
|
+
);
|
|
56
72
|
console.log(` Icon CID: ${iconCid}`);
|
|
57
73
|
const executableCids = {};
|
|
58
74
|
for (const exec of config.executables) {
|
|
@@ -74,37 +90,61 @@ Manifest publish \u2014 ${config.domain}`);
|
|
|
74
90
|
const rootManifest = composeRoot(config, iconCid);
|
|
75
91
|
const rootJson = JSON.stringify(rootManifest);
|
|
76
92
|
console.log(` Writing root manifest text record on ${config.domain} (${Buffer.byteLength(rootJson, "utf8")} B)\u2026`);
|
|
77
|
-
await dotns.setTextRecord(baseLabel, "manifest", rootJson);
|
|
93
|
+
const rootTextRes = await dotns.setTextRecord(baseLabel, "manifest", rootJson);
|
|
94
|
+
let recordsWritten = 0;
|
|
95
|
+
let recordsSkipped = 0;
|
|
96
|
+
let subnamesRegistered = 0;
|
|
97
|
+
if (rootTextRes.txHash === TX_KIND_SKIPPED) recordsSkipped++;
|
|
98
|
+
else recordsWritten++;
|
|
78
99
|
let textRecordsWritten = 1;
|
|
79
100
|
for (const exec of config.executables) {
|
|
80
101
|
const cid = executableCids[exec.kind];
|
|
81
102
|
if (!cid) throw new NonRetryableError(`Internal: missing CID for executable kind '${exec.kind}'`);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
);
|
|
103
|
+
await withSpan(
|
|
104
|
+
"deploy.manifest.executable",
|
|
105
|
+
`manifest executable ${exec.kind}`,
|
|
106
|
+
{ "deploy.manifest.executable_kind": exec.kind },
|
|
107
|
+
async () => {
|
|
108
|
+
const ownership = await dotns.checkSubdomainOwnership(exec.kind, baseLabel);
|
|
109
|
+
const { registered } = await registerOrEnsureResolver(dotns, ownership, exec.kind, baseLabel, config.domain);
|
|
110
|
+
if (registered) subnamesRegistered++;
|
|
111
|
+
const subContenthash = `0x${encodeContenthash(cid)}`;
|
|
112
|
+
console.log(` Setting contenthash on ${exec.kind}.${config.domain} \u2192 ${cid}\u2026`);
|
|
113
|
+
const contenthashRes = await dotns.setContenthash(`${exec.kind}.${baseLabel}`, subContenthash);
|
|
114
|
+
if (contenthashRes.skipped) recordsSkipped++;
|
|
115
|
+
const execManifest = composeExecutable(exec);
|
|
116
|
+
const execJson = JSON.stringify(execManifest);
|
|
117
|
+
console.log(` Writing executable manifest on ${exec.kind}.${config.domain} (${Buffer.byteLength(execJson, "utf8")} B)\u2026`);
|
|
118
|
+
const execTextRes = await dotns.setTextRecord(`${exec.kind}.${baseLabel}`, "executable", execJson);
|
|
119
|
+
if (execTextRes.txHash === TX_KIND_SKIPPED) recordsSkipped++;
|
|
120
|
+
else recordsWritten++;
|
|
88
121
|
}
|
|
89
|
-
|
|
90
|
-
await dotns.registerSubdomain(exec.kind, baseLabel);
|
|
91
|
-
}
|
|
92
|
-
await dotns.ensureContentResolver(`${exec.kind}.${baseLabel}`);
|
|
93
|
-
const subContenthash = `0x${encodeContenthash(cid)}`;
|
|
94
|
-
console.log(` Setting contenthash on ${exec.kind}.${config.domain} \u2192 ${cid}\u2026`);
|
|
95
|
-
await dotns.setContenthash(`${exec.kind}.${baseLabel}`, subContenthash);
|
|
96
|
-
const execManifest = composeExecutable(exec);
|
|
97
|
-
const execJson = JSON.stringify(execManifest);
|
|
98
|
-
console.log(` Writing executable manifest on ${exec.kind}.${config.domain} (${Buffer.byteLength(execJson, "utf8")} B)\u2026`);
|
|
99
|
-
await dotns.setTextRecord(`${exec.kind}.${baseLabel}`, "executable", execJson);
|
|
122
|
+
);
|
|
100
123
|
textRecordsWritten++;
|
|
101
124
|
}
|
|
125
|
+
setDeployAttribute("deploy.manifest.text_records_written", String(recordsWritten));
|
|
126
|
+
setDeployAttribute("deploy.manifest.text_records_skipped", String(recordsSkipped));
|
|
127
|
+
setDeployAttribute("deploy.manifest.subnames_registered", String(subnamesRegistered));
|
|
102
128
|
console.log(` \u2713 ${textRecordsWritten} text record${textRecordsWritten === 1 ? "" : "s"} written.`);
|
|
103
129
|
return { iconCid, executableCids, textRecordsWritten };
|
|
104
130
|
} finally {
|
|
105
131
|
dotns.disconnect();
|
|
106
132
|
}
|
|
107
133
|
}
|
|
134
|
+
async function registerOrEnsureResolver(dotns, ownership, execKind, baseLabel, domain) {
|
|
135
|
+
if (!ownership.owned) {
|
|
136
|
+
if (ownership.owner) {
|
|
137
|
+
throw new NonRetryableError(
|
|
138
|
+
`Subname ${execKind}.${domain} is owned by ${ownership.owner}, not the publisher. Aborting.`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
console.log(` Registering subname ${execKind}.${domain}\u2026`);
|
|
142
|
+
await dotns.registerSubdomain(execKind, baseLabel);
|
|
143
|
+
return { registered: true };
|
|
144
|
+
}
|
|
145
|
+
await dotns.ensureContentResolver(`${execKind}.${baseLabel}`);
|
|
146
|
+
return { registered: false };
|
|
147
|
+
}
|
|
108
148
|
async function readFileOrThrow(p, label) {
|
|
109
149
|
try {
|
|
110
150
|
return await fs.readFile(p);
|
|
@@ -165,5 +205,6 @@ function stripDotSuffix(domain) {
|
|
|
165
205
|
}
|
|
166
206
|
|
|
167
207
|
export {
|
|
168
|
-
publishManifest
|
|
208
|
+
publishManifest,
|
|
209
|
+
registerOrEnsureResolver
|
|
169
210
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
package_default,
|
|
3
3
|
writeRunState
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-S2NLQSLC.js";
|
|
5
5
|
|
|
6
6
|
// src/memory-report.ts
|
|
7
7
|
import * as fs2 from "fs";
|
|
@@ -309,7 +309,7 @@ function getDeployAttributes(domain) {
|
|
|
309
309
|
return attrs;
|
|
310
310
|
}
|
|
311
311
|
function isExpectedError(msg) {
|
|
312
|
-
return /personhood|owned by|owner mismatch|reserved for original|invalid domain label|not authorized for bulletin|insufficient balance|insufficient funds|quota exhausted|insufficient .* authorization|bip39 mnemonic|ipfs cli not installed|base name is \d+ chars|NameNotAvailable|name must be lowercase/i.test(msg);
|
|
312
|
+
return /personhood|owned by|owner mismatch|reserved for original|invalid domain label|not authorized for bulletin|insufficient balance|insufficient funds|quota exhausted|insufficient .* authorization|bip39 mnemonic|ipfs cli not installed|base name is \d+ chars|cannot register it|NameNotAvailable|name must be lowercase/i.test(msg);
|
|
313
313
|
}
|
|
314
314
|
function classifyDeployError(msg) {
|
|
315
315
|
if (isExpectedError(msg)) return "user";
|
|
@@ -348,6 +348,15 @@ var ERROR_KIND_RULES = [
|
|
|
348
348
|
[/No contract deployed at .+ returned empty success data/i, "naming.contract_unavailable"],
|
|
349
349
|
[/Contract call returned empty data — contract=/i, "naming.contract_unavailable"],
|
|
350
350
|
[/Domain\s+\S+\.dot\s+is already owned by\s+0x[a-fA-F0-9]+/i, "naming.already_owned"],
|
|
351
|
+
// #1185: formatUnregistrableReason's distinctive phrase — present in both
|
|
352
|
+
// the unregistered ("...is not registered, and bulletin-deploy cannot
|
|
353
|
+
// register it: ...") and owned-by-another-account ("...is owned by
|
|
354
|
+
// 0x..., and bulletin-deploy cannot register it for a different account:
|
|
355
|
+
// ...") variants, so one rule classifies both. Ordered before
|
|
356
|
+
// naming.subdomain_orphan (a different message shape entirely) and after
|
|
357
|
+
// naming.already_owned (whose "already owned by 0x..." phrasing this rule
|
|
358
|
+
// never produces, so there's no overlap either way).
|
|
359
|
+
[/cannot register it/i, "naming.governance_reserved"],
|
|
351
360
|
[/Cannot deploy\s+[\w.-]+\.dot:\s*parent\s+[\w.-]+\.dot\s+is owned by/i, "naming.subdomain_orphan"],
|
|
352
361
|
[/Post-deploy verification failed for .+: on-chain contenthash is /i, "verify.contenthash_mismatch"],
|
|
353
362
|
[/Deploy verification failed:\s*DAG-PB root.+not finalised/i, "verify.dagpb_not_finalised"],
|
|
@@ -576,6 +585,42 @@ async function withDeploySpan(domain, fn) {
|
|
|
576
585
|
}
|
|
577
586
|
}
|
|
578
587
|
}
|
|
588
|
+
async function withManifestSpan(domain, fn) {
|
|
589
|
+
if (!Sentry) return fn();
|
|
590
|
+
const savedRootSpan = deployRootSpan;
|
|
591
|
+
const attrs = { ...getDeployAttributes(domain), "deploy.domain": domain };
|
|
592
|
+
try {
|
|
593
|
+
return await Sentry.startSpan(
|
|
594
|
+
{ op: "deploy.manifest.publish", name: `manifest ${domain}`, attributes: attrs },
|
|
595
|
+
async (span) => {
|
|
596
|
+
deployRootSpan = span;
|
|
597
|
+
span.setAttribute("deploy.tool_version", VERSION);
|
|
598
|
+
span.setAttribute("deploy.domain", domain);
|
|
599
|
+
Sentry.setTags({
|
|
600
|
+
"deploy.repo": String(attrs["deploy.repo"] ?? ""),
|
|
601
|
+
"deploy.domain": domain
|
|
602
|
+
});
|
|
603
|
+
try {
|
|
604
|
+
const result = await fn();
|
|
605
|
+
span.setAttribute("deploy.manifest.status", "ok");
|
|
606
|
+
return result;
|
|
607
|
+
} catch (error) {
|
|
608
|
+
const msg = error.message ?? String(error);
|
|
609
|
+
setDeployErrorOnSpan(span, msg);
|
|
610
|
+
span.setAttribute("deploy.manifest.status", "error");
|
|
611
|
+
span.setStatus({ code: 2, message: "internal_error" });
|
|
612
|
+
throw error;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
);
|
|
616
|
+
} finally {
|
|
617
|
+
deployRootSpan = savedRootSpan;
|
|
618
|
+
try {
|
|
619
|
+
await Sentry.flush(5e3);
|
|
620
|
+
} catch {
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
579
624
|
function setDeployReportContext(patch) {
|
|
580
625
|
reportContext = { ...reportContext, ...patch };
|
|
581
626
|
}
|
|
@@ -788,6 +833,7 @@ export {
|
|
|
788
833
|
withSpan,
|
|
789
834
|
sampleMemory,
|
|
790
835
|
withDeploySpan,
|
|
836
|
+
withManifestSpan,
|
|
791
837
|
setDeployReportContext,
|
|
792
838
|
setDeployAttribute,
|
|
793
839
|
setDeployError,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
setDeploySentryTag,
|
|
12
12
|
truncateAddress,
|
|
13
13
|
withSpan
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-4IPJMCP5.js";
|
|
15
15
|
import {
|
|
16
16
|
validateContractAddresses
|
|
17
17
|
} from "./chunk-3ZEMX4WD.js";
|
|
@@ -578,28 +578,121 @@ function sanitizeDomainLabel(label) {
|
|
|
578
578
|
console.log(` Domain label sanitized: "${label}" \u2192 "${stripped}" (normalized trailing digits to 0)`);
|
|
579
579
|
return stripped;
|
|
580
580
|
}
|
|
581
|
-
function
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
const baseWithHyphen = sanitized.replace(/\d+$/, "");
|
|
587
|
-
const dropHyphen = sanitized.replace(/-(\d+)$/, "$1");
|
|
588
|
-
const insertSegment = sanitized.replace(/-(\d+)$/, "-pr$1");
|
|
589
|
-
throw new Error(
|
|
590
|
-
`Invalid domain label: "${sanitized}" \u2014 dotns base-name extraction leaves a trailing hyphen ("${baseWithHyphen}"), which the registry rejects with PopError("Name must be lowercase ASCII DNS label"). Drop the hyphen before the digits (e.g. "${dropHyphen}") or add a non-digit segment between (e.g. "${insertSegment}").`
|
|
591
|
-
);
|
|
581
|
+
function classifyLabelStatus(label) {
|
|
582
|
+
const trailingDigits = countTrailingDigits(label);
|
|
583
|
+
const baseLength = label.length - trailingDigits;
|
|
584
|
+
if (trailingDigits === 1 || trailingDigits > 2 || baseLength <= 5) {
|
|
585
|
+
return { status: ProofOfPersonhoodStatus.Reserved, trailingDigits, baseLength };
|
|
592
586
|
}
|
|
593
|
-
if (
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
587
|
+
if (baseLength <= 8) {
|
|
588
|
+
return { status: trailingDigits === 2 ? ProofOfPersonhoodStatus.ProofOfPersonhoodLite : ProofOfPersonhoodStatus.ProofOfPersonhoodFull, trailingDigits, baseLength };
|
|
589
|
+
}
|
|
590
|
+
return { status: ProofOfPersonhoodStatus.NoStatus, trailingDigits, baseLength };
|
|
591
|
+
}
|
|
592
|
+
function tierDescriptionFor(status) {
|
|
593
|
+
if (status === ProofOfPersonhoodStatus.NoStatus) return "open to any account";
|
|
594
|
+
if (status === ProofOfPersonhoodStatus.ProofOfPersonhoodLite) return "requires Personhood Lite";
|
|
595
|
+
if (status === ProofOfPersonhoodStatus.ProofOfPersonhoodFull) return "requires Personhood Full";
|
|
596
|
+
return "unavailable";
|
|
597
|
+
}
|
|
598
|
+
function noStatusFallbackBase(base) {
|
|
599
|
+
return `${base.padEnd(9, "x").slice(0, 9)}00`;
|
|
600
|
+
}
|
|
601
|
+
function buildLabelAlternatives(label) {
|
|
602
|
+
const trailingRun = label.slice(label.length - countTrailingDigits(label));
|
|
603
|
+
const base = stripTrailingDigits(label);
|
|
604
|
+
const twoDigitSuffix = trailingRun.length >= 2 ? trailingRun.slice(-2) : trailingRun.padStart(2, "0");
|
|
605
|
+
const noStatusBase = noStatusFallbackBase(base.replace(/[^a-z0-9-]/g, "x"));
|
|
606
|
+
const candidateLabels = [
|
|
607
|
+
`${base}${twoDigitSuffix}`,
|
|
608
|
+
// (a) 2 trailing digits
|
|
609
|
+
base,
|
|
610
|
+
// (b) 0 trailing digits
|
|
611
|
+
noStatusBase
|
|
612
|
+
// (c) NoStatus-safe fallback
|
|
613
|
+
];
|
|
614
|
+
const seen = /* @__PURE__ */ new Set();
|
|
615
|
+
const alternatives = [];
|
|
616
|
+
for (const candidate of candidateLabels) {
|
|
617
|
+
if (seen.has(candidate)) continue;
|
|
618
|
+
seen.add(candidate);
|
|
619
|
+
if (!/^[a-z0-9-]{3,63}$/.test(candidate)) continue;
|
|
620
|
+
if (candidate.startsWith("-") || candidate.endsWith("-")) continue;
|
|
621
|
+
if (/-\d+$/.test(candidate)) continue;
|
|
622
|
+
const { status, baseLength } = classifyLabelStatus(candidate);
|
|
623
|
+
if (status === ProofOfPersonhoodStatus.Reserved) continue;
|
|
624
|
+
alternatives.push({ label: candidate, baseLength, status, tierDescription: tierDescriptionFor(status) });
|
|
625
|
+
}
|
|
626
|
+
return alternatives;
|
|
627
|
+
}
|
|
628
|
+
function formatAlternativesList(alternatives) {
|
|
629
|
+
return alternatives.map((a) => ` - ${a.label}.dot \u2014 base ${a.baseLength}, ${a.tierDescription}`).join("\n");
|
|
630
|
+
}
|
|
631
|
+
function classifyRegistrability(label) {
|
|
632
|
+
const { trailingDigits, baseLength } = classifyLabelStatus(label);
|
|
633
|
+
if (trailingDigits !== 0 && trailingDigits !== 2) {
|
|
634
|
+
const digitWord = trailingDigits === 1 ? "digit" : "digits";
|
|
635
|
+
return {
|
|
636
|
+
registrable: false,
|
|
637
|
+
rule: "trailing-digits",
|
|
638
|
+
message: `Name has ${trailingDigits} trailing ${digitWord}; DotNS allows exactly 0 or 2 trailing digits. Use a base name with no trailing digits or a 2-digit suffix.`
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
if (/-\d+$/.test(label)) {
|
|
642
|
+
const baseWithHyphen = label.replace(/\d+$/, "");
|
|
643
|
+
return {
|
|
644
|
+
registrable: false,
|
|
645
|
+
rule: "hyphen-base",
|
|
646
|
+
message: `dotns-cli's base-name extraction leaves a trailing hyphen ("${baseWithHyphen}"), which the registry rejects with PopError("Name must be lowercase ASCII DNS label").`
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
if (baseLength <= 5) {
|
|
650
|
+
return {
|
|
651
|
+
registrable: false,
|
|
652
|
+
rule: "reserved-base",
|
|
653
|
+
message: `Base name is ${baseLength} char${baseLength === 1 ? "" : "s"}; DotNS reserves base names of 5 chars or fewer for governance (PopRules).`
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
return { registrable: true };
|
|
657
|
+
}
|
|
658
|
+
function formatUnregistrableReason(args) {
|
|
659
|
+
const { label, registrability, existingOwner } = args;
|
|
660
|
+
const alternatives = buildLabelAlternatives(label);
|
|
661
|
+
const alternativesBlock = alternatives.length > 0 ? `
|
|
662
|
+
|
|
663
|
+
Alternatively, use a name you can register yourself:
|
|
664
|
+
${formatAlternativesList(alternatives)}` : "";
|
|
665
|
+
if (existingOwner === null) {
|
|
666
|
+
return `${label}.dot is not registered, and bulletin-deploy cannot register it: ${registrability.message}
|
|
667
|
+
|
|
668
|
+
Names like this can only be registered by an account whitelisted on the DotNS controller:
|
|
669
|
+
- Request whitelisting at https://github.com/paritytech/dotns/issues/new/choose
|
|
670
|
+
- From the whitelisted account: dotns register domain -n ${label} --governance
|
|
671
|
+
- Transfer it to this signer if they differ
|
|
672
|
+
Then re-run this deploy unchanged.` + alternativesBlock;
|
|
601
673
|
}
|
|
602
|
-
return
|
|
674
|
+
return `${label}.dot is owned by ${existingOwner}, and bulletin-deploy cannot register it for a different account: ${registrability.message}
|
|
675
|
+
|
|
676
|
+
This signer does not hold that name. If you control ${existingOwner}'s key, redeploy with that account (e.g. via --mnemonic) to update its content directly.` + alternativesBlock;
|
|
677
|
+
}
|
|
678
|
+
function decideRegistrabilityOutcome(args) {
|
|
679
|
+
const { label, registrability, existingOwner, selfAddress } = args;
|
|
680
|
+
if (existingOwner !== null && existingOwner === selfAddress) {
|
|
681
|
+
return { canProceed: true, plannedAction: "already-owned-by-us" };
|
|
682
|
+
}
|
|
683
|
+
if (registrability.registrable) {
|
|
684
|
+
return { canProceed: true, plannedAction: "register" };
|
|
685
|
+
}
|
|
686
|
+
return {
|
|
687
|
+
canProceed: false,
|
|
688
|
+
plannedAction: "abort",
|
|
689
|
+
reason: formatUnregistrableReason({ label, registrability, existingOwner, selfAddress })
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
function validateDomainLabel(label) {
|
|
693
|
+
if (!/^[a-z0-9-]{3,63}$/.test(label)) throw new Error("Invalid domain label: must be 3-63 chars and contain only lowercase letters, digits, and hyphens");
|
|
694
|
+
if (label.startsWith("-") || label.endsWith("-")) throw new Error("Invalid domain label: cannot start or end with hyphen");
|
|
695
|
+
return label;
|
|
603
696
|
}
|
|
604
697
|
function isCommitmentMature(chainNowSeconds, commitTimestampSeconds, minimumAgeSeconds) {
|
|
605
698
|
return chainNowSeconds > commitTimestampSeconds + minimumAgeSeconds;
|
|
@@ -608,26 +701,27 @@ function isCommitmentTimingBarerevert(msg) {
|
|
|
608
701
|
return /bare-revert.*\(empty 0x\)/i.test(msg) || /commitment.*too new.*expired/i.test(msg) || /expired.*commitment/i.test(msg);
|
|
609
702
|
}
|
|
610
703
|
function classifyDotnsLabel(label) {
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
704
|
+
const { status, trailingDigits, baseLength } = classifyLabelStatus(label);
|
|
705
|
+
if (status === ProofOfPersonhoodStatus.Reserved) {
|
|
706
|
+
if (trailingDigits === 1 || trailingDigits > 2) {
|
|
707
|
+
return {
|
|
708
|
+
status,
|
|
709
|
+
message: `Name has ${trailingDigits} trailing digit${trailingDigits === 1 ? "" : "s"}; DotNS allows exactly 0 or 2 trailing digits. Use a base name with no trailing digits or a 2-digit suffix.`
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
const alternatives = buildLabelAlternatives(label);
|
|
713
|
+
const suggestion = alternatives.length > 0 ? `
|
|
714
|
+
|
|
715
|
+
Use a name you can register instead:
|
|
716
|
+
${formatAlternativesList(alternatives)}` : "";
|
|
621
717
|
return {
|
|
622
|
-
status
|
|
623
|
-
message: `Base name is ${
|
|
718
|
+
status,
|
|
719
|
+
message: `Base name is ${baseLength} char${baseLength === 1 ? "" : "s"}; DotNS reserves base names of 5 chars or fewer for governance (PopRules).${suggestion}`
|
|
624
720
|
};
|
|
625
721
|
}
|
|
626
|
-
if (
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
}
|
|
630
|
-
return { status: ProofOfPersonhoodStatus.NoStatus, message: "Available to all" };
|
|
722
|
+
if (status === ProofOfPersonhoodStatus.ProofOfPersonhoodLite) return { status, message: "Requires Light personhood verification" };
|
|
723
|
+
if (status === ProofOfPersonhoodStatus.ProofOfPersonhoodFull) return { status, message: "Requires Full personhood verification" };
|
|
724
|
+
return { status, message: "Available to all" };
|
|
631
725
|
}
|
|
632
726
|
function canRegister(requiredStatus, userStatus) {
|
|
633
727
|
if (requiredStatus === ProofOfPersonhoodStatus.Reserved) return false;
|
|
@@ -640,8 +734,8 @@ function canRegister(requiredStatus, userStatus) {
|
|
|
640
734
|
return true;
|
|
641
735
|
}
|
|
642
736
|
function exampleNoStatusLabel(label) {
|
|
643
|
-
const base = stripTrailingDigits(
|
|
644
|
-
return `${base
|
|
737
|
+
const base = stripTrailingDigits(label).replace(/[^a-z0-9-]/g, "x");
|
|
738
|
+
return `${noStatusFallbackBase(base)}.dot`;
|
|
645
739
|
}
|
|
646
740
|
function parseDomainName(input) {
|
|
647
741
|
const name = input.replace(/\.dot$/, "");
|
|
@@ -651,7 +745,7 @@ function parseDomainName(input) {
|
|
|
651
745
|
return { isSubdomain: false, label: sanitized, sublabel: null, parentLabel: null, fullName: `${sanitized}.dot` };
|
|
652
746
|
}
|
|
653
747
|
if (parts.length === 2) {
|
|
654
|
-
const sanitizedSub = validateDomainLabel(parts[0]
|
|
748
|
+
const sanitizedSub = validateDomainLabel(parts[0]);
|
|
655
749
|
const sanitizedParent = validateDomainLabel(parts[1]);
|
|
656
750
|
const fullLabel = `${sanitizedSub}.${sanitizedParent}`;
|
|
657
751
|
return { isSubdomain: true, label: fullLabel, sublabel: sanitizedSub, parentLabel: sanitizedParent, fullName: `${fullLabel}.dot` };
|
|
@@ -1887,6 +1981,9 @@ var DotNS = class {
|
|
|
1887
1981
|
label
|
|
1888
1982
|
);
|
|
1889
1983
|
}
|
|
1984
|
+
// `skipped` (#1187): set true on the pre-check fast path so callers (e.g.
|
|
1985
|
+
// manifest publish's telemetry counters) can tell a no-op skip from a real
|
|
1986
|
+
// on-chain write without a second read of their own.
|
|
1890
1987
|
async setContenthash(domainName, contenthashHex, opts = {}) {
|
|
1891
1988
|
return withSpan("deploy.dotns.set-contenthash", "2b. set-contenthash", {}, async () => {
|
|
1892
1989
|
this.ensureConnected();
|
|
@@ -1907,7 +2004,7 @@ var DotNS = class {
|
|
|
1907
2004
|
if (current === expected) {
|
|
1908
2005
|
console.log(` Contenthash already set: ${ipfsCid} \u2014 skipping tx`);
|
|
1909
2006
|
setDeployAttribute("deploy.dotns.contenthash_unchanged", "true");
|
|
1910
|
-
return { node };
|
|
2007
|
+
return { node, skipped: true };
|
|
1911
2008
|
}
|
|
1912
2009
|
} catch (_) {
|
|
1913
2010
|
}
|
|
@@ -1979,7 +2076,7 @@ var DotNS = class {
|
|
|
1979
2076
|
}
|
|
1980
2077
|
console.log(` Verified on-chain: ${ipfsCid}
|
|
1981
2078
|
`);
|
|
1982
|
-
return { node };
|
|
2079
|
+
return { node, skipped: false };
|
|
1983
2080
|
});
|
|
1984
2081
|
}
|
|
1985
2082
|
/**
|
|
@@ -2498,29 +2595,39 @@ var DotNS = class {
|
|
|
2498
2595
|
const trailingDigits = countTrailingDigits(validated);
|
|
2499
2596
|
const baselength = validated.length - trailingDigits;
|
|
2500
2597
|
const classification = classifyDotnsLabel(validated);
|
|
2501
|
-
|
|
2502
|
-
const sanitizeTrail = label !== validated ? `Input "${label}" was sanitized to "${validated}" (excess trailing digits trimmed). ` : "";
|
|
2503
|
-
return {
|
|
2504
|
-
label: validated,
|
|
2505
|
-
classification,
|
|
2506
|
-
userStatus: 0,
|
|
2507
|
-
trailingDigits,
|
|
2508
|
-
baselength,
|
|
2509
|
-
isAvailable: false,
|
|
2510
|
-
existingOwner: null,
|
|
2511
|
-
isBaseNameReserved: false,
|
|
2512
|
-
reservationOwner: null,
|
|
2513
|
-
isTestnet: false,
|
|
2514
|
-
canProceed: false,
|
|
2515
|
-
reason: `${sanitizeTrail}${classification.message}`,
|
|
2516
|
-
plannedAction: "abort",
|
|
2517
|
-
needsPopUpgrade: false
|
|
2518
|
-
};
|
|
2519
|
-
}
|
|
2598
|
+
const registrability = classifyRegistrability(validated);
|
|
2520
2599
|
const baseName = stripTrailingDigits(validated);
|
|
2521
2600
|
const [userStatus, baseReservation, ownership, isTestnet, signerFreeBalance] = await Promise.all([
|
|
2522
2601
|
this.getUserPopStatus(),
|
|
2523
|
-
|
|
2602
|
+
// Non-fatal (#1185): deleting the early Reserved abort above widens
|
|
2603
|
+
// what reaches this call — every 1-digit, 3+-digit, hyphen-base, and
|
|
2604
|
+
// short-base label now hits isBaseNameReserved where previously only
|
|
2605
|
+
// "registrable" labels did. A hyphen-adjacent base (e.g. from
|
|
2606
|
+
// `foo--88`, where stripTrailingDigits only removes ONE trailing
|
|
2607
|
+
// hyphen) can revert here; treat a revert/empty-data the same as
|
|
2608
|
+
// "not reserved" rather than failing preflight over a read that is
|
|
2609
|
+
// advisory context, not an authorization gate. Uses the same
|
|
2610
|
+
// this.contractCall the pre-#1185 code used (not contractCallNullable
|
|
2611
|
+
// — a different RPC path with its own stub surface); only the
|
|
2612
|
+
// fatal-vs-non-fatal handling changes, via a plain try/catch.
|
|
2613
|
+
//
|
|
2614
|
+
// Code review finding: a blanket catch-all here would also swallow a
|
|
2615
|
+
// genuine RPC/connection failure (timeout, WS drop) and silently
|
|
2616
|
+
// default to "not reserved" — fail-open on network noise, not just
|
|
2617
|
+
// on a real revert. Rethrow anything that looks like the read itself
|
|
2618
|
+
// never completed; only default when the dry-run call DID complete
|
|
2619
|
+
// (a revert or empty-data response).
|
|
2620
|
+
(async () => {
|
|
2621
|
+
try {
|
|
2622
|
+
return await withTimeout(this.contractCall(this._contracts.POP_RULES, POP_RULES_ABI, "isBaseNameReserved", [baseName]), 3e4, "isBaseNameReserved");
|
|
2623
|
+
} catch (e) {
|
|
2624
|
+
const msg = e?.message ?? String(e);
|
|
2625
|
+
if (/timed out after \d+ms|heartbeat timeout|WS halt|Unable to connect|ChainHead disjointed|websocket.*closed|socket closed|disconnect/i.test(msg)) {
|
|
2626
|
+
throw e;
|
|
2627
|
+
}
|
|
2628
|
+
return [false, zeroAddress, 0n];
|
|
2629
|
+
}
|
|
2630
|
+
})(),
|
|
2524
2631
|
this.checkOwnership(validated),
|
|
2525
2632
|
this.isTestnet(),
|
|
2526
2633
|
this.readFreeBalance(this.substrateAddress)
|
|
@@ -2567,6 +2674,28 @@ var DotNS = class {
|
|
|
2567
2674
|
signerFreeBalance
|
|
2568
2675
|
};
|
|
2569
2676
|
}
|
|
2677
|
+
if (!registrability.registrable) {
|
|
2678
|
+
const decision = decideRegistrabilityOutcome({ label: validated, registrability, existingOwner, selfAddress });
|
|
2679
|
+
if (!decision.canProceed) {
|
|
2680
|
+
return {
|
|
2681
|
+
label: validated,
|
|
2682
|
+
classification,
|
|
2683
|
+
userStatus,
|
|
2684
|
+
trailingDigits,
|
|
2685
|
+
baselength,
|
|
2686
|
+
isAvailable: true,
|
|
2687
|
+
existingOwner,
|
|
2688
|
+
isBaseNameReserved: isReserved,
|
|
2689
|
+
reservationOwner,
|
|
2690
|
+
isTestnet,
|
|
2691
|
+
canProceed: false,
|
|
2692
|
+
reason: decision.reason,
|
|
2693
|
+
plannedAction: "abort",
|
|
2694
|
+
needsPopUpgrade: false,
|
|
2695
|
+
signerFreeBalance
|
|
2696
|
+
};
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2570
2699
|
let transferFeeNative = 0n;
|
|
2571
2700
|
if (transferRecipientH160) {
|
|
2572
2701
|
try {
|
|
@@ -2790,10 +2919,10 @@ var DotNS = class {
|
|
|
2790
2919
|
return withSpan("deploy.dotns.register", `2a. register ${label}.dot`, {}, async () => {
|
|
2791
2920
|
if (!this.connected) await this.connect(options);
|
|
2792
2921
|
label = validateDomainLabel(label);
|
|
2793
|
-
const
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
throw new
|
|
2922
|
+
const registrability = classifyRegistrability(label);
|
|
2923
|
+
if (!registrability.registrable) {
|
|
2924
|
+
const decision = decideRegistrabilityOutcome({ label, registrability, existingOwner: null, selfAddress: this.evmAddress.toLowerCase() });
|
|
2925
|
+
throw new NonRetryableError(decision.reason);
|
|
2797
2926
|
}
|
|
2798
2927
|
const isTestnet = await this.isTestnet();
|
|
2799
2928
|
const registerAliasState = isTestnet && this._popSelfServe?.stateAwareGuidance === true && this.substrateAddress ? await this.classifyAliasAccountState(this.substrateAddress) : null;
|
|
@@ -2817,9 +2946,6 @@ var DotNS = class {
|
|
|
2817
2946
|
this.ensureNotRegistered(label)
|
|
2818
2947
|
]);
|
|
2819
2948
|
const requiredStatus = classification.requiredStatus;
|
|
2820
|
-
if (requiredStatus === ProofOfPersonhoodStatus.Reserved) {
|
|
2821
|
-
throw new Error(classification.message);
|
|
2822
|
-
}
|
|
2823
2949
|
const userStatus = await this.getUserPopStatus();
|
|
2824
2950
|
if (!canRegister(requiredStatus, userStatus)) {
|
|
2825
2951
|
rejectIneligible(requiredStatus, userStatus);
|
|
@@ -3018,6 +3144,10 @@ export {
|
|
|
3018
3144
|
countTrailingDigits,
|
|
3019
3145
|
stripTrailingDigits,
|
|
3020
3146
|
sanitizeDomainLabel,
|
|
3147
|
+
buildLabelAlternatives,
|
|
3148
|
+
classifyRegistrability,
|
|
3149
|
+
formatUnregistrableReason,
|
|
3150
|
+
decideRegistrabilityOutcome,
|
|
3021
3151
|
validateDomainLabel,
|
|
3022
3152
|
isCommitmentMature,
|
|
3023
3153
|
isCommitmentTimingBarerevert,
|