bulletin-deploy 0.7.3 → 0.7.4

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.
@@ -0,0 +1,869 @@
1
+ import {
2
+ captureWarning,
3
+ setDeployAttribute,
4
+ withSpan
5
+ } from "./chunk-DHQ3JGF4.js";
6
+ import {
7
+ isTestnetSpecName
8
+ } from "./chunk-JHNW2EKY.js";
9
+
10
+ // src/dotns.ts
11
+ import { spawn } from "child_process";
12
+ import { readFileSync } from "fs";
13
+ import { dirname, join } from "path";
14
+ import { createClient } from "polkadot-api";
15
+ import { getPolkadotSigner } from "polkadot-api/signer";
16
+ import { getWsProvider } from "polkadot-api/ws-provider";
17
+ import { Keyring } from "@polkadot/keyring";
18
+ import { cryptoWaitReady } from "@polkadot/util-crypto";
19
+ import { Binary } from "polkadot-api";
20
+ import {
21
+ encodeFunctionData,
22
+ decodeFunctionResult,
23
+ keccak256,
24
+ toBytes,
25
+ isAddress,
26
+ bytesToHex,
27
+ isHex,
28
+ toHex,
29
+ zeroAddress,
30
+ namehash,
31
+ concatHex
32
+ } from "viem";
33
+ import { CID } from "multiformats/cid";
34
+ import { createRequire } from "module";
35
+ var _require = createRequire(import.meta.url);
36
+ var _dotnsCliPath;
37
+ var _dotnsCliVersion;
38
+ try {
39
+ _dotnsCliPath = _require.resolve("@parity/dotns-cli");
40
+ const pkgDir = dirname(dirname(_dotnsCliPath));
41
+ const pkg = JSON.parse(readFileSync(join(pkgDir, "package.json"), "utf-8"));
42
+ _dotnsCliVersion = pkg.version;
43
+ } catch (err) {
44
+ _dotnsCliVersion = "unknown";
45
+ _dotnsCliPath = "dotns";
46
+ console.warn(`[bulletin-deploy] @parity/dotns-cli not found in node_modules \u2014 falling back to "dotns" in PATH. Install with: npm install @parity/dotns-cli. Underlying: ${err.message}`);
47
+ }
48
+ var RPC_ENDPOINTS = [
49
+ "wss://asset-hub-paseo.dotters.network",
50
+ "wss://sys.ibp.network/asset-hub-paseo",
51
+ "wss://pas-rpc.stakeworld.io/assethub"
52
+ ];
53
+ var CONTRACTS = {
54
+ DOTNS_REGISTRAR: "0x329aAA5b6bEa94E750b2dacBa74Bf41291E6c2BD",
55
+ DOTNS_REGISTRAR_CONTROLLER: "0xd09e0F1c1E6CE8Cf40df929ef4FC778629573651",
56
+ DOTNS_REGISTRY: "0x4Da0d37aBe96C06ab19963F31ca2DC0412057a6f",
57
+ DOTNS_RESOLVER: "0x95645C7fD0fF38790647FE13F87Eb11c1DCc8514",
58
+ DOTNS_CONTENT_RESOLVER: "0x7756DF72CBc7f062e7403cD59e45fBc78bed1cD7",
59
+ DOTNS_REVERSE_RESOLVER: "0x95D57363B491CF743970c640fe419541386ac8BF",
60
+ STORE_FACTORY: "0x030296782F4d3046B080BcB017f01837561D9702",
61
+ POP_RULES: "0x4e8920B1E69d0cEA9b23CBFC87A17Ee6fE02d2d3"
62
+ };
63
+ var DECIMALS = 12n;
64
+ var NATIVE_TO_ETH_RATIO = 1000000n;
65
+ var CONNECTION_TIMEOUT_MS = 3e4;
66
+ var OPERATION_TIMEOUT_MS = 3e5;
67
+ var TX_TIMEOUT_MS = 9e4;
68
+ var TX_CHAIN_TIME_BUDGET_MS = 18e4;
69
+ var TX_WALL_CLOCK_CEILING_MS = 10 * 60 * 1e3;
70
+ var WS_HEARTBEAT_TIMEOUT_MS = 3e5;
71
+ var DOTNS_TX_MAX_ATTEMPTS = 3;
72
+ function classifyTxRetryDecision(err) {
73
+ const msg = err instanceof Error ? err.message : String(err);
74
+ const lower = msg.toLowerCase();
75
+ if (/\bstale\b/.test(lower)) return "retry";
76
+ if (/"type"\s*:\s*"future"|\binvalid::future\b/.test(lower)) return "retry";
77
+ if (lower.includes("websocket") || lower.includes("connection") || lower.includes("socket closed") || lower.includes("disconnect")) return "retry";
78
+ if (lower.includes("timed out") || lower.includes("timeout")) return "retry";
79
+ return "abort";
80
+ }
81
+ var DEFAULT_MNEMONIC = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
82
+ var _rpcIdCounter = 0;
83
+ async function fetchNonceFromEndpoint(rpc, ss58Address) {
84
+ const WS = globalThis.WebSocket ?? (await import("./wrapper-IFSKR7DG.js")).default;
85
+ return new Promise((resolve, reject) => {
86
+ let done = false;
87
+ const settle = (fn, ...args) => {
88
+ if (done) return;
89
+ done = true;
90
+ clearTimeout(timer);
91
+ try {
92
+ ws.close();
93
+ } catch {
94
+ }
95
+ fn(...args);
96
+ };
97
+ const timer = setTimeout(() => settle(reject, new Error(`fetchNonce timed out after 8s for ${rpc}`)), 8e3);
98
+ const ws = new WS(rpc);
99
+ const id = ++_rpcIdCounter;
100
+ ws.onopen = () => ws.send(JSON.stringify({ jsonrpc: "2.0", id, method: "system_accountNextIndex", params: [ss58Address] }));
101
+ ws.onmessage = (e) => {
102
+ const d = typeof e.data === "string" ? e.data : e.data.toString();
103
+ const r = JSON.parse(d);
104
+ if (r.id === id) {
105
+ r.error ? settle(reject, new Error(r.error.message)) : settle(resolve, r.result);
106
+ }
107
+ };
108
+ ws.onerror = () => settle(reject, new Error(`WebSocket to ${rpc} failed`));
109
+ ws.onclose = () => settle(reject, new Error(`WebSocket to ${rpc} closed before response`));
110
+ });
111
+ }
112
+ async function fetchNonce(rpc, ss58Address) {
113
+ if (Array.isArray(rpc)) return Promise.any(rpc.map((ep) => fetchNonceFromEndpoint(ep, ss58Address)));
114
+ return fetchNonceFromEndpoint(rpc, ss58Address);
115
+ }
116
+ async function verifyNonceAdvanced(endpoints, ss58Address, originalNonce) {
117
+ const results = await Promise.allSettled(
118
+ endpoints.map((ep) => fetchNonceFromEndpoint(ep, ss58Address).then((n) => ({ n, ep })))
119
+ );
120
+ for (const r of results) {
121
+ if (r.status === "fulfilled" && r.value.n > originalNonce) {
122
+ return { advanced: true, witnessRpc: r.value.ep };
123
+ }
124
+ }
125
+ return { advanced: false };
126
+ }
127
+ var ProofOfPersonhoodStatus = {
128
+ NoStatus: 0,
129
+ ProofOfPersonhoodLite: 1,
130
+ ProofOfPersonhoodFull: 2,
131
+ Reserved: 3
132
+ };
133
+ var POP_RULES_ABI = [
134
+ { inputs: [{ name: "name", type: "string" }], name: "isBaseNameReserved", outputs: [{ name: "isReserved", type: "bool" }, { name: "reservationOwner", type: "address" }, { name: "expiryTimestamp", type: "uint64" }], stateMutability: "view", type: "function" }
135
+ ];
136
+ var DOTNS_REGISTRY_ABI = [
137
+ { inputs: [{ name: "node", type: "bytes32" }], name: "owner", outputs: [{ name: "", type: "address" }], stateMutability: "view", type: "function" }
138
+ ];
139
+ function convertToHexString(value) {
140
+ if (!value) return "0x";
141
+ if (typeof value?.asHex === "function") return value.asHex();
142
+ if (typeof value?.toHex === "function") return value.toHex();
143
+ if (typeof value === "string" && isHex(value)) return value;
144
+ if (value instanceof Uint8Array) return bytesToHex(value);
145
+ try {
146
+ return toHex(value);
147
+ } catch {
148
+ return "0x";
149
+ }
150
+ }
151
+ function convertToBigInt(value, fallback = 0n) {
152
+ try {
153
+ if (typeof value === "bigint") return value;
154
+ if (typeof value === "number") return BigInt(value);
155
+ if (typeof value === "string") return BigInt(value);
156
+ if (value && typeof value.toString === "function") return BigInt(value.toString());
157
+ return fallback;
158
+ } catch {
159
+ return fallback;
160
+ }
161
+ }
162
+ function normalizeWeight(weight) {
163
+ const referenceTime = weight?.ref_time ?? weight?.refTime ?? 0;
164
+ const proofSize = weight?.proof_size ?? weight?.proofSize ?? 0;
165
+ return { referenceTime: convertToBigInt(referenceTime, 0n), proofSize: convertToBigInt(proofSize, 0n) };
166
+ }
167
+ function extractStorageDepositCharge(rawStorageDeposit) {
168
+ if (!rawStorageDeposit) return 0n;
169
+ if (typeof rawStorageDeposit?.isCharge === "boolean") {
170
+ if (rawStorageDeposit.isCharge && rawStorageDeposit.asCharge != null) return convertToBigInt(rawStorageDeposit.asCharge, 0n);
171
+ return 0n;
172
+ }
173
+ if (rawStorageDeposit.charge != null) return convertToBigInt(rawStorageDeposit.charge, 0n);
174
+ if (rawStorageDeposit.Charge != null) return convertToBigInt(rawStorageDeposit.Charge, 0n);
175
+ if (rawStorageDeposit.value != null) return convertToBigInt(rawStorageDeposit.value, 0n);
176
+ return 0n;
177
+ }
178
+ function unwrapExecutionResult(rawResult) {
179
+ if (!rawResult) return { ok: null, err: null, successFlag: null };
180
+ if (typeof rawResult.success === "boolean") {
181
+ return rawResult.success ? { ok: rawResult.value ?? null, err: null, successFlag: true } : { ok: null, err: rawResult.error ?? rawResult.value ?? null, successFlag: false };
182
+ }
183
+ if (typeof rawResult.isOk === "boolean") {
184
+ return rawResult.isOk ? { ok: rawResult.value ?? null, err: null, successFlag: true } : { ok: null, err: rawResult.value ?? null, successFlag: false };
185
+ }
186
+ if (rawResult.ok != null) return { ok: rawResult.ok, err: null, successFlag: true };
187
+ if (rawResult.err != null) return { ok: null, err: rawResult.err, successFlag: false };
188
+ return { ok: null, err: rawResult, successFlag: null };
189
+ }
190
+ function withTimeout(promise, timeoutMs, operationName) {
191
+ let timer;
192
+ const timeoutPromise = new Promise((_, reject) => {
193
+ timer = setTimeout(() => reject(new Error(`${operationName} timed out after ${timeoutMs}ms`)), timeoutMs);
194
+ });
195
+ return Promise.race([promise, timeoutPromise]).finally(() => clearTimeout(timer));
196
+ }
197
+ var DOT_NODE = "0x3fce7d1364a893e213bc4212792b517ffc88f5b13b86c8ef9c8d390c3a1370ce";
198
+ function convertWeiToNative(weiValue) {
199
+ return weiValue / NATIVE_TO_ETH_RATIO;
200
+ }
201
+ function computeDomainTokenId(label) {
202
+ const labelhash = keccak256(toBytes(label));
203
+ const node = keccak256(concatHex([DOT_NODE, labelhash]));
204
+ return BigInt(node);
205
+ }
206
+ function countTrailingDigits(label) {
207
+ let count = 0;
208
+ for (let i = label.length - 1; i >= 0; i--) {
209
+ const code = label.charCodeAt(i);
210
+ if (code >= 48 && code <= 57) count++;
211
+ else break;
212
+ }
213
+ return count;
214
+ }
215
+ function stripTrailingDigits(label) {
216
+ return label.replace(/\d+$/, "").replace(/-$/, "");
217
+ }
218
+ function sanitizeDomainLabel(label) {
219
+ const trailingDigitCount = countTrailingDigits(label);
220
+ if (trailingDigitCount > 2) {
221
+ const sanitized = stripTrailingDigits(label) + label.slice(-2);
222
+ console.log(` Domain label sanitized: "${label}" \u2192 "${sanitized}" (stripped excess trailing digits)`);
223
+ return sanitized;
224
+ }
225
+ return label;
226
+ }
227
+ function validateDomainLabel(label) {
228
+ if (!/^[a-z0-9-]{3,}$/.test(label)) throw new Error("Invalid domain label: must contain only lowercase letters, digits, and hyphens, min 3 chars");
229
+ if (label.startsWith("-") || label.endsWith("-")) throw new Error("Invalid domain label: cannot start or end with hyphen");
230
+ return sanitizeDomainLabel(label);
231
+ }
232
+ function isCommitmentMature(chainNowSeconds, commitTimestampSeconds, minimumAgeSeconds) {
233
+ return chainNowSeconds > commitTimestampSeconds + minimumAgeSeconds;
234
+ }
235
+ function classifyDotnsLabel(label) {
236
+ const totalLength = label.length;
237
+ const trailingDigits = countTrailingDigits(label);
238
+ if (trailingDigits > 2) {
239
+ return {
240
+ status: ProofOfPersonhoodStatus.Reserved,
241
+ message: `Name has ${trailingDigits} trailing digits; DotNS allows at most 2 trailing digits. Use a base name with 0-2 trailing digits.`
242
+ };
243
+ }
244
+ const baselength = totalLength - trailingDigits;
245
+ if (baselength <= 5) {
246
+ return {
247
+ status: ProofOfPersonhoodStatus.Reserved,
248
+ message: `Base name is ${baselength} char${baselength === 1 ? "" : "s"}; DotNS reserves base names of 5 chars or fewer for governance (PopRules). Use a base name of 6+ chars \u2014 role prefixes like 'rc<N>pool' / 'rc<N>dir' / 'nightly-<role>' work well.`
249
+ };
250
+ }
251
+ if (baselength >= 6 && baselength <= 8) {
252
+ if (trailingDigits === 2) return { status: ProofOfPersonhoodStatus.ProofOfPersonhoodLite, message: "Requires Light personhood verification" };
253
+ return { status: ProofOfPersonhoodStatus.ProofOfPersonhoodFull, message: "Requires Full personhood verification" };
254
+ }
255
+ if (trailingDigits === 2) return { status: ProofOfPersonhoodStatus.NoStatus, message: "Available to all" };
256
+ return { status: ProofOfPersonhoodStatus.ProofOfPersonhoodFull, message: "Requires Full personhood verification" };
257
+ }
258
+ function canRegister(requiredStatus, userStatus, trailingDigits) {
259
+ if (requiredStatus === ProofOfPersonhoodStatus.Reserved) return false;
260
+ if (requiredStatus === ProofOfPersonhoodStatus.ProofOfPersonhoodFull) {
261
+ return userStatus === ProofOfPersonhoodStatus.ProofOfPersonhoodFull;
262
+ }
263
+ if (requiredStatus === ProofOfPersonhoodStatus.ProofOfPersonhoodLite) {
264
+ return userStatus === ProofOfPersonhoodStatus.ProofOfPersonhoodLite || userStatus === ProofOfPersonhoodStatus.ProofOfPersonhoodFull;
265
+ }
266
+ return trailingDigits !== 0 && userStatus !== ProofOfPersonhoodStatus.ProofOfPersonhoodLite;
267
+ }
268
+ function simulateUserStatus(currentStatus, requiredStatus, options) {
269
+ const max = (a, b) => a > b ? a : b;
270
+ if (options.explicitStatus !== void 0) {
271
+ return max(currentStatus, options.explicitStatus);
272
+ }
273
+ if (requiredStatus === ProofOfPersonhoodStatus.NoStatus && currentStatus === ProofOfPersonhoodStatus.ProofOfPersonhoodLite && options.isTestnet) {
274
+ return ProofOfPersonhoodStatus.ProofOfPersonhoodFull;
275
+ }
276
+ if (requiredStatus !== ProofOfPersonhoodStatus.NoStatus) {
277
+ return max(currentStatus, requiredStatus);
278
+ }
279
+ return currentStatus;
280
+ }
281
+ function parseDomainName(input) {
282
+ const name = input.replace(/\.dot$/, "");
283
+ const parts = name.split(".");
284
+ if (parts.length === 1) {
285
+ validateDomainLabel(parts[0]);
286
+ return { isSubdomain: false, label: parts[0], sublabel: null, parentLabel: null, fullName: `${parts[0]}.dot` };
287
+ }
288
+ if (parts.length === 2) {
289
+ validateDomainLabel(parts[0]);
290
+ validateDomainLabel(parts[1]);
291
+ return { isSubdomain: true, label: name, sublabel: parts[0], parentLabel: parts[1], fullName: `${name}.dot` };
292
+ }
293
+ throw new Error(`Invalid domain: only one level of subdomains supported (got ${parts.length} labels)`);
294
+ }
295
+ function parseProofOfPersonhoodStatus(status) {
296
+ const s = (status ?? "none").toLowerCase();
297
+ if (s === "none" || s === "nostatus") return ProofOfPersonhoodStatus.NoStatus;
298
+ if (s === "lite" || s === "poplite") return ProofOfPersonhoodStatus.ProofOfPersonhoodLite;
299
+ if (s === "full" || s === "popfull") return ProofOfPersonhoodStatus.ProofOfPersonhoodFull;
300
+ throw new Error("Invalid status. Use none, lite, or full");
301
+ }
302
+ function popStatusName(status) {
303
+ return Object.keys(ProofOfPersonhoodStatus).find((k) => ProofOfPersonhoodStatus[k] === status) ?? String(status);
304
+ }
305
+ var ReviveClientWrapper = class _ReviveClientWrapper {
306
+ static DRY_RUN_STORAGE_LIMIT = 18446744073709551615n;
307
+ static DRY_RUN_WEIGHT_LIMIT = { ref_time: 18446744073709551615n, proof_size: 18446744073709551615n };
308
+ client;
309
+ mappedAccounts;
310
+ constructor(client) {
311
+ this.client = client;
312
+ this.mappedAccounts = /* @__PURE__ */ new Set();
313
+ }
314
+ async getEvmAddress(substrateAddress) {
315
+ if (isAddress(substrateAddress)) return substrateAddress;
316
+ const address = await this.client.apis.ReviveApi.address(substrateAddress);
317
+ return address.asHex();
318
+ }
319
+ async performDryRunCall(originSubstrateAddress, contractAddress, value, encodedData) {
320
+ if (isAddress(originSubstrateAddress)) throw new Error("performDryRunCall requires SS58 Substrate address, not EVM H160 address");
321
+ const executionResults = await this.client.apis.ReviveApi.call(originSubstrateAddress, Binary.fromHex(contractAddress), value, _ReviveClientWrapper.DRY_RUN_WEIGHT_LIMIT, _ReviveClientWrapper.DRY_RUN_STORAGE_LIMIT, Binary.fromHex(encodedData));
322
+ const { ok, err, successFlag } = unwrapExecutionResult(executionResults.result);
323
+ const flags = ok?.flags ? convertToBigInt(ok.flags, 0n) : 0n;
324
+ const returnData = convertToHexString(ok?.data);
325
+ const didRevert = ok ? (flags & 1n) === 1n : true;
326
+ const gasConsumed = normalizeWeight(executionResults.weight_consumed);
327
+ const gasRequired = normalizeWeight(executionResults.weight_required ?? executionResults.weight_consumed);
328
+ const storageDepositValue = extractStorageDepositCharge(executionResults.storage_deposit);
329
+ const isOk = !!ok && !didRevert;
330
+ const isErr = !ok || didRevert || !!err || (typeof successFlag === "boolean" ? !successFlag : false);
331
+ return { gasConsumed, gasRequired, storageDeposit: { value: storageDepositValue }, result: { isOk, isErr, value: { data: ok ? returnData : "0x", flags: ok ? flags : 1n } } };
332
+ }
333
+ async checkIfAccountMapped(substrateAddress) {
334
+ try {
335
+ const evmAddress = await this.getEvmAddress(substrateAddress);
336
+ const key = Binary.fromHex(evmAddress);
337
+ const mappedAccount = await this.client.query.Revive.OriginalAccount.getValue(key);
338
+ return mappedAccount !== null && mappedAccount !== void 0;
339
+ } catch {
340
+ return false;
341
+ }
342
+ }
343
+ };
344
+ async function runDotnsCli(argv, env) {
345
+ return new Promise((resolve, reject) => {
346
+ const proc = spawn(process.execPath, [_dotnsCliPath, ...argv], {
347
+ stdio: ["ignore", "pipe", "pipe"],
348
+ env: { ...process.env, ...env }
349
+ });
350
+ let stdout = "";
351
+ let stderr = "";
352
+ proc.stdout.on("data", (chunk) => {
353
+ stdout += chunk.toString();
354
+ });
355
+ proc.stderr.on("data", (chunk) => {
356
+ stderr += chunk.toString();
357
+ });
358
+ proc.on("close", (code) => {
359
+ const out = stdout.trim();
360
+ const err = stderr.trim();
361
+ if (code === 0) {
362
+ if (!out) {
363
+ resolve({});
364
+ return;
365
+ }
366
+ try {
367
+ resolve(JSON.parse(out));
368
+ } catch {
369
+ resolve({ raw: out });
370
+ }
371
+ return;
372
+ }
373
+ let errorMsg = `dotns ${argv[0] ?? ""} failed (exit ${code})`;
374
+ if (err) {
375
+ try {
376
+ const parsed = JSON.parse(err);
377
+ if (parsed?.error) errorMsg = parsed.error;
378
+ } catch {
379
+ errorMsg = err.slice(0, 500) || errorMsg;
380
+ }
381
+ } else if (out) {
382
+ errorMsg = out.slice(0, 500);
383
+ }
384
+ reject(new Error(errorMsg));
385
+ });
386
+ proc.on("error", (err) => reject(new Error(`Failed to spawn dotns CLI: ${err.message}`)));
387
+ });
388
+ }
389
+ function buildAuthEnv(opts) {
390
+ const env = {};
391
+ if (opts.keyUri) {
392
+ env.DOTNS_KEY_URI = opts.keyUri;
393
+ } else if (opts.mnemonic) {
394
+ env.DOTNS_MNEMONIC = opts.mnemonic;
395
+ }
396
+ return env;
397
+ }
398
+ function rpcFlag(rpc) {
399
+ return rpc ? ["--rpc", rpc] : [];
400
+ }
401
+ var DotNS = class {
402
+ client;
403
+ clientWrapper;
404
+ rpc;
405
+ substrateAddress;
406
+ evmAddress;
407
+ signer;
408
+ connected;
409
+ // Stored credentials for CLI subprocess calls. Never logged or exposed.
410
+ _mnemonic = null;
411
+ _keyUri = null;
412
+ constructor() {
413
+ this.client = null;
414
+ this.clientWrapper = null;
415
+ this.rpc = null;
416
+ this.substrateAddress = null;
417
+ this.evmAddress = null;
418
+ this.signer = null;
419
+ this.connected = false;
420
+ }
421
+ async connect(options = {}) {
422
+ if (options.signer || options.signerAddress) {
423
+ throw new Error("External signer mode is not supported with dotns-cli subprocess \u2014 tracked as a follow-up (#158 regression notice)");
424
+ }
425
+ const rpc = options.rpc || process.env.DOTNS_RPC || RPC_ENDPOINTS[0];
426
+ this.rpc = rpc;
427
+ const mnemonicArg = options.mnemonic || process.env.DOTNS_MNEMONIC || process.env.MNEMONIC;
428
+ const keyUriArg = options.keyUri || process.env.DOTNS_KEY_URI;
429
+ const source = keyUriArg || mnemonicArg || DEFAULT_MNEMONIC;
430
+ const isKeyUri = Boolean(keyUriArg);
431
+ this._keyUri = isKeyUri ? source : null;
432
+ this._mnemonic = isKeyUri ? null : source;
433
+ if (options.derivationPath && !isKeyUri && this._mnemonic) {
434
+ this._keyUri = `${this._mnemonic}${options.derivationPath}`;
435
+ this._mnemonic = null;
436
+ }
437
+ const authEnv = buildAuthEnv({ mnemonic: this._mnemonic ?? void 0, keyUri: this._keyUri ?? void 0 });
438
+ const popInfo = await withTimeout(
439
+ runDotnsCli(["pop", "info", "--json", ...rpcFlag(rpc)], authEnv),
440
+ CONNECTION_TIMEOUT_MS,
441
+ "pop info (connect)"
442
+ );
443
+ this.substrateAddress = popInfo.substrate;
444
+ this.evmAddress = popInfo.evm;
445
+ console.log(` SS58 Address: ${this.substrateAddress}`);
446
+ console.log(` H160 Address: ${this.evmAddress}`);
447
+ try {
448
+ await cryptoWaitReady();
449
+ const keyring = new Keyring({ type: "sr25519" });
450
+ const account = isKeyUri || this._keyUri ? keyring.addFromUri(this._keyUri ?? source) : keyring.addFromMnemonic(source);
451
+ this.signer = getPolkadotSigner(account.publicKey, "Sr25519", async (input) => account.sign(input));
452
+ this.client = createClient(getWsProvider(rpc, { heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS }));
453
+ const unsafeApi = this.client.getUnsafeApi();
454
+ this.clientWrapper = new ReviveClientWrapper(unsafeApi);
455
+ } catch (e) {
456
+ captureWarning("DotNS polkadot-api client setup failed (contractCall / checkIfAccountMapped unavailable)", { error: e.message?.slice(0, 200) });
457
+ }
458
+ try {
459
+ await runDotnsCli(["account", "map", ...rpcFlag(rpc)], authEnv);
460
+ } catch (e) {
461
+ const msg = e.message?.toLowerCase() ?? "";
462
+ if (!msg.includes("already") && !msg.includes("mapped")) {
463
+ captureWarning("account map failed during connect", { error: e.message?.slice(0, 200) });
464
+ }
465
+ }
466
+ try {
467
+ setDeployAttribute("deploy.dotns_cli.version", _dotnsCliVersion);
468
+ } catch {
469
+ }
470
+ this.connected = true;
471
+ return this;
472
+ }
473
+ ensureConnected() {
474
+ if (!this.connected) throw new Error("Not connected. Call connect() first.");
475
+ }
476
+ // Returns true when the DotNS chain (Asset Hub) reports a testnet spec_name.
477
+ // Used to gate test-only behaviors like self-granting Full PoP on a Lite
478
+ // signer for a NoStatus label.
479
+ _testnetCache = null;
480
+ async isTestnet() {
481
+ if (this._testnetCache !== null) return this._testnetCache;
482
+ this.ensureConnected();
483
+ if (this.clientWrapper) {
484
+ try {
485
+ const version = await this.clientWrapper.client.constants.System.Version();
486
+ const raw = version?.spec_name ?? version?.specName;
487
+ const specName = typeof raw === "string" ? raw : raw?.asText?.() ?? String(raw ?? "");
488
+ this._testnetCache = isTestnetSpecName(specName);
489
+ return this._testnetCache;
490
+ } catch {
491
+ }
492
+ }
493
+ const rpc = this.rpc ?? "";
494
+ this._testnetCache = isTestnetSpecName(rpc) || rpc.includes("paseo") || rpc.includes("westend") || rpc.includes("rococo");
495
+ return this._testnetCache;
496
+ }
497
+ async contractCall(contractAddress, contractAbi, functionName, args = []) {
498
+ this.ensureConnected();
499
+ if (!this.clientWrapper) throw new Error("contractCall: polkadot-api client not available");
500
+ const encodedCallData = encodeFunctionData({ abi: contractAbi, functionName, args });
501
+ const callResult = await this.clientWrapper.performDryRunCall(this.substrateAddress, contractAddress, 0n, encodedCallData);
502
+ if (!callResult.result.isOk) {
503
+ const errorData = callResult.result.value;
504
+ const flags = errorData?.flags ?? 0n;
505
+ const revertData = errorData?.data ?? "0x";
506
+ const isRevert = (flags & 1n) === 1n;
507
+ if (isRevert) throw new Error(`Contract reverted (flags=${flags}) with data: ${revertData}`);
508
+ throw new Error(`Contract call failed (flags=${flags}) with data: ${revertData}`);
509
+ }
510
+ return decodeFunctionResult({ abi: contractAbi, functionName, data: callResult.result.value.data });
511
+ }
512
+ async checkOwnership(label, ownerAddress = null) {
513
+ this.ensureConnected();
514
+ const checkAddress = (ownerAddress || this.evmAddress).toLowerCase();
515
+ try {
516
+ const result = await runDotnsCli(
517
+ ["lookup", "owner-of", label, "--json", ...rpcFlag(this.rpc)]
518
+ );
519
+ if (!result.registered) return { owned: false, owner: null };
520
+ const owner = result.ownerEvm;
521
+ const owned = owner.toLowerCase() === checkAddress;
522
+ return { owned, owner };
523
+ } catch {
524
+ return { owned: false, owner: null };
525
+ }
526
+ }
527
+ async getUserPopStatus(ownerAddress = null) {
528
+ this.ensureConnected();
529
+ if (ownerAddress && ownerAddress.toLowerCase() !== (this.evmAddress ?? "").toLowerCase()) {
530
+ if (!this.clientWrapper) return 0;
531
+ return 0;
532
+ }
533
+ const authEnv = buildAuthEnv({ mnemonic: this._mnemonic ?? void 0, keyUri: this._keyUri ?? void 0 });
534
+ const info = await runDotnsCli(
535
+ ["pop", "info", "--json", ...rpcFlag(this.rpc)],
536
+ authEnv
537
+ );
538
+ return info.statusCode ?? 0;
539
+ }
540
+ async setUserPopStatus(status) {
541
+ this.ensureConnected();
542
+ console.log(`
543
+ Checking current PoP status...`);
544
+ const currentStatus = await this.getUserPopStatus();
545
+ const currentStatusName = popStatusName(currentStatus);
546
+ const desiredStatusName = popStatusName(status);
547
+ console.log(` Current: ${currentStatusName}`);
548
+ console.log(` Desired: ${desiredStatusName}`);
549
+ if (currentStatus === status) {
550
+ console.log(` Status already set, skipping update`);
551
+ return;
552
+ }
553
+ if (currentStatus > status) {
554
+ console.log(` Current status already satisfies desired, skipping downgrade`);
555
+ return;
556
+ }
557
+ console.log(` Setting PoP status to ${desiredStatusName}...`);
558
+ const statusStr = desiredStatusName.replace("ProofOfPersonhood", "").toLowerCase() || "none";
559
+ const authEnv = buildAuthEnv({ mnemonic: this._mnemonic ?? void 0, keyUri: this._keyUri ?? void 0 });
560
+ const result = await runDotnsCli(
561
+ ["pop", "set", statusStr, "--json", ...rpcFlag(this.rpc)],
562
+ authEnv
563
+ );
564
+ console.log(` PoP status set: ${result.statusCode}`);
565
+ }
566
+ async checkSubdomainOwnership(sublabel, parentLabel) {
567
+ this.ensureConnected();
568
+ if (!this.clientWrapper) return { owned: false, owner: null };
569
+ const node = namehash(`${sublabel}.${parentLabel}.dot`);
570
+ try {
571
+ const owner = await withTimeout(this.contractCall(CONTRACTS.DOTNS_REGISTRY, DOTNS_REGISTRY_ABI, "owner", [node]), 3e4, "owner");
572
+ if (!owner || owner === zeroAddress) return { owned: false, owner: null };
573
+ const owned = owner.toLowerCase() === this.evmAddress.toLowerCase();
574
+ return { owned, owner };
575
+ } catch {
576
+ return { owned: false, owner: null };
577
+ }
578
+ }
579
+ async registerSubdomain(sublabel, parentLabel) {
580
+ return withSpan("deploy.dotns.register-subdomain", `2a. register ${sublabel}.${parentLabel}.dot`, {}, async () => {
581
+ this.ensureConnected();
582
+ console.log(`
583
+ Registering subdomain ${sublabel}.${parentLabel}.dot...`);
584
+ const authEnv = buildAuthEnv({ mnemonic: this._mnemonic ?? void 0, keyUri: this._keyUri ?? void 0 });
585
+ const result = await runDotnsCli(
586
+ ["register", "subname", "-n", sublabel, "-p", parentLabel, "--json", ...rpcFlag(this.rpc)],
587
+ authEnv
588
+ );
589
+ console.log(` Subdomain registered: ${result.domain}`);
590
+ return { sublabel, parentLabel, owner: result.owner ?? this.evmAddress };
591
+ });
592
+ }
593
+ async setContenthash(domainName, contenthashHex) {
594
+ return withSpan("deploy.dotns.set-contenthash", "2b. set-contenthash", {}, async () => {
595
+ this.ensureConnected();
596
+ const node = namehash(`${domainName}.dot`);
597
+ let ipfsCid = null;
598
+ if (contenthashHex && contenthashHex !== "0x") {
599
+ const bytes = Buffer.from(contenthashHex.slice(2), "hex");
600
+ if (bytes[0] === 227 && bytes.length >= 4) {
601
+ const cidBytes = bytes.slice(2);
602
+ ipfsCid = CID.decode(cidBytes).toString();
603
+ }
604
+ }
605
+ if (!ipfsCid) throw new Error(`setContenthash: cannot decode contenthash ${contenthashHex} to an IPFS CID`);
606
+ console.log(` Setting contenthash: ${ipfsCid}`);
607
+ const authEnv = buildAuthEnv({ mnemonic: this._mnemonic ?? void 0, keyUri: this._keyUri ?? void 0 });
608
+ const setResult = await runDotnsCli(
609
+ ["content", "set", domainName, ipfsCid, "--json", ...rpcFlag(this.rpc)],
610
+ authEnv
611
+ );
612
+ console.log(` Tx: ${setResult.txHash}`);
613
+ const viewResult = await runDotnsCli(
614
+ ["content", "view", domainName, "--json", ...rpcFlag(this.rpc)]
615
+ );
616
+ const onChainCid = viewResult.cid;
617
+ if (!onChainCid || onChainCid !== ipfsCid) {
618
+ throw new Error(
619
+ `Post-deploy verification failed for ${domainName}.dot: on-chain CID is ${onChainCid ?? "null"}, not the ${ipfsCid} we just wrote. The setContenthash tx may have silently failed, or another party overwrote the domain. Re-run the deploy to retry.`
620
+ );
621
+ }
622
+ console.log(` Verified on-chain: ${ipfsCid}
623
+ `);
624
+ return { node };
625
+ });
626
+ }
627
+ async getContenthash(domainName) {
628
+ this.ensureConnected();
629
+ const result = await runDotnsCli(
630
+ ["content", "view", domainName, "--json", ...rpcFlag(this.rpc)]
631
+ );
632
+ return result.contenthash ?? "0x";
633
+ }
634
+ // View-only readiness check. Runs every chain read needed to predict whether
635
+ // `register(label)` will succeed, so the caller can fail-fast BEFORE the
636
+ // Bulletin chunk upload. Never writes to chain. See issue #100.
637
+ async preflight(label, explicitStatusOverride) {
638
+ return withSpan("deploy.dotns.preflight", `preflight ${label}.dot`, {}, async () => {
639
+ this.ensureConnected();
640
+ const validated = validateDomainLabel(label);
641
+ const trailingDigits = countTrailingDigits(validated);
642
+ const baselength = validated.length - trailingDigits;
643
+ const classification = classifyDotnsLabel(validated);
644
+ if (classification.status === ProofOfPersonhoodStatus.Reserved) {
645
+ const sanitizeTrail = label !== validated ? `Input "${label}" was sanitized to "${validated}" (excess trailing digits trimmed). ` : "";
646
+ return {
647
+ label: validated,
648
+ classification,
649
+ userStatus: 0,
650
+ trailingDigits,
651
+ baselength,
652
+ isAvailable: false,
653
+ existingOwner: null,
654
+ isBaseNameReserved: false,
655
+ reservationOwner: null,
656
+ isTestnet: false,
657
+ canProceed: false,
658
+ reason: `${sanitizeTrail}${classification.message}`,
659
+ plannedAction: "abort",
660
+ needsPopUpgrade: false
661
+ };
662
+ }
663
+ const baseName = stripTrailingDigits(validated);
664
+ const [userStatus, baseReservation, ownership, isTestnet] = await Promise.all([
665
+ this.getUserPopStatus(),
666
+ withTimeout(this.contractCall(CONTRACTS.POP_RULES, POP_RULES_ABI, "isBaseNameReserved", [baseName]), 3e4, "isBaseNameReserved"),
667
+ this.checkOwnership(validated),
668
+ this.isTestnet()
669
+ ]);
670
+ const [isReserved, reservationOwnerRaw] = baseReservation;
671
+ const reservationOwner = isReserved ? reservationOwnerRaw.toLowerCase() : null;
672
+ const ownerRaw = ownership.owner?.toLowerCase() ?? null;
673
+ const existingOwner = ownerRaw && ownerRaw !== zeroAddress ? ownerRaw : null;
674
+ const selfAddress = this.evmAddress.toLowerCase();
675
+ if (existingOwner !== null && existingOwner !== selfAddress) {
676
+ return {
677
+ label: validated,
678
+ classification,
679
+ userStatus,
680
+ trailingDigits,
681
+ baselength,
682
+ isAvailable: false,
683
+ existingOwner,
684
+ isBaseNameReserved: isReserved,
685
+ reservationOwner,
686
+ isTestnet,
687
+ canProceed: false,
688
+ reason: `Domain ${validated}.dot is already owned by ${existingOwner}.`,
689
+ plannedAction: "abort",
690
+ needsPopUpgrade: false
691
+ };
692
+ }
693
+ if (existingOwner !== null && existingOwner === selfAddress) {
694
+ return {
695
+ label: validated,
696
+ classification,
697
+ userStatus,
698
+ trailingDigits,
699
+ baselength,
700
+ isAvailable: true,
701
+ existingOwner,
702
+ isBaseNameReserved: isReserved,
703
+ reservationOwner,
704
+ isTestnet,
705
+ canProceed: true,
706
+ plannedAction: "already-owned-by-us",
707
+ needsPopUpgrade: false
708
+ };
709
+ }
710
+ if (isReserved && reservationOwner !== selfAddress) {
711
+ return {
712
+ label: validated,
713
+ classification,
714
+ userStatus,
715
+ trailingDigits,
716
+ baselength,
717
+ isAvailable: true,
718
+ existingOwner: null,
719
+ isBaseNameReserved: true,
720
+ reservationOwner,
721
+ isTestnet,
722
+ canProceed: false,
723
+ reason: `Base name ${baseName} is reserved for ${reservationOwner}.`,
724
+ plannedAction: "abort",
725
+ needsPopUpgrade: false
726
+ };
727
+ }
728
+ const explicitStatus = explicitStatusOverride ?? process.env.DOTNS_STATUS;
729
+ const explicitStatusNum = explicitStatus ? parseProofOfPersonhoodStatus(explicitStatus) : void 0;
730
+ const targetPopStatus = simulateUserStatus(userStatus, classification.status, { explicitStatus: explicitStatusNum, isTestnet });
731
+ if (!canRegister(classification.status, targetPopStatus, trailingDigits)) {
732
+ const className = popStatusName(classification.status);
733
+ return {
734
+ label: validated,
735
+ classification,
736
+ userStatus,
737
+ trailingDigits,
738
+ baselength,
739
+ isAvailable: true,
740
+ existingOwner: null,
741
+ isBaseNameReserved: isReserved,
742
+ reservationOwner,
743
+ isTestnet,
744
+ canProceed: false,
745
+ reason: `${validated}.dot classifies as ${className}; this signer cannot register it (PopRules.priceWithCheck gate). Remediations: use a signer with Full PoP, or pick a base name of 6-8 chars + trailing 00 (which classifies as PopLite).`,
746
+ plannedAction: "abort",
747
+ needsPopUpgrade: false,
748
+ targetPopStatus
749
+ };
750
+ }
751
+ return {
752
+ label: validated,
753
+ classification,
754
+ userStatus,
755
+ trailingDigits,
756
+ baselength,
757
+ isAvailable: true,
758
+ existingOwner: null,
759
+ isBaseNameReserved: isReserved,
760
+ reservationOwner,
761
+ isTestnet,
762
+ canProceed: true,
763
+ plannedAction: "register",
764
+ needsPopUpgrade: targetPopStatus !== userStatus,
765
+ targetPopStatus
766
+ };
767
+ });
768
+ }
769
+ async register(label, options = {}) {
770
+ return withSpan("deploy.dotns.register", `2a. register ${label}.dot`, {}, async () => {
771
+ if (!this.connected) await this.connect(options);
772
+ label = validateDomainLabel(label);
773
+ const trailingDigitCount = countTrailingDigits(label);
774
+ const preClassification = classifyDotnsLabel(label);
775
+ if (preClassification.status === ProofOfPersonhoodStatus.NoStatus) {
776
+ const userStatus = await this.getUserPopStatus();
777
+ if (trailingDigitCount === 0 || userStatus === ProofOfPersonhoodStatus.ProofOfPersonhoodLite) {
778
+ throw new Error("Personhood Lite cannot register base names \u2014 this name class requires a Full or NoStatus signer");
779
+ }
780
+ }
781
+ const explicitStatus = options.status || process.env.DOTNS_STATUS;
782
+ const reverse = options.reverse ?? (process.env.DOTNS_REVERSE ?? "false").toLowerCase() === "true";
783
+ const statusArgs = explicitStatus ? ["-s", explicitStatus] : [];
784
+ const reverseArgs = reverse ? ["-r"] : [];
785
+ console.log(`
786
+ Registering ${label}.dot via dotns CLI...`);
787
+ const authEnv = buildAuthEnv({ mnemonic: this._mnemonic ?? void 0, keyUri: this._keyUri ?? void 0 });
788
+ const userSetBuffer = !!process.env.DOTNS_COMMITMENT_BUFFER;
789
+ if (!userSetBuffer) {
790
+ authEnv.DOTNS_COMMITMENT_BUFFER = "30";
791
+ }
792
+ const runRegister = async (bufferSeconds) => {
793
+ const env = bufferSeconds ? { ...authEnv, DOTNS_COMMITMENT_BUFFER: bufferSeconds } : authEnv;
794
+ return await runDotnsCli(
795
+ ["register", "domain", "-n", label, "--json", ...statusArgs, ...reverseArgs, ...rpcFlag(this.rpc)],
796
+ env
797
+ );
798
+ };
799
+ let result;
800
+ try {
801
+ result = await runRegister();
802
+ } catch (err) {
803
+ const msg = err.message;
804
+ if (!/CommitmentTooNew/i.test(msg)) throw err;
805
+ const retryBuffer = userSetBuffer ? void 0 : "60";
806
+ console.log(` CommitmentTooNew on first attempt \u2014 retrying register once${retryBuffer ? ` with DOTNS_COMMITMENT_BUFFER=${retryBuffer}s` : ""} (block-time lag typically resolves within a block or two).`);
807
+ try {
808
+ result = await runRegister(retryBuffer);
809
+ } catch (retryErr) {
810
+ const retryMsg = retryErr.message;
811
+ if (!/CommitmentTooNew/i.test(retryMsg)) throw retryErr;
812
+ throw new Error(
813
+ `DotNS commit-reveal race: the chain's block timestamps are lagging wall-clock by more than 30s across two register attempts. This is usually a transient block-production slowdown. Retry in a minute, or set DOTNS_COMMITMENT_BUFFER=60 (or higher) to absorb longer drift. Upstream fix tracked at paritytech/dotns-sdk#105. Underlying: ${retryMsg}`
814
+ );
815
+ }
816
+ }
817
+ console.log(`
818
+ Registration complete! Owner: ${result.owner}`);
819
+ return { label, owner: result.owner };
820
+ });
821
+ }
822
+ disconnect() {
823
+ if (this.client) {
824
+ this.client.destroy();
825
+ this.client = null;
826
+ this.clientWrapper = null;
827
+ this.connected = false;
828
+ }
829
+ this._mnemonic = null;
830
+ this._keyUri = null;
831
+ }
832
+ };
833
+ var dotns = new DotNS();
834
+
835
+ export {
836
+ RPC_ENDPOINTS,
837
+ CONTRACTS,
838
+ DECIMALS,
839
+ NATIVE_TO_ETH_RATIO,
840
+ CONNECTION_TIMEOUT_MS,
841
+ OPERATION_TIMEOUT_MS,
842
+ TX_TIMEOUT_MS,
843
+ TX_CHAIN_TIME_BUDGET_MS,
844
+ TX_WALL_CLOCK_CEILING_MS,
845
+ WS_HEARTBEAT_TIMEOUT_MS,
846
+ DOTNS_TX_MAX_ATTEMPTS,
847
+ classifyTxRetryDecision,
848
+ DEFAULT_MNEMONIC,
849
+ fetchNonce,
850
+ verifyNonceAdvanced,
851
+ ProofOfPersonhoodStatus,
852
+ DOT_NODE,
853
+ convertWeiToNative,
854
+ computeDomainTokenId,
855
+ countTrailingDigits,
856
+ stripTrailingDigits,
857
+ sanitizeDomainLabel,
858
+ validateDomainLabel,
859
+ isCommitmentMature,
860
+ classifyDotnsLabel,
861
+ canRegister,
862
+ simulateUserStatus,
863
+ parseDomainName,
864
+ parseProofOfPersonhoodStatus,
865
+ popStatusName,
866
+ runDotnsCli,
867
+ DotNS,
868
+ dotns
869
+ };