@parity/dotns-cli 0.6.4 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +34 -0
  2. package/dist/cli.js +534 -29
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -118,6 +118,40 @@ dotns --password test-password register domain --account default --name coolname
118
118
 
119
119
  # With reverse record
120
120
  dotns --password test-password register domain --account default --name coolname42 --reverse
121
+
122
+ # Auto-retry on failure, resuming from the cached commitment (here, up to 3 times)
123
+ dotns --password test-password register domain --account default --name coolname42 --retry 3
124
+ ```
125
+
126
+ ### Resume and manage commitments
127
+
128
+ Registration is a two-step commit-reveal: the commit lands on-chain, then after the
129
+ minimum commitment age the reveal completes the mint. If the reveal is interrupted
130
+ (crash, network drop, closed terminal), the commitment is cached locally so it can be
131
+ resumed rather than restarted. The reveal secret is encrypted at rest with your
132
+ credential (keystore password, mnemonic, or key URI); the rest of the record is
133
+ plaintext so it can be listed and cleared without unlocking.
134
+
135
+ Records live under `~/.dotns/registrations/` (override with `DOTNS_REGISTRATION_DIR`).
136
+
137
+ ```bash
138
+ # Resume the most recent interrupted registration
139
+ dotns --password test-password register retry --account default
140
+
141
+ # Resume a specific cached commitment by name
142
+ dotns --password test-password register retry coolname42 --account default
143
+
144
+ # List cached commitments and their on-chain status
145
+ dotns register list
146
+
147
+ # Review cached commitments: purges completed ones, then prompts for any pending
148
+ dotns --password test-password register clear --account default
149
+
150
+ # Non-interactively discard pending commitments
151
+ dotns register clear --discard
152
+
153
+ # Non-interactively complete pending commitments
154
+ dotns --password test-password register clear --register --account default
121
155
  ```
122
156
 
123
157
  ### Register Subnames
package/dist/cli.js CHANGED
@@ -267174,6 +267174,30 @@ async function waitForMinimumCommitmentAge(clientWrapper, originSubstrateAddress
267174
267174
  waitSpinner.fail("Commitment age verification timed out");
267175
267175
  throw new Error(`Commitment still too new after ${waitSeconds + COMMITMENT_POLL_TIMEOUT_MS / 1000}s. The chain's block timestamps may be advancing slower than expected. Try increasing --commitment-buffer or DOTNS_COMMITMENT_BUFFER.`);
267176
267176
  }
