bulletin-deploy 0.18.0 → 0.18.2-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/bin/bulletin-deploy +4 -1
- package/dist/auth-config.js +3 -3
- package/dist/bug-report.js +4 -4
- package/dist/chain-health.js +3 -3
- package/dist/{chunk-A7GZZQ3Q.js → chunk-6EGOJ4VS.js} +2 -2
- package/dist/{chunk-6LR7OM7F.js → chunk-AAJ7QAAM.js} +35 -15
- package/dist/{chunk-WYJX7OFF.js → chunk-D3NJT3DW.js} +1 -1
- package/dist/chunk-HNJWIY55.js +8 -0
- package/dist/{chunk-TIQ3TCK4.js → chunk-JBWL6PA4.js} +3 -3
- package/dist/{chunk-RSAEMNX4.js → chunk-MG7JEKB7.js} +230 -27
- package/dist/{chunk-6WLVGBLQ.js → chunk-MMKZKXSS.js} +1 -1
- package/dist/{chunk-3CES4RZG.js → chunk-NXZHFDRN.js} +1 -1
- package/dist/{chunk-6BIAW73W.js → chunk-QSE3WU2B.js} +1 -1
- package/dist/{chunk-LZ7HCV4C.js → chunk-RDJSG2D3.js} +1 -1
- package/dist/{chunk-3TDAKT7Y.js → chunk-S4ZROJ5B.js} +3 -3
- package/dist/{chunk-F4OW5UVA.js → chunk-WFFDZKW7.js} +1 -1
- package/dist/{chunk-UY2DR5V4.js → chunk-ZP4U7C3N.js} +3 -3
- package/dist/chunk-probe.js +3 -3
- package/dist/commands/login.js +12 -11
- package/dist/commands/logout.js +4 -4
- package/dist/commands/transfer.js +4 -4
- package/dist/commands/whoami.js +3 -3
- package/dist/deploy-actors.js +6 -6
- package/dist/deploy.js +12 -11
- package/dist/dotns.d.ts +98 -6
- package/dist/dotns.js +4 -4
- package/dist/index.js +13 -12
- package/dist/manifest/publish.js +13 -12
- package/dist/manifest/schema.d.ts +4 -0
- package/dist/memory-report.js +2 -2
- package/dist/merkle.js +12 -11
- package/dist/personhood/bootstrap.js +13 -13
- package/dist/personhood/people-client.js +4 -4
- package/dist/run-state.js +1 -1
- package/dist/sss-allowance-cache.js +4 -4
- package/dist/storage-signer.js +12 -11
- package/dist/subname-depth.d.ts +22 -0
- package/dist/subname-depth.js +6 -0
- package/dist/telemetry.js +2 -2
- package/dist/version-check.js +3 -3
- package/package.json +3 -3
- package/tools/release-retry-wrapper.mjs +11 -0
package/bin/bulletin-deploy
CHANGED
|
@@ -585,8 +585,11 @@ try {
|
|
|
585
585
|
console.error(`Manifest publish failed: ${err?.message ?? err}`);
|
|
586
586
|
process.exit(err instanceof NonRetryableError ? EXIT_CODE_NO_RETRY : 1);
|
|
587
587
|
}
|
|
588
|
+
} else if (!loaded && !flags.noManifest) {
|
|
589
|
+
// Only the walk-up can come back empty: an explicit --config that is
|
|
590
|
+
// missing throws out of tryLoadProductConfig rather than returning null.
|
|
591
|
+
console.log(`No product config found from ${buildDirAbs} upward; content deployed, no manifest published.`);
|
|
588
592
|
}
|
|
589
|
-
// No config (or --no-manifest) → the contenthash-only deploy above is the whole job; nothing else to do.
|
|
590
593
|
}
|
|
591
594
|
|
|
592
595
|
if (!flags.help && !flags.version) {
|
package/dist/auth-config.js
CHANGED
|
@@ -9,12 +9,12 @@ import {
|
|
|
9
9
|
getPeopleChainEndpoints,
|
|
10
10
|
hasPersistedSession,
|
|
11
11
|
resolveBulletinEndpoints
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-MMKZKXSS.js";
|
|
13
13
|
import "./chunk-TSPERKUS.js";
|
|
14
14
|
import "./chunk-HDGD5FYX.js";
|
|
15
15
|
import "./chunk-ZOC4GITL.js";
|
|
16
|
-
import "./chunk-
|
|
17
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-NXZHFDRN.js";
|
|
17
|
+
import "./chunk-S4ZROJ5B.js";
|
|
18
18
|
export {
|
|
19
19
|
DOT_DAPP_ID,
|
|
20
20
|
DOT_DERIVATION_INDEX,
|
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-ZP4U7C3N.js";
|
|
14
|
+
import "./chunk-WFFDZKW7.js";
|
|
15
|
+
import "./chunk-NXZHFDRN.js";
|
|
16
|
+
import "./chunk-S4ZROJ5B.js";
|
|
17
17
|
export {
|
|
18
18
|
buildCliFlagsSummary,
|
|
19
19
|
buildLabels,
|
package/dist/chain-health.js
CHANGED
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
DEFAULT_WINDOW_BLOCKS,
|
|
4
4
|
MAX_SPANS_PER_SAMPLER,
|
|
5
5
|
endpointHost
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-RDJSG2D3.js";
|
|
7
|
+
import "./chunk-NXZHFDRN.js";
|
|
8
|
+
import "./chunk-S4ZROJ5B.js";
|
|
9
9
|
export {
|
|
10
10
|
ChainHealthSampler,
|
|
11
11
|
DEFAULT_WINDOW_BLOCKS,
|
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
} from "./chunk-5FLTDWWP.js";
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_MNEMONIC
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-MG7JEKB7.js";
|
|
8
8
|
import {
|
|
9
9
|
DOT_PRODUCT_ID
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-MMKZKXSS.js";
|
|
11
11
|
|
|
12
12
|
// src/deploy-actors.ts
|
|
13
13
|
var DEFAULT_WORKER_SURI = DEFAULT_MNEMONIC;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
subnameNestingLevels
|
|
3
|
+
} from "./chunk-HNJWIY55.js";
|
|
1
4
|
import {
|
|
2
5
|
deriveProductSigner
|
|
3
6
|
} from "./chunk-M5AKL3FL.js";
|
|
4
7
|
import {
|
|
5
8
|
preflightSssAllowance
|
|
6
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-QSE3WU2B.js";
|
|
7
10
|
import {
|
|
8
11
|
statementSigningAccount
|
|
9
12
|
} from "./chunk-GRPLHUYC.js";
|
|
@@ -29,18 +32,18 @@ import {
|
|
|
29
32
|
} from "./chunk-WPC2PUCV.js";
|
|
30
33
|
import {
|
|
31
34
|
setDeployContext
|
|
32
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-ZP4U7C3N.js";
|
|
33
36
|
import {
|
|
34
37
|
getBestBlockNumber,
|
|
35
38
|
probeChunks,
|
|
36
39
|
probeFinalityGap
|
|
37
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-D3NJT3DW.js";
|
|
38
41
|
import {
|
|
39
42
|
packSection
|
|
40
43
|
} from "./chunk-C2TS5MER.js";
|
|
41
44
|
import {
|
|
42
45
|
resolveStorageSigner
|
|
43
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-6EGOJ4VS.js";
|
|
44
47
|
import {
|
|
45
48
|
createSlotAccountSigner,
|
|
46
49
|
requestResourceAllocation
|
|
@@ -50,12 +53,13 @@ import {
|
|
|
50
53
|
DotNS,
|
|
51
54
|
TX_TIMEOUT_MS,
|
|
52
55
|
classifyRegistrability,
|
|
56
|
+
computeDomainNode,
|
|
53
57
|
fetchNonce,
|
|
54
58
|
formatUnregistrableReason,
|
|
55
59
|
parseDomainName,
|
|
56
60
|
popStatusName,
|
|
57
61
|
verifyNonceAdvanced
|
|
58
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-MG7JEKB7.js";
|
|
59
63
|
import {
|
|
60
64
|
derivePoolAccounts,
|
|
61
65
|
detectTestnet,
|
|
@@ -69,7 +73,7 @@ import {
|
|
|
69
73
|
STALE_SESSION_MESSAGE,
|
|
70
74
|
getPeopleChainEndpoints,
|
|
71
75
|
hasPersistedSession
|
|
72
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-MMKZKXSS.js";
|
|
73
77
|
import {
|
|
74
78
|
CLI_NAME
|
|
75
79
|
} from "./chunk-TSPERKUS.js";
|
|
@@ -84,7 +88,7 @@ import {
|
|
|
84
88
|
} from "./chunk-ZOC4GITL.js";
|
|
85
89
|
import {
|
|
86
90
|
ChainHealthSampler
|
|
87
|
-
} from "./chunk-
|
|
91
|
+
} from "./chunk-RDJSG2D3.js";
|
|
88
92
|
import {
|
|
89
93
|
VERSION,
|
|
90
94
|
captureWarning,
|
|
@@ -98,7 +102,7 @@ import {
|
|
|
98
102
|
truncateAddress,
|
|
99
103
|
withDeploySpan,
|
|
100
104
|
withSpan
|
|
101
|
-
} from "./chunk-
|
|
105
|
+
} from "./chunk-NXZHFDRN.js";
|
|
102
106
|
|
|
103
107
|
// src/merkle.ts
|
|
104
108
|
import * as fs2 from "fs";
|
|
@@ -2025,11 +2029,18 @@ function assertSubdomainOwnerMatchesSigner(result, signerEvmAddress, sublabel, p
|
|
|
2025
2029
|
}
|
|
2026
2030
|
}
|
|
2027
2031
|
function formatSubdomainParentError(fullName, parentLabel, parentOwner, selfAddress, tld = DEFAULT_TLD, profile = "poprules-startingPrice") {
|
|
2028
|
-
|
|
2029
|
-
|
|
2032
|
+
const owner = parentOwner !== null && /^0x0{40}$/i.test(parentOwner) ? null : parentOwner;
|
|
2033
|
+
const parentIsNested = parentLabel.includes(".");
|
|
2034
|
+
if (owner === null && parentIsNested) {
|
|
2035
|
+
return `Cannot deploy ${fullName}: parent ${parentLabel}.${tld} does not exist.
|
|
2036
|
+
|
|
2037
|
+
It is itself a subname, so it cannot be registered directly \u2014 whoever owns ${parentLabel.slice(parentLabel.indexOf(".") + 1)}.${tld} has to create it first, then hand it to this signer (${selfAddress || "this account"}).`;
|
|
2038
|
+
}
|
|
2039
|
+
if (owner !== null) {
|
|
2040
|
+
const transferBullet = selfAddress ? ` - ask the ${owner} account to hand the parent to this signer, run BY that account: ${CLI_NAME} transfer ${parentLabel}.${tld} --to ${selfAddress} --mnemonic <the ${owner} account's key>
|
|
2030
2041
|
` : "";
|
|
2031
2042
|
const ownedParentBullet = ` - deploy the subdomain under a parent this signer already owns instead.`;
|
|
2032
|
-
return `Cannot deploy ${fullName}: parent ${parentLabel}.${tld} is owned by ${
|
|
2043
|
+
return `Cannot deploy ${fullName}: parent ${parentLabel}.${tld} is owned by ${owner}, not by this signer.
|
|
2033
2044
|
|
|
2034
2045
|
` + (transferBullet ? `Either:
|
|
2035
2046
|
${transferBullet}${ownedParentBullet}` : ownedParentBullet);
|
|
@@ -2158,6 +2169,10 @@ async function deploy(content, domainName = null, options = {}) {
|
|
|
2158
2169
|
_deployRpcFailedOver = false;
|
|
2159
2170
|
POOL_SIZE = options.poolSize ?? parseInt(process.env.BULLETIN_POOL_SIZE ?? String(DEFAULT_POOL_SIZE), 10);
|
|
2160
2171
|
let parsed = domainName ? parseDomainName(domainName, envTld) : null;
|
|
2172
|
+
if (parsed?.isSubdomain && parsed.parentLabel.includes(".")) {
|
|
2173
|
+
const levels = subnameNestingLevels(1 + parsed.parentLabel.split(".").length);
|
|
2174
|
+
console.log(` NOTE: "${domainName}" parses as sublabel "${parsed.sublabel}" under parent path "${parsed.parentLabel}.${envTld}" (${levels} levels of subname nesting).`);
|
|
2175
|
+
}
|
|
2161
2176
|
let sessionCleanup;
|
|
2162
2177
|
const hasSession = hasPersistedSession();
|
|
2163
2178
|
const signerChoice = chooseSignerInput({
|
|
@@ -2264,6 +2279,9 @@ async function deploy(content, domainName = null, options = {}) {
|
|
|
2264
2279
|
setDeployAttribute("deploy.env", envId);
|
|
2265
2280
|
setDeployAttribute("deploy.label", parsed?.label ?? name);
|
|
2266
2281
|
setDeployAttribute("deploy.subdomain", String(parsed?.isSubdomain ?? false));
|
|
2282
|
+
if (parsed?.isSubdomain) {
|
|
2283
|
+
setDeployAttribute("deploy.dotns.subname_levels", subnameNestingLevels(1 + parsed.parentLabel.split(".").length));
|
|
2284
|
+
}
|
|
2267
2285
|
if (envNetwork) setDeployAttribute("deploy.network", envNetwork);
|
|
2268
2286
|
if (envSource) setDeployAttribute("deploy.environments_source", envSource);
|
|
2269
2287
|
setDeployAttribute("deploy.transfer.enabled", options.transferTo ? "true" : "false");
|
|
@@ -2296,8 +2314,9 @@ async function deploy(content, domainName = null, options = {}) {
|
|
|
2296
2314
|
const subResult = await preflight.checkSubdomainOwnership(parsed.sublabel, parsed.parentLabel);
|
|
2297
2315
|
assertSubdomainOwnerMatchesSigner(subResult, preflight.evmAddress, parsed.sublabel, parsed.parentLabel, envTld);
|
|
2298
2316
|
if (!subResult.owned) {
|
|
2299
|
-
const
|
|
2300
|
-
|
|
2317
|
+
const parentNode = computeDomainNode(parsed.parentLabel, envTld);
|
|
2318
|
+
const { authorised: parentAuthorised, owner: parentOwner } = await preflight.checkNodeAuthorization(parentNode, preflight.evmAddress);
|
|
2319
|
+
if (!parentAuthorised) {
|
|
2301
2320
|
throw new NonRetryableError(
|
|
2302
2321
|
formatSubdomainParentError(parsed.fullName, parsed.parentLabel, parentOwner ?? null, preflight.evmAddress ?? "", envTld, preflight.protocolVersion)
|
|
2303
2322
|
);
|
|
@@ -2554,8 +2573,9 @@ async function deploy(content, domainName = null, options = {}) {
|
|
|
2554
2573
|
} else if (owner) {
|
|
2555
2574
|
throw new Error(`Subdomain ${parsed.fullName} is owned by ${owner}, not ${dotns.evmAddress}`);
|
|
2556
2575
|
} else {
|
|
2557
|
-
const
|
|
2558
|
-
|
|
2576
|
+
const parentNode = computeDomainNode(parsed.parentLabel, envTld);
|
|
2577
|
+
const { authorised: parentAuthorised } = await dotns.checkNodeAuthorization(parentNode, dotns.evmAddress);
|
|
2578
|
+
if (!parentAuthorised) throw new Error(`You must own (or be approved for) ${parsed.parentLabel}.${envTld} to register subdomains under it`);
|
|
2559
2579
|
console.log(` Status: Registering subdomain...`);
|
|
2560
2580
|
await dotns.registerSubdomain(parsed.sublabel, parsed.parentLabel);
|
|
2561
2581
|
registeredFresh = true;
|
|
@@ -13,13 +13,13 @@ import {
|
|
|
13
13
|
setBulletinEndpoints,
|
|
14
14
|
storeDirectory,
|
|
15
15
|
storeFile
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-AAJ7QAAM.js";
|
|
17
17
|
import {
|
|
18
18
|
DEFAULT_TLD,
|
|
19
19
|
DotNS,
|
|
20
20
|
TX_KIND_SKIPPED,
|
|
21
21
|
stripTldSuffix
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-MG7JEKB7.js";
|
|
23
23
|
import {
|
|
24
24
|
DEFAULT_ENV_ID,
|
|
25
25
|
getPopSelfServeConfig,
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
setDeployAttribute,
|
|
34
34
|
withManifestSpan,
|
|
35
35
|
withSpan
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-NXZHFDRN.js";
|
|
37
37
|
|
|
38
38
|
// src/manifest/publish.ts
|
|
39
39
|
import * as fs2 from "fs/promises";
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "./chunk-ZOC4GITL.js";
|
|
14
14
|
import {
|
|
15
15
|
ChainHealthSampler
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-RDJSG2D3.js";
|
|
17
17
|
import {
|
|
18
18
|
captureWarning,
|
|
19
19
|
markCodePath,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
setDeploySentryTag,
|
|
22
22
|
truncateAddress,
|
|
23
23
|
withSpan
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-NXZHFDRN.js";
|
|
25
25
|
import {
|
|
26
26
|
POP_CONTROLLER_PROBE_ABI,
|
|
27
27
|
PROTOCOL_PROBE_LABEL,
|
|
@@ -411,7 +411,21 @@ var DOTNS_REGISTRY_ABI = [
|
|
|
411
411
|
{ inputs: [{ name: "record", type: "tuple", components: [{ name: "parentNode", type: "bytes32" }, { name: "subLabel", type: "string" }, { name: "parentLabel", type: "string" }, { name: "owner", type: "address" }] }], name: "setSubnodeOwner", outputs: [{ name: "subnode", type: "bytes32" }], stateMutability: "nonpayable", type: "function" },
|
|
412
412
|
{ inputs: [{ name: "node", type: "bytes32" }, { name: "newResolver", type: "address" }], name: "setResolver", outputs: [], stateMutability: "nonpayable", type: "function" },
|
|
413
413
|
{ inputs: [{ name: "node", type: "bytes32" }], name: "owner", outputs: [{ name: "", type: "address" }], stateMutability: "view", type: "function" },
|
|
414
|
-
{ inputs: [{ name: "node", type: "bytes32" }], name: "resolver", outputs: [{ name: "", type: "address" }], stateMutability: "view", type: "function" }
|
|
414
|
+
{ inputs: [{ name: "node", type: "bytes32" }], name: "resolver", outputs: [{ name: "", type: "address" }], stateMutability: "view", type: "function" },
|
|
415
|
+
// #1435-adjacent: DotnsRegistry.isAuthorised(node, account) — verified against
|
|
416
|
+
// upstream IDotnsRegistry.sol at v0.7.0. The contract's own doc comment calls
|
|
417
|
+
// this "the canonical authorisation check the registry enforces on
|
|
418
|
+
// owner-gated entry points" and "the single source of truth" _authorised/
|
|
419
|
+
// isAuthorised both delegate to: a non-zero stored subnode owner must equal
|
|
420
|
+
// account; otherwise, for a tokenised node, the registrar's ERC-721 owner,
|
|
421
|
+
// an operator-for-all delegate, or a single-token approval all qualify.
|
|
422
|
+
// Strictly wider than a raw owner()-equality test. See
|
|
423
|
+
// checkNodeAuthorization below, which prefers this and falls back to
|
|
424
|
+
// owner()-equality only when this accessor is absent (a bare revert).
|
|
425
|
+
{ inputs: [{ name: "node", type: "bytes32" }, { name: "account", type: "address" }], name: "isAuthorised", outputs: [{ name: "authorisedFlag", type: "bool" }], stateMutability: "view", type: "function" }
|
|
426
|
+
];
|
|
427
|
+
var DOTNS_REGISTRY_SET_SUBNODE_OWNER_ABI_V07 = [
|
|
428
|
+
{ inputs: [{ name: "record", type: "tuple", components: [{ name: "parentNode", type: "bytes32" }, { name: "subLabel", type: "string" }, { name: "parentLabel", type: "string" }, { name: "owner", type: "address" }, { name: "persist", type: "bool" }] }], name: "setSubnodeOwner", outputs: [{ name: "subnode", type: "bytes32" }], stateMutability: "nonpayable", type: "function" }
|
|
415
429
|
];
|
|
416
430
|
var DOTNS_CONTENT_RESOLVER_ABI = [
|
|
417
431
|
{ inputs: [{ name: "node", type: "bytes32" }, { name: "hash", type: "bytes" }], name: "setContenthash", outputs: [], stateMutability: "nonpayable", type: "function" },
|
|
@@ -482,6 +496,9 @@ function dotnsContractName(address, contracts = CONTRACTS) {
|
|
|
482
496
|
function stringifyDebugValue(value) {
|
|
483
497
|
return JSON.stringify(value, (_key, nested) => typeof nested === "bigint" ? nested.toString() : nested);
|
|
484
498
|
}
|
|
499
|
+
function isBareRevertResult(revertData, revertFlags) {
|
|
500
|
+
return (revertData === void 0 || revertData.trim() === "0x") && revertFlags === 1n;
|
|
501
|
+
}
|
|
485
502
|
function dotnsTxDebugEnabled() {
|
|
486
503
|
return process.env.BULLETIN_DEPLOY_DOTNS_DEBUG === "1" || process.env.DOTNS_DEBUG === "1";
|
|
487
504
|
}
|
|
@@ -510,8 +527,7 @@ function formatContractDryRunFailure(gasEstimate, context) {
|
|
|
510
527
|
lines.push(` calldata: ${context.encodedData}`);
|
|
511
528
|
if (context.args) lines.push(` args: ${stringifyDebugValue(context.args)}`);
|
|
512
529
|
}
|
|
513
|
-
const
|
|
514
|
-
const isBareRevert = (revertData === void 0 || revertData.trim() === "0x") && gasEstimate.revertFlags === 1n;
|
|
530
|
+
const isBareRevert = isBareRevertResult(gasEstimate.revertData, gasEstimate.revertFlags);
|
|
515
531
|
if (isBareRevert && BARE_REVERT_DIAGNOSTIC_FUNCTIONS.has(functionName)) {
|
|
516
532
|
if (functionName === "register") {
|
|
517
533
|
lines.push(
|
|
@@ -814,13 +830,11 @@ function parseDomainName(input, tld = DEFAULT_TLD) {
|
|
|
814
830
|
const sanitized = validateDomainLabel(parts[0]);
|
|
815
831
|
return { isSubdomain: false, label: sanitized, sublabel: null, parentLabel: null, fullName: `${sanitized}.${tld}` };
|
|
816
832
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
}
|
|
823
|
-
throw new Error(`Invalid domain: only one level of subdomains supported (got ${parts.length} labels)`);
|
|
833
|
+
const restParts = parts.slice(1);
|
|
834
|
+
const sanitizedSub = validateDomainLabel(parts[0]);
|
|
835
|
+
const sanitizedParent = restParts.map((p) => validateDomainLabel(p)).join(".");
|
|
836
|
+
const fullLabel = `${sanitizedSub}.${sanitizedParent}`;
|
|
837
|
+
return { isSubdomain: true, label: fullLabel, sublabel: sanitizedSub, parentLabel: sanitizedParent, fullName: `${fullLabel}.${tld}` };
|
|
824
838
|
}
|
|
825
839
|
function parseProofOfPersonhoodStatus(status) {
|
|
826
840
|
const s = (status ?? "none").toLowerCase();
|
|
@@ -1322,6 +1336,22 @@ var DotNS = class _DotNS {
|
|
|
1322
1336
|
// this into a throw-if-unset without re-checking both of those.
|
|
1323
1337
|
_protocolVersion = DEFAULT_DOTNS_PROFILE;
|
|
1324
1338
|
_adapter = getAdapter(DEFAULT_DOTNS_PROFILE);
|
|
1339
|
+
// #1435: which setSubnodeOwner tuple shape this connection's chain accepts
|
|
1340
|
+
// — "legacy" (4-field, pre-v0.7) or "v07" (5-field + persist). `null` means
|
|
1341
|
+
// "not yet probed"; resolveSubnodeOwnerShape resolves it lazily on the
|
|
1342
|
+
// FIRST subname write per connection (register or transfer, whichever runs
|
|
1343
|
+
// first) and caches the verdict here for the rest of that connection's
|
|
1344
|
+
// life. Defaults to "legacy" (NOT null) for the exact same reason
|
|
1345
|
+
// _protocolVersion defaults to DEFAULT_DOTNS_PROFILE above: every existing
|
|
1346
|
+
// test/library caller that constructs a DotNS instance and stubs
|
|
1347
|
+
// contractTransaction/submitBatchedContractCalls directly WITHOUT ever
|
|
1348
|
+
// calling connect() must keep today's exact pre-#1435 4-field behaviour,
|
|
1349
|
+
// with no probe attempted at all (there is no clientWrapper to probe
|
|
1350
|
+
// against in that case). connect() resets this to null so a real
|
|
1351
|
+
// connection always re-probes on its first subname write rather than
|
|
1352
|
+
// trusting this default or a previous connection's stale verdict — see
|
|
1353
|
+
// the reset next to detectProtocolVersion() below.
|
|
1354
|
+
_subnodeOwnerShape = "legacy";
|
|
1325
1355
|
_onPhoneSigningRequired = void 0;
|
|
1326
1356
|
_confirmPhoneReady = void 0;
|
|
1327
1357
|
/** Total phone-signature count for this DotNS session (drives the `total` field passed to confirmPhoneReady). */
|
|
@@ -1359,6 +1389,10 @@ var DotNS = class _DotNS {
|
|
|
1359
1389
|
this._protocolVersion = profile;
|
|
1360
1390
|
this._adapter = getAdapter(profile);
|
|
1361
1391
|
}
|
|
1392
|
+
/** #1435 test-only: pin the setSubnodeOwner shape cache directly ("legacy", "v07", or null to force resolveSubnodeOwnerShape to re-probe), bypassing the live dry-run probe — for unit tests that stub the probe or exercise the fallback/caching logic itself. */
|
|
1393
|
+
__setSubnodeOwnerShapeForTest(shape) {
|
|
1394
|
+
this._subnodeOwnerShape = shape;
|
|
1395
|
+
}
|
|
1362
1396
|
constructor() {
|
|
1363
1397
|
this.client = null;
|
|
1364
1398
|
this.clientWrapper = null;
|
|
@@ -1540,6 +1574,7 @@ var DotNS = class _DotNS {
|
|
|
1540
1574
|
this.connected = true;
|
|
1541
1575
|
await this.resolveAndVerifyTld(options.tld);
|
|
1542
1576
|
await this.detectProtocolVersion();
|
|
1577
|
+
this._subnodeOwnerShape = null;
|
|
1543
1578
|
if (options.dotnsProtocol && options.dotnsProtocol !== this._protocolVersion) {
|
|
1544
1579
|
this.connected = false;
|
|
1545
1580
|
throw new Error(
|
|
@@ -1932,8 +1967,13 @@ var DotNS = class _DotNS {
|
|
|
1932
1967
|
* v0.5.8-rc1 (pricingVersion answers on both), and classifyProtocolVersion
|
|
1933
1968
|
* already only consults it when pricingVersionOk is true, so gating it
|
|
1934
1969
|
* behind that result resolving first would just add a serialized
|
|
1935
|
-
* round-trip for no benefit — worse
|
|
1936
|
-
*
|
|
1970
|
+
* round-trip for no benefit — worse as v0.6.0 spreads (#1410), since that
|
|
1971
|
+
* "no benefit" case becomes the common one. The discarded-probe cost does
|
|
1972
|
+
* NOT self-liquidate the moment v0.6.0 ships: it persists on every
|
|
1973
|
+
* environment still running an older generation (dotns-protocol.ts's
|
|
1974
|
+
* profile comment owns per-generation status — this one does not restate
|
|
1975
|
+
* it). Parallel regardless: one discarded read beats a serialized
|
|
1976
|
+
* round-trip on every connect. Resolves to `null`
|
|
1937
1977
|
* immediately (no RPC at all) when DOTNS_POP_CONTROLLER has no configured
|
|
1938
1978
|
* address.
|
|
1939
1979
|
*/
|
|
@@ -2086,6 +2126,92 @@ var DotNS = class _DotNS {
|
|
|
2086
2126
|
}
|
|
2087
2127
|
throw new Error("contractTransaction: unreachable \u2014 phone-silence re-arm loop exited without resolving");
|
|
2088
2128
|
}
|
|
2129
|
+
/**
|
|
2130
|
+
* #1435: resolve — once per connection — which `setSubnodeOwner` tuple
|
|
2131
|
+
* shape this chain's DOTNS_REGISTRY accepts, and cache the verdict on
|
|
2132
|
+
* `_subnodeOwnerShape` for the rest of this connection's life. Called by
|
|
2133
|
+
* buildSetSubnodeOwnerCall on the first subname write (register or
|
|
2134
|
+
* transfer, whichever runs first); every subsequent subname write in the
|
|
2135
|
+
* same connection reuses the cached shape with no further chain I/O.
|
|
2136
|
+
*
|
|
2137
|
+
* There is no read-only capability probe for this (see
|
|
2138
|
+
* DOTNS_REGISTRY_SET_SUBNODE_OWNER_ABI_V07's comment — v0.7 adds no new
|
|
2139
|
+
* function/contract to probe), so this probes the WRITE itself: dry-run
|
|
2140
|
+
* the v0.7 (5-field, `persist: false`) encoding against `sampleRecord`
|
|
2141
|
+
* first (a dry run commits no state, so this never spends a fee or
|
|
2142
|
+
* mutates the registry). Three outcomes:
|
|
2143
|
+
*
|
|
2144
|
+
* - The dry run succeeds → this chain is on v0.7+; cache "v07".
|
|
2145
|
+
* - The dry run bare-reverts (empty `0x` data, flags=1 — i.e. no
|
|
2146
|
+
* function matches this selector at all) → this chain predates v0.7;
|
|
2147
|
+
* cache "legacy" and fall back to the 4-field encoding.
|
|
2148
|
+
* - The dry run reverts WITH data → the 5-field function EXISTS and
|
|
2149
|
+
* rejected the call for a real reason (bad parent, not authorised,
|
|
2150
|
+
* etc.) — that is a genuine failure of the caller's actual write, not
|
|
2151
|
+
* a shape mismatch, so it propagates immediately rather than being
|
|
2152
|
+
* swallowed into a fallback attempt. Conflating the two here would
|
|
2153
|
+
* turn a real authorization failure into a confusing shape-probe
|
|
2154
|
+
* loop, retrying the legacy shape (which will ALSO fail, just with a
|
|
2155
|
+
* less informative bare-revert-style error) instead of surfacing the
|
|
2156
|
+
* real cause.
|
|
2157
|
+
*
|
|
2158
|
+
* `sampleRecord` is the actual record the caller is about to write (not a
|
|
2159
|
+
* throwaway probe payload) — probing with real args means a v0.7 chain
|
|
2160
|
+
* that genuinely rejects this specific write (e.g. the signer doesn't own
|
|
2161
|
+
* the parent) reports that real reason here, on the first (probe) dry
|
|
2162
|
+
* run, rather than surfacing a second time once the real submission's own
|
|
2163
|
+
* internal dry run repeats the same call.
|
|
2164
|
+
*/
|
|
2165
|
+
async resolveSubnodeOwnerShape(sampleRecord) {
|
|
2166
|
+
if (this._subnodeOwnerShape !== null) return this._subnodeOwnerShape;
|
|
2167
|
+
this.ensureConnected();
|
|
2168
|
+
if (!this.clientWrapper) throw new Error("resolveSubnodeOwnerShape: polkadot-api client not available");
|
|
2169
|
+
const contractAddress = this._contracts.DOTNS_REGISTRY;
|
|
2170
|
+
const v07Abi = DOTNS_REGISTRY_SET_SUBNODE_OWNER_ABI_V07;
|
|
2171
|
+
const v07Args = [{ ...sampleRecord, persist: false }];
|
|
2172
|
+
const encodedCallData = encodeFunctionData({ abi: v07Abi, functionName: "setSubnodeOwner", args: v07Args });
|
|
2173
|
+
const callResult = await this.clientWrapper.performDryRunCall(this.substrateAddress, contractAddress, 0n, encodedCallData);
|
|
2174
|
+
if (callResult.result.isOk) {
|
|
2175
|
+
this._subnodeOwnerShape = "v07";
|
|
2176
|
+
return "v07";
|
|
2177
|
+
}
|
|
2178
|
+
const errorData = callResult.result.value;
|
|
2179
|
+
const revertData = errorData?.data;
|
|
2180
|
+
const revertFlags = errorData?.flags;
|
|
2181
|
+
if (!isBareRevertResult(revertData, revertFlags)) {
|
|
2182
|
+
throw new Error(formatContractDryRunFailure({
|
|
2183
|
+
revertData,
|
|
2184
|
+
revertFlags,
|
|
2185
|
+
gasConsumed: callResult.gasConsumed,
|
|
2186
|
+
gasRequired: callResult.gasRequired,
|
|
2187
|
+
storageDeposit: callResult.storageDeposit?.value
|
|
2188
|
+
}, {
|
|
2189
|
+
contractAddress,
|
|
2190
|
+
functionName: "setSubnodeOwner",
|
|
2191
|
+
signerSubstrateAddress: this.substrateAddress,
|
|
2192
|
+
signerEvmAddress: this.evmAddress ?? void 0,
|
|
2193
|
+
value: 0n,
|
|
2194
|
+
encodedData: encodedCallData,
|
|
2195
|
+
args: v07Args,
|
|
2196
|
+
contracts: this._contracts
|
|
2197
|
+
}));
|
|
2198
|
+
}
|
|
2199
|
+
this._subnodeOwnerShape = "legacy";
|
|
2200
|
+
return "legacy";
|
|
2201
|
+
}
|
|
2202
|
+
/**
|
|
2203
|
+
* Build the `{ abi, args }` pair a setSubnodeOwner call site should submit,
|
|
2204
|
+
* after resolving (and caching) this connection's accepted tuple shape —
|
|
2205
|
+
* see resolveSubnodeOwnerShape. `record` carries the 4 fields common to
|
|
2206
|
+
* both shapes; the v0.7 shape appends `persist: false` (per #1435, this
|
|
2207
|
+
* only gates the LabelStore write, which is restricted to protocol store
|
|
2208
|
+
* writers — the ownership/resolver record itself is written either way).
|
|
2209
|
+
*/
|
|
2210
|
+
async buildSetSubnodeOwnerCall(record) {
|
|
2211
|
+
const shape = await this.resolveSubnodeOwnerShape(record);
|
|
2212
|
+
if (shape === "v07") return { abi: DOTNS_REGISTRY_SET_SUBNODE_OWNER_ABI_V07, args: [{ ...record, persist: false }] };
|
|
2213
|
+
return { abi: DOTNS_REGISTRY_ABI, args: [record] };
|
|
2214
|
+
}
|
|
2089
2215
|
async checkOwnership(label, ownerAddress = null) {
|
|
2090
2216
|
this.ensureConnected();
|
|
2091
2217
|
const checkAddress = (ownerAddress || this.evmAddress).toLowerCase();
|
|
@@ -2099,6 +2225,82 @@ var DotNS = class _DotNS {
|
|
|
2099
2225
|
return { owned: false, owner: null };
|
|
2100
2226
|
}
|
|
2101
2227
|
}
|
|
2228
|
+
/**
|
|
2229
|
+
* Raw probe for DotnsRegistry.isAuthorised(node, account) — returns the
|
|
2230
|
+
* decoded bool when the accessor exists, or `null` when it is absent from
|
|
2231
|
+
* this registry deployment (a bare selector-not-found revert). Shares the
|
|
2232
|
+
* bare-revert-vs-real-revert discrimination (isBareRevertResult) with
|
|
2233
|
+
* resolveSubnodeOwnerShape's v0.7 shape probe above: a revert WITH data
|
|
2234
|
+
* means the function exists and the on-chain call itself is a normal view
|
|
2235
|
+
* read that should never revert for a real reason (see _isAuthorised in
|
|
2236
|
+
* DotnsRegistry.sol — it only ever returns a bool), so that branch
|
|
2237
|
+
* propagates as an unexpected failure rather than being folded into
|
|
2238
|
+
* "absent".
|
|
2239
|
+
*/
|
|
2240
|
+
async probeIsAuthorised(node, account) {
|
|
2241
|
+
this.ensureConnected();
|
|
2242
|
+
if (!this.clientWrapper) throw new Error("probeIsAuthorised: polkadot-api client not available");
|
|
2243
|
+
const contractAddress = this._contracts.DOTNS_REGISTRY;
|
|
2244
|
+
const isAuthorisedAbi = DOTNS_REGISTRY_ABI;
|
|
2245
|
+
const isAuthorisedArgs = [node, account];
|
|
2246
|
+
const encodedCallData = encodeFunctionData({ abi: isAuthorisedAbi, functionName: "isAuthorised", args: isAuthorisedArgs });
|
|
2247
|
+
const callResult = await this.clientWrapper.performDryRunCall(this.substrateAddress, contractAddress, 0n, encodedCallData);
|
|
2248
|
+
if (callResult.result.isOk) {
|
|
2249
|
+
const rawData = callResult.result.value.data ?? "0x";
|
|
2250
|
+
if (rawData.length > 2) {
|
|
2251
|
+
return decodeFunctionResult({ abi: DOTNS_REGISTRY_ABI, functionName: "isAuthorised", data: rawData });
|
|
2252
|
+
}
|
|
2253
|
+
return null;
|
|
2254
|
+
}
|
|
2255
|
+
const errorData = callResult.result.value;
|
|
2256
|
+
const revertData = errorData?.data;
|
|
2257
|
+
const revertFlags = errorData?.flags;
|
|
2258
|
+
if (isBareRevertResult(revertData, revertFlags)) return null;
|
|
2259
|
+
throw new Error(formatContractDryRunFailure({
|
|
2260
|
+
revertData,
|
|
2261
|
+
revertFlags,
|
|
2262
|
+
gasConsumed: callResult.gasConsumed,
|
|
2263
|
+
gasRequired: callResult.gasRequired,
|
|
2264
|
+
storageDeposit: callResult.storageDeposit?.value
|
|
2265
|
+
}, {
|
|
2266
|
+
contractAddress,
|
|
2267
|
+
functionName: "isAuthorised",
|
|
2268
|
+
signerSubstrateAddress: this.substrateAddress,
|
|
2269
|
+
signerEvmAddress: this.evmAddress ?? void 0,
|
|
2270
|
+
value: 0n,
|
|
2271
|
+
encodedData: encodedCallData,
|
|
2272
|
+
args: [node, account],
|
|
2273
|
+
contracts: this._contracts
|
|
2274
|
+
}));
|
|
2275
|
+
}
|
|
2276
|
+
/**
|
|
2277
|
+
* Ask the registry who may write `node`, per DotnsRegistry.isAuthorised —
|
|
2278
|
+
* the contract's own "single source of truth" for owner-gated entry
|
|
2279
|
+
* points (setSubnodeOwner, setSubnodeResolver, setResolver): a non-zero
|
|
2280
|
+
* stored subnode owner must equal `account`; otherwise, for a tokenised
|
|
2281
|
+
* node, the registrar's ERC-721 owner, an operator-for-all delegate, or a
|
|
2282
|
+
* single-token approval all qualify. Strictly wider than a raw
|
|
2283
|
+
* owner()-equality test, which refuses an approved/operator delegate the
|
|
2284
|
+
* chain would accept — the contract's own comment calls operator-for-all
|
|
2285
|
+
* "the common marketplace / escrow delegation path".
|
|
2286
|
+
*
|
|
2287
|
+
* Falls back to owner()-equality when isAuthorised is absent from this
|
|
2288
|
+
* registry deployment (a bare revert on the probe) so a missing accessor
|
|
2289
|
+
* never turns into a refusal. `owner` is always the node's registry-read
|
|
2290
|
+
* owner (for informative messaging), independent of which path decided
|
|
2291
|
+
* `authorised`.
|
|
2292
|
+
*/
|
|
2293
|
+
async checkNodeAuthorization(node, account) {
|
|
2294
|
+
this.ensureConnected();
|
|
2295
|
+
const owner = await withTimeout(
|
|
2296
|
+
this.contractCallNullable(this._contracts.DOTNS_REGISTRY, DOTNS_REGISTRY_ABI, "owner", [node]),
|
|
2297
|
+
3e4,
|
|
2298
|
+
"owner"
|
|
2299
|
+
);
|
|
2300
|
+
const authorised = await this.probeIsAuthorised(node, account);
|
|
2301
|
+
if (authorised !== null) return { authorised, owner };
|
|
2302
|
+
return { authorised: owner !== null && owner.toLowerCase() === account.toLowerCase(), owner };
|
|
2303
|
+
}
|
|
2102
2304
|
/** Live transfer-fee quote. transferFloor is a pure PopRules view — it
|
|
2103
2305
|
* classifies the label and reads both tiers, so it works BEFORE the name is
|
|
2104
2306
|
* registered (unlike quoteTransferFee, which reverts on an unregistered token). */
|
|
@@ -2149,9 +2351,12 @@ var DotNS = class _DotNS {
|
|
|
2149
2351
|
* transferName/transferFrom — the registrar has no token for them. Instead
|
|
2150
2352
|
* the *parent-domain* owner reassigns a subname via the registry's
|
|
2151
2353
|
* setSubnodeOwner (the same call registerSubdomain uses to create it).
|
|
2152
|
-
* Authorisation is on parent
|
|
2153
|
-
*
|
|
2154
|
-
*
|
|
2354
|
+
* Authorisation is on the parent node, not on the subname's current owner —
|
|
2355
|
+
* checked via the registry's own isAuthorised(parentNode, account)
|
|
2356
|
+
* (checkNodeAuthorization), so the connected signer may be the parent's
|
|
2357
|
+
* owner OR an operator-for-all/single-token approval on it, matching what
|
|
2358
|
+
* the chain itself allows on setSubnodeOwner. Idempotent: a no-op when the
|
|
2359
|
+
* recipient already owns it.
|
|
2155
2360
|
*
|
|
2156
2361
|
* Every node here is derived via computeSubnodeIds, which routes through the
|
|
2157
2362
|
* single ensNode() primitive (#1304, see the comment above ensNode) — the
|
|
@@ -2167,17 +2372,13 @@ var DotNS = class _DotNS {
|
|
|
2167
2372
|
const fullName = `${sublabel}.${parentLabel}.${this._tld}`;
|
|
2168
2373
|
assertNotZeroRecipient(toH160, fullName);
|
|
2169
2374
|
const { parentNode, subnode } = computeSubnodeIds(sublabel, parentLabel, this._tld);
|
|
2170
|
-
const parentOwner = await
|
|
2171
|
-
this.contractCallNullable(this._contracts.DOTNS_REGISTRY, DOTNS_REGISTRY_ABI, "owner", [parentNode]),
|
|
2172
|
-
3e4,
|
|
2173
|
-
"owner"
|
|
2174
|
-
);
|
|
2375
|
+
const { authorised: parentAuthorised, owner: parentOwner } = await this.checkNodeAuthorization(parentNode, this.evmAddress);
|
|
2175
2376
|
if (!parentOwner || parentOwner === zeroAddress) {
|
|
2176
2377
|
throw new Error(`Cannot transfer ${fullName}: parent ${parentLabel}.${this._tld} is not registered.`);
|
|
2177
2378
|
}
|
|
2178
|
-
if (
|
|
2379
|
+
if (!parentAuthorised) {
|
|
2179
2380
|
throw new Error(
|
|
2180
|
-
`Cannot transfer ${fullName}: it is a subname, which only the owner of the parent ${parentLabel}.${this._tld} can reassign (subnames are not transferable tokens). Parent is owned by ${parentOwner}, but the signer is ${this.evmAddress}. Sign as the parent owner (e.g. pass its --mnemonic).`
|
|
2381
|
+
`Cannot transfer ${fullName}: it is a subname, which only the owner of the parent ${parentLabel}.${this._tld} (or an account it has approved) can reassign (subnames are not transferable tokens). Parent is owned by ${parentOwner}, but the signer is ${this.evmAddress} and is not approved. Sign as the parent owner, or as an approved account (e.g. pass its --mnemonic).`
|
|
2181
2382
|
);
|
|
2182
2383
|
}
|
|
2183
2384
|
const current = await withTimeout(
|
|
@@ -2190,12 +2391,13 @@ var DotNS = class _DotNS {
|
|
|
2190
2391
|
return { status: "skipped-already-owned" };
|
|
2191
2392
|
}
|
|
2192
2393
|
const subnodeRecord = { parentNode, subLabel: sublabel, parentLabel, owner: toH160 };
|
|
2394
|
+
const { abi: setSubnodeOwnerAbi, args: setSubnodeOwnerArgs } = await this.buildSetSubnodeOwnerCall(subnodeRecord);
|
|
2193
2395
|
const txRes = await this.contractTransaction(
|
|
2194
2396
|
this._contracts.DOTNS_REGISTRY,
|
|
2195
2397
|
0n,
|
|
2196
|
-
|
|
2398
|
+
setSubnodeOwnerAbi,
|
|
2197
2399
|
"setSubnodeOwner",
|
|
2198
|
-
|
|
2400
|
+
setSubnodeOwnerArgs,
|
|
2199
2401
|
statusCallback
|
|
2200
2402
|
);
|
|
2201
2403
|
const after = await withTimeout(
|
|
@@ -2274,9 +2476,10 @@ var DotNS = class _DotNS {
|
|
|
2274
2476
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
2275
2477
|
}
|
|
2276
2478
|
};
|
|
2479
|
+
const { abi: setSubnodeOwnerAbi, args: setSubnodeOwnerArgs } = await this.buildSetSubnodeOwnerCall(subnodeRecord);
|
|
2277
2480
|
const txResolution = await this.submitBatchedContractCalls(
|
|
2278
2481
|
[
|
|
2279
|
-
{ contractAddress: this._contracts.DOTNS_REGISTRY, abi:
|
|
2482
|
+
{ contractAddress: this._contracts.DOTNS_REGISTRY, abi: setSubnodeOwnerAbi, functionName: "setSubnodeOwner", args: setSubnodeOwnerArgs },
|
|
2280
2483
|
{ contractAddress: this._contracts.DOTNS_REGISTRY, abi: DOTNS_REGISTRY_ABI, functionName: "setResolver", args: [subnodeNode, this._contracts.DOTNS_CONTENT_RESOLVER] }
|
|
2281
2484
|
],
|
|
2282
2485
|
(s) => console.log(` ${s}`),
|