267177
+ async function readDomainOwner(clientWrapper, originSubstrateAddress, label) {
267178
+ const tokenId = computeDomainTokenId(label);
267179
+ try {
267180
+ return await withTimeout(performContractCall(clientWrapper, originSubstrateAddress, CONTRACTS.DOTNS_REGISTRAR, DOTNS_REGISTRAR_ABI, "ownerOf", [tokenId]), 30000, "ownerOf");
267181
+ } catch {
267182
+ return zeroAddress;
267183
+ }
267184
+ }
267185
+ async function readCommitmentStatus(clientWrapper, originSubstrateAddress, commitment) {
267186
+ const toNumber = (value3) => typeof value3 === "bigint" ? Number(value3) : value3;
267187
+ const [minAge, maxAge, committedAt] = await Promise.all([
267188
+ withTimeout(performContractCall(clientWrapper, originSubstrateAddress, CONTRACTS.DOTNS_REGISTRAR_CONTROLLER, DOTNS_REGISTRAR_CONTROLLER_ABI, "minCommitmentAge", []), 30000, "minCommitmentAge"),
267189
+ withTimeout(performContractCall(clientWrapper, originSubstrateAddress, CONTRACTS.DOTNS_REGISTRAR_CONTROLLER, DOTNS_REGISTRAR_CONTROLLER_ABI, "maxCommitmentAge", []), 30000, "maxCommitmentAge"),
267190
+ withTimeout(performContractCall(clientWrapper, originSubstrateAddress, CONTRACTS.DOTNS_REGISTRAR_CONTROLLER, DOTNS_REGISTRAR_CONTROLLER_ABI, "commitments", [commitment]), 30000, "commitments")
267191
+ ]);
267192
+ const timestampQuery = clientWrapper.client.query?.Timestamp?.Now;
267193
+ const nowSeconds = timestampQuery?.getValue ? Math.floor(Number(await timestampQuery.getValue()) / 1000) : Math.floor(Date.now() / 1000);
267194
+ return {
267195
+ committedTimestampSeconds: toNumber(committedAt),
267196
+ nowSeconds,
267197
+ minAgeSeconds: toNumber(minAge),
267198
+ maxAgeSeconds: toNumber(maxAge)
267199
+ };
267200
+ }
267177
267201
  async function getUserProofOfPersonhoodStatus(clientWrapper, originSubstrateAddress, ownerAddress) {
267178
267202
  const personhoodInfo = await withTimeout(performContractCall(clientWrapper, originSubstrateAddress, PERSONHOOD_PRECOMPILE_ADDRESS, PERSONHOOD_ABI, "personhoodStatus", [ownerAddress, PERSONHOOD_CONTEXT]), 30000, "personhoodStatus");
267179
267203
  return convertToProofOfPersonhoodStatus(getPersonhoodStatusValue(personhoodInfo));
@@ -267456,6 +267480,95 @@ async function transferDomain(clientWrapper, originSubstrateAddress, signer, fro
267456
267480
  // src/cli/commands/register.ts
267457
267481
  init_source();
267458
267482
  init__esm();
267483
+ init_formatting();
267484
+
267485
+ // src/commands/registrationManifest.ts
267486
+ init_source();
267487
+ import os12 from "node:os";
267488
+ import path10 from "node:path";
267489
+ import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync, existsSync } from "node:fs";
267490
+ init_formatting();
267491
+ var MANIFEST_EXTENSION2 = ".json";
267492
+ function getManifestDirectory2() {
267493
+ const configured = process.env.DOTNS_REGISTRATION_DIR;
267494
+ if (configured && configured.trim().length > 0) {
267495
+ return path10.resolve(configured);
267496
+ }
267497
+ return path10.join(os12.homedir(), ".dotns", "registrations");
267498
+ }
267499
+ function recordId(env3, caller, label) {
267500
+ return `${env3}__${caller.toLowerCase()}__${label.toLowerCase()}`;
267501
+ }
267502
+ function recordPath(id) {
267503
+ return path10.join(getManifestDirectory2(), `${id}${MANIFEST_EXTENSION2}`);
267504
+ }
267505
+ function belongsTo(record2, env3, caller) {
267506
+ return record2.env === env3 && record2.caller.toLowerCase() === caller.toLowerCase();
267507
+ }
267508
+ function resolveManifestCredential(options2) {
267509
+ return options2.password || process.env.DOTNS_KEYSTORE_PASSWORD || options2.mnemonic || options2.keyUri || null;
267510
+ }
267511
+ function saveCommitmentRecord(params2) {
267512
+ const record2 = {
267513
+ env: params2.env,
267514
+ caller: params2.caller,
267515
+ label: params2.label,
267516
+ owner: params2.owner,
267517
+ reserved: params2.reserved,
267518
+ governance: params2.governance,
267519
+ commitmentHash: params2.commitmentHash,
267520
+ commitTxHash: params2.commitTxHash,
267521
+ committedAtIso: params2.committedAtIso,
267522
+ transferDestination: params2.transferDestination,
267523
+ encryptedSecret: encryptKeystorePayload({ secret: params2.secret }, params2.credential)
267524
+ };
267525
+ mkdirSync(getManifestDirectory2(), { recursive: true });
267526
+ writeFileSync(recordPath(recordId(params2.env, params2.caller, params2.label)), JSON.stringify(record2, null, 2), "utf8");
267527
+ }
267528
+ function loadCommitmentRecords(env3, caller) {
267529
+ const dir = getManifestDirectory2();
267530
+ if (!existsSync(dir))
267531
+ return [];
267532
+ const records = [];
267533
+ for (const file of readdirSync(dir)) {
267534
+ if (!file.endsWith(MANIFEST_EXTENSION2))
267535
+ continue;
267536
+ try {
267537
+ const parsed2 = JSON.parse(readFileSync(path10.join(dir, file), "utf8"));
267538
+ if (parsed2?.label && parsed2?.encryptedSecret && belongsTo(parsed2, env3, caller)) {
267539
+ records.push(parsed2);
267540
+ }
267541
+ } catch (error2) {
267542
+ console.warn(source_default.yellow(` ⚠ Skipping unreadable registration cache file ${file}: ${formatErrorMessage(error2)}`));
267543
+ }
267544
+ }
267545
+ return records.sort((a2, b) => b.committedAtIso.localeCompare(a2.committedAtIso));
267546
+ }
267547
+ function findCommitmentRecord(env3, caller, label) {
267548
+ const file = recordPath(recordId(env3, caller, label));
267549
+ if (!existsSync(file))
267550
+ return null;
267551
+ try {
267552
+ const parsed2 = JSON.parse(readFileSync(file, "utf8"));
267553
+ return belongsTo(parsed2, env3, caller) ? parsed2 : null;
267554
+ } catch (error2) {
267555
+ console.warn(source_default.yellow(` ⚠ Could not read cached commitment for ${label}: ${formatErrorMessage(error2)}`));
267556
+ return null;
267557
+ }
267558
+ }
267559
+ function latestCommitmentRecord(env3, caller) {
267560
+ return loadCommitmentRecords(env3, caller)[0] ?? null;
267561
+ }
267562
+ function deleteCommitmentRecord(env3, caller, label) {
267563
+ rmSync(recordPath(recordId(env3, caller, label)), { force: true });
267564
+ }
267565
+ function decryptCommitmentSecret(record2, credential) {
267566
+ const payload = decryptKeystorePayload(record2.encryptedSecret, credential);
267567
+ if (!payload?.secret) {
267568
+ throw new Error("Stored commitment is missing its secret or the credential is wrong.");
267569
+ }
267570
+ return payload.secret;
267571
+ }
267459
267572
 
267460
267573
  // src/cli/labels.ts
267461
267574
  function generateRandomLabel(status) {
@@ -267492,6 +267605,12 @@ function generateRandomLabel(status) {
267492
267605
  }
267493
267606
 
267494
267607
  // src/cli/commands/register.ts
267608
+ function requireEnvironment(environment) {
267609
+ if (!environment) {
267610
+ throw new Error("Could not resolve the DotNS environment for this command.");
267611
+ }
267612
+ return environment;
267613
+ }
267495
267614
  function resolveTransferDestination(options2) {
267496
267615
  const legacy = options2.transferTo;
267497
267616
  const modern = options2.to;
@@ -267518,6 +267637,34 @@ function isValidTransferDestination(destination) {
267518
267637
  const domainLabelPattern = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
267519
267638
  return domainLabelPattern.test(destination) && destination.length >= 3 && destination.length <= 63;
267520
267639
  }
267640
+ async function registerWithRetries(params2) {
267641
+ try {
267642
+ await params2.attempt();
267643
+ return;
267644
+ } catch (error2) {
267645
+ if (params2.retries <= 0)
267646
+ throw error2;
267647
+ const { env: env3, caller, credential } = params2.persistContext;
267648
+ let lastError = error2;
267649
+ for (let attempt = 1;attempt <= params2.retries; attempt++) {
267650
+ console.log(source_default.yellow(`
267651
+ ↻ Registration failed: ${formatErrorMessage(lastError)}` + `
267652
+ Retry ${attempt}/${params2.retries} (resuming from commitment)…`));
267653
+ try {
267654
+ const record2 = credential ? findCommitmentRecord(env3, caller, params2.label) : null;
267655
+ if (record2 && credential) {
267656
+ await resumeRegistration(params2.context, record2, credential, params2.commitmentBuffer);
267657
+ } else {
267658
+ await params2.attempt();
267659
+ }
267660
+ return;
267661
+ } catch (retryError) {
267662
+ lastError = retryError;
267663
+ }
267664
+ }
267665
+ throw lastError;
267666
+ }
267667
+ }
267521
267668
  async function executeRegistration(options2 = {}) {
267522
267669
  if (options2.mnemonic && options2.keyUri) {
267523
267670
  throw new Error("Cannot specify both --mnemonic and --key-uri");
@@ -267558,17 +267705,22 @@ async function executeRegistration(options2 = {}) {
267558
267705
  }
267559
267706
  console.log(source_default.gray(" Transfer: ") + (transferDestination ? source_default.green("post-mint") : source_default.gray("none")));
267560
267707
  await step("Ensuring account mapped", async () => clientWrapper.ensureAccountMapped(substrateAddress, signer));
267561
- if (options2.governance) {
267562
- await executeGovernanceRegistration(clientWrapper, substrateAddress, signer, evmAddress, label, transferDestination, options2.commitmentBuffer, context.nativeTokenDecimals);
267563
- } else {
267564
- await executeRegularRegistration(clientWrapper, substrateAddress, signer, evmAddress, ownerEvmAddress, label, context.nativeTokenDecimals, options2.reverse ?? false, transferDestination, options2.commitmentBuffer);
267565
- }
267566
- console.log(`
267567
- ${source_default.bold.green("═══════════════════════════════════════")}`);
267568
- console.log(`${source_default.bold.green(" ✓ Operation Complete ")}`);
267569
- console.log(`${source_default.bold.green("═══════════════════════════════════════")}
267570
- `);
267571
- console.log(source_default.gray(" Domain: ") + source_default.cyan(label + ".dot"));
267708
+ const credential = resolveManifestCredential(options2);
267709
+ const persistContext = {
267710
+ env: requireEnvironment(context.environment),
267711
+ caller: evmAddress,
267712
+ credential
267713
+ };
267714
+ const attemptRegistration = () => options2.governance ? executeGovernanceRegistration(clientWrapper, substrateAddress, signer, evmAddress, label, transferDestination, persistContext, options2.commitmentBuffer, context.nativeTokenDecimals) : executeRegularRegistration(clientWrapper, substrateAddress, signer, evmAddress, ownerEvmAddress, label, context.nativeTokenDecimals, options2.reverse ?? false, transferDestination, persistContext, options2.commitmentBuffer);
267715
+ await registerWithRetries({
267716
+ attempt: attemptRegistration,
267717
+ context,
267718
+ persistContext,
267719
+ label,
267720
+ retries: options2.retry ?? 0,
267721
+ commitmentBuffer: options2.commitmentBuffer
267722
+ });
267723
+ printCompletionBanner("✓ Operation Complete", `${label}.dot`);
267572
267724
  return {
267573
267725
  ok: true,
267574
267726
  label,
@@ -267598,12 +267750,7 @@ async function executeSubnameRegistration(options2) {
267598
267750
  console.log(source_default.gray(" Owner: ") + source_default.white(ownerAddress));
267599
267751
  await step("Ensuring account mapped", async () => clientWrapper.ensureAccountMapped(substrateAddress, signer));
267600
267752
  await registerSubnode(clientWrapper, substrateAddress, signer, sublabel, parentLabel, ownerAddress);
267601
- console.log(`
267602
- ${source_default.bold.green("═══════════════════════════════════════")}`);
267603
- console.log(`${source_default.bold.green(" ✓ Subname Registered ")}`);
267604
- console.log(`${source_default.bold.green("═══════════════════════════════════════")}
267605
- `);
267606
- console.log(source_default.gray(" Domain: ") + source_default.cyan(fullName));
267753
+ printCompletionBanner("✓ Subname Registered", fullName);
267607
267754
  return {
267608
267755
  ok: true,
267609
267756
  label: sublabel,
@@ -267612,7 +267759,62 @@ ${source_default.bold.green("═════════════════
267612
267759
  owner: ownerAddress
267613
267760
  };
267614
267761
  }
267615
- async function executeGovernanceRegistration(clientWrapper, substrateAddress, signer, evmAddress, label, transferDestination, commitmentBuffer, nativeTokenDecimals) {
267762
+ function persistCommitment(persistContext, params2) {
267763
+ if (!persistContext.credential)
267764
+ return;
267765
+ try {
267766
+ saveCommitmentRecord({
267767
+ env: persistContext.env,
267768
+ caller: persistContext.caller,
267769
+ label: params2.label,
267770
+ owner: params2.owner,
267771
+ reserved: params2.reserved,
267772
+ governance: params2.governance,
267773
+ secret: params2.secret,
267774
+ commitmentHash: params2.commitmentHash,
267775
+ committedAtIso: new Date().toISOString(),
267776
+ transferDestination: params2.transferDestination,
267777
+ credential: persistContext.credential
267778
+ });
267779
+ } catch (error2) {
267780
+ console.warn(source_default.yellow(` ⚠ Could not cache commitment: ${formatErrorMessage(error2)}`));
267781
+ }
267782
+ }
267783
+ function forgetCommitment(persistContext, label) {
267784
+ try {
267785
+ deleteCommitmentRecord(persistContext.env, persistContext.caller, label);
267786
+ } catch (error2) {
267787
+ console.warn(source_default.yellow(` ⚠ Could not clear cached commitment: ${formatErrorMessage(error2)}`));
267788
+ }
267789
+ }
267790
+ var BANNER_RULE = "═══════════════════════════════════════";
267791
+ function centerInBanner(title) {
267792
+ const width = BANNER_RULE.length;
267793
+ if (title.length >= width)
267794
+ return title;
267795
+ const left = Math.floor((width - title.length) / 2);
267796
+ return title.padStart(title.length + left).padEnd(width);
267797
+ }
267798
+ function printCompletionBanner(title, domain) {
267799
+ console.log(`
267800
+ ${source_default.bold.green(BANNER_RULE)}`);
267801
+ console.log(source_default.bold.green(centerInBanner(title)));
267802
+ console.log(`${source_default.bold.green(BANNER_RULE)}
267803
+ `);
267804
+ console.log(source_default.gray(" Domain: ") + source_default.cyan(domain));
267805
+ }
267806
+ async function replayTransfer(params2) {
267807
+ const recipient = await step("Resolving recipient", async () => resolveTransferRecipient(params2.clientWrapper, params2.substrateAddress, params2.transferDestination));
267808
+ await step("Transferring domain", async () => transferDomain(params2.clientWrapper, params2.substrateAddress, params2.signer, params2.evmAddress, recipient, params2.label, params2.nativeTokenDecimals));
267809
+ await step("Verifying ownership", async () => verifyDomainOwnership(params2.clientWrapper, params2.substrateAddress, params2.label, recipient));
267810
+ }
267811
+ async function finalizeRegularReveal(params2) {
267812
+ const isCrossPayer = checksumAddress(params2.ownerEvmAddress) !== checksumAddress(params2.evmAddress);
267813
+ const pricing = await step("Pricing and eligibility", async () => getPriceAndValidateEligibility(params2.clientWrapper, params2.substrateAddress, params2.label, params2.ownerEvmAddress));
267814
+ const frictionWei = isCrossPayer ? await step("Quoting cross-payer friction", async () => quoteCrossPayerFriction(params2.clientWrapper, params2.substrateAddress, params2.label, params2.evmAddress, params2.ownerEvmAddress)) : 0n;
267815
+ await step("Finalizing registration", async () => finalizeRegularRegistration(params2.clientWrapper, params2.substrateAddress, params2.signer, params2.registration, pricing.priceWei, params2.nativeTokenDecimals, frictionWei));
267816
+ }
267817
+ async function executeGovernanceRegistration(clientWrapper, substrateAddress, signer, evmAddress, label, transferDestination, persistContext, commitmentBuffer, nativeTokenDecimals) {
267616
267818
  console.log(source_default.bold(`
267617
267819
  \uD83C\uDFDB Governance registration (commit-reveal)
267618
267820
  `));
@@ -267624,17 +267826,33 @@ async function executeGovernanceRegistration(clientWrapper, substrateAddress, si
267624
267826
  await step("Checking availability", async () => ensureDomainNotRegistered(clientWrapper, substrateAddress, label));
267625
267827
  const { commitment, registration } = await step("Generating commitment", async () => generateCommitment(clientWrapper, substrateAddress, label, evmAddress, true));
267626
267828
  await step("Submitting commitment", async () => submitCommitment(clientWrapper, substrateAddress, signer, commitment));
267829
+ persistCommitment(persistContext, {
267830
+ label,
267831
+ owner: evmAddress,
267832
+ reserved: true,
267833
+ governance: true,
267834
+ secret: registration.secret,
267835
+ commitmentHash: commitment,
267836
+ transferDestination
267837
+ });
267627
267838
  await step("Waiting commitment age", async () => waitForMinimumCommitmentAge(clientWrapper, substrateAddress, commitment, commitmentBuffer));
267628
267839
  await step("Finalizing registration", async () => finalizeGovernanceRegistration(clientWrapper, substrateAddress, signer, registration));
267840
+ forgetCommitment(persistContext, label);
267629
267841
  await step("Verifying ownership", async () => verifyDomainOwnership(clientWrapper, substrateAddress, label, evmAddress));
267630
267842
  await step("Ensuring label store", async () => ensureLabelStoreReady(clientWrapper, substrateAddress, signer, evmAddress));
267631
267843
  if (transferDestination) {
267632
- const recipient = await step("Resolving recipient", async () => resolveTransferRecipient(clientWrapper, substrateAddress, transferDestination));
267633
- await step("Transferring domain", async () => transferDomain(clientWrapper, substrateAddress, signer, evmAddress, recipient, label, nativeTokenDecimals));
267634
- await step("Verifying ownership", async () => verifyDomainOwnership(clientWrapper, substrateAddress, label, recipient));
267844
+ await replayTransfer({
267845
+ clientWrapper,
267846
+ substrateAddress,
267847
+ signer,
267848
+ evmAddress,
267849
+ label,
267850
+ transferDestination,
267851
+ nativeTokenDecimals
267852
+ });
267635
267853
  }
267636
267854
  }
267637
- async function executeRegularRegistration(clientWrapper, substrateAddress, signer, evmAddress, ownerEvmAddress, label, nativeTokenDecimals, enableReverseRecord, transferDestination, commitmentBuffer) {
267855
+ async function executeRegularRegistration(clientWrapper, substrateAddress, signer, evmAddress, ownerEvmAddress, label, nativeTokenDecimals, enableReverseRecord, transferDestination, persistContext, commitmentBuffer) {
267638
267856
  console.log(source_default.bold(`
267639
267857
  \uD83E\uDDFE Regular registration (commit-reveal)
267640
267858
  `));
@@ -267644,20 +267862,249 @@ async function executeRegularRegistration(clientWrapper, substrateAddress, signe
267644
267862
  await step("Checking availability", async () => ensureDomainNotRegistered(clientWrapper, substrateAddress, label));
267645
267863
  const { commitment, registration } = await step("Generating commitment", async () => generateCommitment(clientWrapper, substrateAddress, label, ownerEvmAddress, enableReverseRecord));
267646
267864
  await step("Submitting commitment", async () => submitCommitment(clientWrapper, substrateAddress, signer, commitment));
267865
+ persistCommitment(persistContext, {
267866
+ label,
267867
+ owner: ownerEvmAddress,
267868
+ reserved: enableReverseRecord,
267869
+ governance: false,
267870
+ secret: registration.secret,
267871
+ commitmentHash: commitment,
267872
+ transferDestination
267873
+ });
267647
267874
  await step("Waiting commitment age", async () => waitForMinimumCommitmentAge(clientWrapper, substrateAddress, commitment, commitmentBuffer));
267648
- const pricing = await step("Pricing and eligibility", async () => getPriceAndValidateEligibility(clientWrapper, substrateAddress, label, ownerEvmAddress));
267649
- const frictionWei = isCrossPayer ? await step("Quoting cross-payer friction", async () => quoteCrossPayerFriction(clientWrapper, substrateAddress, label, evmAddress, ownerEvmAddress)) : 0n;
267650
- await step("Finalizing registration", async () => finalizeRegularRegistration(clientWrapper, substrateAddress, signer, registration, pricing.priceWei, nativeTokenDecimals, frictionWei));
267875
+ await finalizeRegularReveal({
267876
+ clientWrapper,
267877
+ substrateAddress,
267878
+ signer,
267879
+ evmAddress,
267880
+ ownerEvmAddress,
267881
+ label,
267882
+ registration,
267883
+ nativeTokenDecimals
267884
+ });
267885
+ forgetCommitment(persistContext, label);
267651
267886
  await step("Verifying ownership", async () => verifyDomainOwnership(clientWrapper, substrateAddress, label, ownerEvmAddress));
267652
267887
  if (!isCrossPayer) {
267653
267888
  await step("Ensuring label store", async () => ensureLabelStoreReady(clientWrapper, substrateAddress, signer, evmAddress));
267654
267889
  }
267655
267890
  if (transferDestination) {
267656
- const recipient = await step("Resolving recipient", async () => resolveTransferRecipient(clientWrapper, substrateAddress, transferDestination));
267657
- await step("Transferring domain", async () => transferDomain(clientWrapper, substrateAddress, signer, evmAddress, recipient, label, nativeTokenDecimals));
267658
- await step("Verifying ownership", async () => verifyDomainOwnership(clientWrapper, substrateAddress, label, recipient));
267891
+ await replayTransfer({
267892
+ clientWrapper,
267893
+ substrateAddress,
267894
+ signer,
267895
+ evmAddress,
267896
+ label,
267897
+ transferDestination,
267898
+ nativeTokenDecimals
267899
+ });
267659
267900
  }
267660
267901
  }
267902
+ async function isRegisteredTo(clientWrapper, substrateAddress, label, owner) {
267903
+ const actual = await readDomainOwner(clientWrapper, substrateAddress, label);
267904
+ return checksumAddress(actual) === checksumAddress(owner);
267905
+ }
267906
+ async function resumeRegistration(context, record2, credential, commitmentBuffer) {
267907
+ const { clientWrapper, substrateAddress, signer, evmAddress, nativeTokenDecimals } = context;
267908
+ const { label } = record2;
267909
+ const persistContext = {
267910
+ env: requireEnvironment(context.environment),
267911
+ caller: evmAddress,
267912
+ credential
267913
+ };
267914
+ console.log(source_default.bold(`
267915
+ ▶ Resuming ${source_default.cyan(label + ".dot")}
267916
+ `));
267917
+ const registration = {
267918
+ label,
267919
+ owner: record2.owner,
267920
+ secret: decryptCommitmentSecret(record2, credential),
267921
+ reserved: record2.reserved
267922
+ };
267923
+ const alreadyOwned = await step("Checking on-chain ownership", async () => isRegisteredTo(clientWrapper, substrateAddress, label, record2.owner));
267924
+ if (alreadyOwned) {
267925
+ console.log(source_default.green(` ✓ ${label}.dot is already registered to ${record2.owner}`));
267926
+ forgetCommitment(persistContext, label);
267927
+ return {
267928
+ ok: true,
267929
+ label,
267930
+ domain: `${label}.dot`,
267931
+ caller: evmAddress,
267932
+ owner: record2.owner
267933
+ };
267934
+ }
267935
+ const status = await step("Reading commitment status", async () => readCommitmentStatus(clientWrapper, substrateAddress, record2.commitmentHash));
267936
+ const commitmentAge = status.nowSeconds - status.committedTimestampSeconds;
267937
+ const needsRecommit = status.committedTimestampSeconds === 0 || commitmentAge > status.maxAgeSeconds;
267938
+ if (needsRecommit) {
267939
+ await step("Re-submitting commitment", async () => submitCommitment(clientWrapper, substrateAddress, signer, record2.commitmentHash));
267940
+ }
267941
+ await step("Waiting commitment age", async () => waitForMinimumCommitmentAge(clientWrapper, substrateAddress, record2.commitmentHash, commitmentBuffer));
267942
+ if (record2.governance) {
267943
+ await step("Finalizing registration", async () => finalizeGovernanceRegistration(clientWrapper, substrateAddress, signer, registration));
267944
+ } else {
267945
+ await finalizeRegularReveal({
267946
+ clientWrapper,
267947
+ substrateAddress,
267948
+ signer,
267949
+ evmAddress,
267950
+ ownerEvmAddress: record2.owner,
267951
+ label,
267952
+ registration,
267953
+ nativeTokenDecimals
267954
+ });
267955
+ }
267956
+ await step("Verifying ownership", async () => verifyDomainOwnership(clientWrapper, substrateAddress, label, record2.owner));
267957
+ forgetCommitment(persistContext, label);
267958
+ if (record2.transferDestination) {
267959
+ await replayTransfer({
267960
+ clientWrapper,
267961
+ substrateAddress,
267962
+ signer,
267963
+ evmAddress,
267964
+ label,
267965
+ transferDestination: record2.transferDestination,
267966
+ nativeTokenDecimals
267967
+ });
267968
+ }
267969
+ printCompletionBanner("✓ Registration Resumed", `${label}.dot`);
267970
+ return {
267971
+ ok: true,
267972
+ label,
267973
+ domain: `${label}.dot`,
267974
+ caller: evmAddress,
267975
+ owner: record2.owner
267976
+ };
267977
+ }
267978
+ function requireManifestCredential(options2) {
267979
+ const credential = resolveManifestCredential(options2);
267980
+ if (!credential) {
267981
+ throw new Error("A credential is required to decrypt the cached commitment: pass --password, --mnemonic, or --key-uri.");
267982
+ }
267983
+ return credential;
267984
+ }
267985
+ async function executeRetry(options2 = {}) {
267986
+ const context = await prepareAssetHubContext(options2);
267987
+ const credential = requireManifestCredential(options2);
267988
+ const caller = context.evmAddress;
267989
+ const env3 = requireEnvironment(context.environment);
267990
+ const record2 = options2.name ? findCommitmentRecord(env3, caller, options2.name) : latestCommitmentRecord(env3, caller);
267991
+ if (!record2) {
267992
+ throw new Error("No cached commitment to retry.");
267993
+ }
267994
+ return resumeRegistration(context, record2, credential, options2.commitmentBuffer);
267995
+ }
267996
+ async function promptPendingAction(pending) {
267997
+ const readline = await import("node:readline/promises");
267998
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
267999
+ console.log(source_default.bold(`
268000
+ ▶ Pending commitments
268001
+ `));
268002
+ for (const record2 of pending) {
268003
+ console.log(source_default.gray(" • ") + source_default.cyan(record2.label + ".dot") + source_default.gray(` committed ${record2.committedAtIso}`));
268004
+ }
268005
+ try {
268006
+ const answer = (await rl.question(source_default.bold(`
268007
+ register / discard / cancel (default cancel): `))).trim().toLowerCase();
268008
+ if (answer === "register" || answer === "r")
268009
+ return "register";
268010
+ if (answer === "discard" || answer === "d")
268011
+ return "discard";
268012
+ return "cancel";
268013
+ } finally {
268014
+ rl.close();
268015
+ }
268016
+ }
268017
+ async function executeClear(options2 = {}) {
268018
+ const context = await prepareAssetHubContext(options2);
268019
+ const { clientWrapper, substrateAddress } = context;
268020
+ const caller = context.evmAddress;
268021
+ const env3 = requireEnvironment(context.environment);
268022
+ const records = loadCommitmentRecords(env3, caller);
268023
+ const summary = {
268024
+ ok: true,
268025
+ purged: [],
268026
+ discarded: [],
268027
+ resumed: [],
268028
+ cancelled: false
268029
+ };
268030
+ const pending = [];
268031
+ for (const record2 of records) {
268032
+ const registered = await step(`Checking ${record2.label}.dot`, async () => isRegisteredTo(clientWrapper, substrateAddress, record2.label, record2.owner));
268033
+ if (registered) {
268034
+ deleteCommitmentRecord(env3, caller, record2.label);
268035
+ summary.purged.push(record2.label);
268036
+ } else {
268037
+ pending.push(record2);
268038
+ }
268039
+ }
268040
+ if (summary.purged.length > 0) {
268041
+ console.log(source_default.green(` ✓ Purged ${summary.purged.length} completed`));
268042
+ }
268043
+ if (pending.length === 0) {
268044
+ return summary;
268045
+ }
268046
+ let action;
268047
+ if (options2.discard) {
268048
+ action = "discard";
268049
+ } else if (options2.register) {
268050
+ action = "register";
268051
+ } else if (process.stdin.isTTY) {
268052
+ action = await promptPendingAction(pending);
268053
+ } else {
268054
+ throw new Error(`${pending.length} pending commitment(s) found. Pass --discard to delete them or --register to complete them.`);
268055
+ }
268056
+ if (action === "cancel") {
268057
+ summary.cancelled = true;
268058
+ return summary;
268059
+ }
268060
+ if (action === "discard") {
268061
+ for (const record2 of pending) {
268062
+ deleteCommitmentRecord(env3, caller, record2.label);
268063
+ summary.discarded.push(record2.label);
268064
+ }
268065
+ return summary;
268066
+ }
268067
+ const credential = requireManifestCredential(options2);
268068
+ for (const record2 of pending) {
268069
+ await resumeRegistration(context, record2, credential, options2.commitmentBuffer);
268070
+ summary.resumed.push(record2.label);
268071
+ }
268072
+ return summary;
268073
+ }
268074
+ async function executeList(options2 = {}) {
268075
+ const context = await prepareReadOnlyContext(options2);
268076
+ const { clientWrapper } = context;
268077
+ const caller = context.evmAddress;
268078
+ const env3 = requireEnvironment(context.environment);
268079
+ const records = loadCommitmentRecords(env3, caller);
268080
+ const rows = [];
268081
+ for (const record2 of records) {
268082
+ const registered = await isRegisteredTo(clientWrapper, context.account.address, record2.label, record2.owner);
268083
+ rows.push({
268084
+ label: record2.label,
268085
+ status: registered ? "registered" : "pending",
268086
+ committedAtIso: record2.committedAtIso,
268087
+ env: record2.env
268088
+ });
268089
+ }
268090
+ if (!options2.json) {
268091
+ if (rows.length === 0) {
268092
+ console.log(source_default.gray(`
268093
+ No cached commitments.
268094
+ `));
268095
+ } else {
268096
+ console.log(source_default.bold(`
268097
+ ▶ Cached commitments
268098
+ `));
268099
+ for (const row of rows) {
268100
+ const statusLabel = row.status === "registered" ? source_default.green("registered") : source_default.yellow("pending");
268101
+ console.log(source_default.gray(" • ") + source_default.cyan((row.label + ".dot").padEnd(24)) + statusLabel + source_default.gray(` ${row.committedAtIso} ${row.env}`));
268102
+ }
268103
+ console.log();
268104
+ }
268105
+ }
268106
+ return { ok: true, records: rows };
268107
+ }
267661
268108
 
267662
268109
  // src/cli/commands/lookup.ts
267663
268110
  async function createReadOnlyChainContext(rpc) {
@@ -268185,14 +268632,24 @@ function resolveCommitmentBuffer(cliValue) {
268185
268632
  }
268186
268633
  return parsed2;
268187
268634
  }
268635
+ function resolveRetryCount(cliValue) {
268636
+ if (cliValue == null)
268637
+ return 0;
268638
+ const parsed2 = Number(cliValue);
268639
+ if (!Number.isInteger(parsed2) || parsed2 < 0) {
268640
+ throw new Error(`Invalid --retry "${cliValue}": must be a non-negative integer`);
268641
+ }
268642
+ return parsed2;
268643
+ }
268188
268644
  function attachRegisterCommand(root) {
268189
268645
  const registerCommand = root.command("register").description("Domain registration commands");
268190
- const domainCommand = registerCommand.command("domain").description("Register a new base domain").option("-n, --name <label>", "Domain label to register (without .dot)").option("-r, --reverse", "Enable reverse record registration", false).option("-g, --governance", "Use governance registration path", false).option("-o, --owner <address>", "Register on behalf of another address (EVM, SS58, or .dot label). Caller pays price + transferFloor friction; owner receives the NFT. Mutually exclusive with --transfer, --reverse, --governance.").option("--transfer", "Transfer domain after registration", false).option("--to <destination>", "Transfer destination (EVM address, SS58, or domain label)").option("--cb, --commitment-buffer <seconds>", `Extra seconds to wait after minCommitmentAge (default: ${DEFAULT_COMMITMENT_BUFFER_SECONDS}, env: DOTNS_COMMITMENT_BUFFER)`).option("--json", "Output result as JSON (suppresses all other output)", false).action(async (options2, cmd) => {
268646
+ const domainCommand = registerCommand.command("domain").description("Register a new base domain").option("-n, --name <label>", "Domain label to register (without .dot)").option("-r, --reverse", "Enable reverse record registration", false).option("-g, --governance", "Use governance registration path", false).option("-o, --owner <address>", "Register on behalf of another address (EVM, SS58, or .dot label). Caller pays price + transferFloor friction; owner receives the NFT. Mutually exclusive with --transfer, --reverse, --governance.").option("--transfer", "Transfer domain after registration", false).option("--to <destination>", "Transfer destination (EVM address, SS58, or domain label)").option("--cb, --commitment-buffer <seconds>", `Extra seconds to wait after minCommitmentAge (default: ${DEFAULT_COMMITMENT_BUFFER_SECONDS}, env: DOTNS_COMMITMENT_BUFFER)`).option("--retry <count>", "On failure, resume from the cached commitment up to N times before giving up").option("--json", "Output result as JSON (suppresses all other output)", false).action(async (options2, cmd) => {
268191
268647
  const jsonOutput = getJsonFlag(cmd);
268192
268648
  try {
268193
268649
  const merged = { ...options2, ...getAuthOptions(cmd) };
268194
268650
  const allOpts = typeof cmd.optsWithGlobals === "function" ? cmd.optsWithGlobals() : cmd.opts();
268195
268651
  merged.commitmentBuffer = resolveCommitmentBuffer(allOpts?.commitmentBuffer);
268652
+ merged.retry = resolveRetryCount(allOpts?.retry);
268196
268653
  if (merged.transfer === true && !merged.to) {
268197
268654
  throw new Error("Missing transfer destination: use --to <evm|ss58|label>");
268198
268655
  }
@@ -268216,6 +268673,54 @@ function attachRegisterCommand(root) {
268216
268673
  }
268217
268674
  });
268218
268675
  addAuthOptions(subnameCommand);
268676
+ const retryCommand = registerCommand.command("retry [name]").description("Resume a cached commit-reveal registration").option("--cb, --commitment-buffer <seconds>", `Extra seconds to wait after minCommitmentAge (default: ${DEFAULT_COMMITMENT_BUFFER_SECONDS}, env: DOTNS_COMMITMENT_BUFFER)`).option("--json", "Output result as JSON (suppresses all other output)", false).action(async (name10, options2, cmd) => {
268677
+ const jsonOutput = getJsonFlag(cmd);
268678
+ try {
268679
+ const merged = { ...options2, ...getAuthOptions(cmd) };
268680
+ if (name10)
268681
+ merged.name = name10;
268682
+ const allOpts = typeof cmd.optsWithGlobals === "function" ? cmd.optsWithGlobals() : cmd.opts();
268683
+ merged.commitmentBuffer = resolveCommitmentBuffer(allOpts?.commitmentBuffer);
268684
+ const result = await maybeQuiet(jsonOutput, () => executeRetry(merged));
268685
+ emitJsonResult(jsonOutput, result);
268686
+ process.exit(0);
268687
+ } catch (error2) {
268688
+ handleCommandError(jsonOutput, error2);
268689
+ }
268690
+ });
268691
+ addAuthOptions(retryCommand);
268692
+ const clearCommand = registerCommand.command("clear [name]").description("Review cached commitments, purging completed ones").option("--discard", "Delete pending cached commitments", false).option("--register", "Complete pending cached commitments", false).option("--json", "Output result as JSON (suppresses all other output)", false).action(async (name10, options2, cmd) => {
268693
+ const jsonOutput = getJsonFlag(cmd);
268694
+ try {
268695
+ if (options2.discard && options2.register) {
268696
+ throw new Error("Cannot combine --discard with --register; pick one");
268697
+ }
268698
+ const merged = {
268699
+ ...options2,
268700
+ ...getAuthOptions(cmd)
268701
+ };
268702
+ if (name10)
268703
+ merged.name = name10;
268704
+ const result = await maybeQuiet(jsonOutput, () => executeClear(merged));
268705
+ emitJsonResult(jsonOutput, result);
268706
+ process.exit(0);
268707
+ } catch (error2) {
268708
+ handleCommandError(jsonOutput, error2);
268709
+ }
268710
+ });
268711
+ addAuthOptions(clearCommand);
268712
+ const listCommand = registerCommand.command("list").description("List cached commitments and their on-chain status").option("--json", "Output result as JSON (suppresses all other output)", false).action(async (options2, cmd) => {
268713
+ const jsonOutput = getJsonFlag(cmd);
268714
+ try {
268715
+ const merged = { ...options2, ...getAuthOptions(cmd) };
268716
+ const result = await maybeQuiet(jsonOutput, () => executeList(merged));
268717
+ emitJsonResult(jsonOutput, result);
268718
+ process.exit(0);
268719
+ } catch (error2) {
268720
+ handleCommandError(jsonOutput, error2);
268721
+ }
268722
+ });
268723
+ addAuthOptions(listCommand);
268219
268724
  }
268220
268725
 
268221
268726
  // src/cli/commands/info.ts
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@parity/dotns-cli",
3
3
  "module": "index.ts",
4
- "version": "0.6.4",
4
+ "version": "0.6.5",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1.2.6",
7
7
  "files": [