@parity/product-deploy 0.9.0-rc.6 → 0.10.0-rc.0

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 (37) hide show
  1. package/bin/bulletin-deploy +44 -11
  2. package/dist/auth-config.js +3 -3
  3. package/dist/bug-report.js +4 -4
  4. package/dist/{chunk-ZL7KBV52.js → chunk-2BTYPNYW.js} +1 -1
  5. package/dist/{chunk-NRDXKYWQ.js → chunk-DY7RVMM5.js} +63 -21
  6. package/dist/{chunk-NEWDQKA3.js → chunk-G676QAN4.js} +2 -2
  7. package/dist/{chunk-6GGRUTTC.js → chunk-GJTVPP7E.js} +1 -1
  8. package/dist/{chunk-SHQZFLPN.js → chunk-HUT626G6.js} +1 -1
  9. package/dist/{chunk-LIAEGW76.js → chunk-MI5B3UCM.js} +1 -1
  10. package/dist/{chunk-HGEYWE2P.js → chunk-PKQOUCPD.js} +1 -1
  11. package/dist/{chunk-2XBLLNDK.js → chunk-Q3RIJ7PU.js} +2 -2
  12. package/dist/{chunk-T4WUEPAS.js → chunk-VR3LF62E.js} +105 -23
  13. package/dist/{chunk-ELX3YZQG.js → chunk-ZJCTG7HF.js} +3 -3
  14. package/dist/chunk-probe.js +3 -3
  15. package/dist/commands/login.js +12 -12
  16. package/dist/commands/logout.js +4 -4
  17. package/dist/commands/transfer.d.ts +14 -0
  18. package/dist/commands/transfer.js +66 -0
  19. package/dist/commands/whoami.js +3 -3
  20. package/dist/deploy-actors.d.ts +23 -0
  21. package/dist/deploy-actors.js +43 -0
  22. package/dist/deploy.d.ts +7 -0
  23. package/dist/deploy.js +9 -9
  24. package/dist/dotns.d.ts +32 -3
  25. package/dist/dotns.js +13 -5
  26. package/dist/index.js +14 -14
  27. package/dist/manifest/publish.js +10 -10
  28. package/dist/memory-report.js +2 -2
  29. package/dist/merkle.js +9 -9
  30. package/dist/personhood/bootstrap.js +9 -9
  31. package/dist/personhood/people-client.js +3 -3
  32. package/dist/run-state.js +1 -1
  33. package/dist/sss-allowance-cache.js +4 -4
  34. package/dist/storage-signer.js +9 -9
  35. package/dist/telemetry.js +2 -2
  36. package/dist/version-check.js +3 -3
  37. package/package.json +3 -3
@@ -69,6 +69,8 @@ for (let i = 0; i < args.length; i++) {
69
69
  else if (args[i] === "--unpublish") { flags.unpublish = true; }
70
70
  else if (args[i] === "--fail-on-publish-error") { flags.failOnPublishError = true; }
71
71
  else if (args[i] === "--suri") { flags.suri = args[++i]; }
72
+ else if (args[i] === "--no-transfer-to-signedin-user") { flags.noTransferToSignedinUser = true; }
73
+ else if (args[i] === "--to") { flags.to = args[++i]; }
72
74
  else if (args[i] === "--version" || args[i] === "-V") { flags.version = true; }
73
75
  else if (args[i] === "--help" || args[i] === "-h") { flags.help = true; }
74
76
  else { positional.push(args[i]); }
@@ -134,23 +136,27 @@ if (flags.unpublish) {
134
136
  }
135
137
  }
136
138
 
139
+ // The SSO adapter's polkadot-api client fires a benign `DestroyedError: Client
140
+ // destroyed` on orphaned pending-response promises during WS teardown, AFTER an
141
+ // SSO subcommand has done its work. The deploy path's handlers (below) aren't
142
+ // installed yet here, so SSO subcommands install these guards to swallow that +
143
+ // connection-teardown noise and surface anything else.
144
+ function installSsoTeardownGuards() {
145
+ const benignTeardown = (e) => {
146
+ const s = e instanceof Error ? `${e.name ?? ""} ${e.message ?? ""}` : String(e);
147
+ return isConnectionError(e) || /DestroyedError|Client destroyed/.test(s);
148
+ };
149
+ process.on("unhandledRejection", (e) => { if (!benignTeardown(e)) { console.error(e); process.exit(1); } });
150
+ process.on("uncaughtException", (e) => { if (!benignTeardown(e)) { console.error(e); process.exit(1); } });
151
+ }
152
+
137
153
  // Sign-in subcommands: login / logout / whoami.
138
154
  // Lazy-imported from dist so the SSO deps never load in headless/deploy mode.
139
155
  {
140
156
  const sub = positional[0];
141
157
  if (sub === "login" || sub === "logout" || sub === "whoami") {
142
158
  const envId = flags.env ?? DEFAULT_ENV_ID;
143
- // The SSO adapter's polkadot-api client fires a benign `DestroyedError:
144
- // Client destroyed` on orphaned pending-response promises during WS teardown,
145
- // AFTER the command has done its work (e.g. logout already disconnected). The
146
- // deploy path's handlers (below) aren't installed yet here, so guard the
147
- // command path: swallow that + connection-teardown noise, surface anything else.
148
- const benignTeardown = (e) => {
149
- const s = e instanceof Error ? `${e.name ?? ""} ${e.message ?? ""}` : String(e);
150
- return isConnectionError(e) || /DestroyedError|Client destroyed/.test(s);
151
- };
152
- process.on("unhandledRejection", (e) => { if (!benignTeardown(e)) { console.error(e); process.exit(1); } });
153
- process.on("uncaughtException", (e) => { if (!benignTeardown(e)) { console.error(e); process.exit(1); } });
159
+ installSsoTeardownGuards();
154
160
  try {
155
161
  const capSub = sub.charAt(0).toUpperCase() + sub.slice(1);
156
162
  const mod = await import(`../dist/commands/${sub}.js`);
@@ -164,6 +170,23 @@ if (flags.unpublish) {
164
170
  }
165
171
  }
166
172
 
173
+ // `transfer` subcommand: hand a name you registered to the signed-in account
174
+ // (or --to). Stand-alone handover + recovery for a deploy whose transfer step
175
+ // failed. Uses the SSO stack to resolve the session recipient.
176
+ if (positional[0] === "transfer") {
177
+ const envId = flags.env ?? DEFAULT_ENV_ID;
178
+ installSsoTeardownGuards();
179
+ try {
180
+ const { runTransfer } = await import("../dist/commands/transfer.js");
181
+ await runTransfer(envId, { label: positional[1], to: flags.to, mnemonic: flags.mnemonic });
182
+ } catch (err) {
183
+ console.error(`Error: ${err?.message ?? err}`);
184
+ process.exit(1);
185
+ }
186
+ await closeTelemetry();
187
+ process.exit(0);
188
+ }
189
+
167
190
  // `product` subcommand. Only `validate` is wired today. `publish` and `resolve` arrive in later phases.
168
191
  if (positional[0] === "product") {
169
192
  const verb = positional[1];
@@ -215,6 +238,7 @@ Usage:
215
238
  bulletin-deploy login Sign in with your Polkadot mobile app
216
239
  bulletin-deploy logout Sign out and clear the session
217
240
  bulletin-deploy whoami Show the currently signed-in identity
241
+ bulletin-deploy transfer <domain.dot> Hand a name you registered to the signed-in account (or --to)
218
242
 
219
243
  Options:
220
244
  --env <id> Target environment from environments.json (default: paseo-next-v2).
@@ -235,6 +259,14 @@ Options:
235
259
  --contract DOTNS_REGISTRY=0x.. --contract DOTNS_CONTENT_RESOLVER=0x..
236
260
  --mnemonic "..." DotNS owner mnemonic (or set MNEMONIC env var)
237
261
  --derivation-path "..." Optional Substrate-style path applied to --mnemonic (e.g. //deploy/3)
262
+ --no-transfer-to-signedin-user
263
+ When signed in, sign every DotNS tx with your mobile
264
+ session (today's behavior) instead of the default:
265
+ a local worker (Alice, or --mnemonic) registers +
266
+ deploys, then hands the name to your signed-in
267
+ account with zero mobile signatures. Testnet only.
268
+ --to <0xH160> (transfer subcommand) recipient address; defaults to
269
+ the signed-in account.
238
270
  --rpc wss://... Override the bulletin RPC for the chosen --env (or set BULLETIN_RPC).
239
271
  Precedence: --rpc > BULLETIN_RPC > --env's bulletin endpoint.
240
272
  --pool-size N Number of pool accounts (default: 10)
@@ -444,6 +476,7 @@ try {
444
476
  dumpCar: flags.dumpCar,
445
477
  publish: flags.publish,
446
478
  failOnPublishError: flags.failOnPublishError,
479
+ transferToSignedInUser: !flags.noTransferToSignedinUser,
447
480
  });
448
481
 
449
482
  const output = process.env.GITHUB_OUTPUT;
@@ -9,9 +9,9 @@ import {
9
9
  getPeopleChainEndpoints,
10
10
  hasPersistedSession,
11
11
  resolveBulletinEndpoints
12
- } from "./chunk-HGEYWE2P.js";
13
- import "./chunk-ZL7KBV52.js";
14
- import "./chunk-ELX3YZQG.js";
12
+ } from "./chunk-PKQOUCPD.js";
13
+ import "./chunk-2BTYPNYW.js";
14
+ import "./chunk-ZJCTG7HF.js";
15
15
  import "./chunk-GL3U7K2B.js";
16
16
  import "./chunk-ZOC4GITL.js";
17
17
  export {
@@ -9,10 +9,10 @@ import {
9
9
  offerBugReport,
10
10
  scrubSecrets,
11
11
  setDeployContext
12
- } from "./chunk-NEWDQKA3.js";
13
- import "./chunk-LIAEGW76.js";
14
- import "./chunk-ZL7KBV52.js";
15
- import "./chunk-ELX3YZQG.js";
12
+ } from "./chunk-G676QAN4.js";
13
+ import "./chunk-MI5B3UCM.js";
14
+ import "./chunk-2BTYPNYW.js";
15
+ import "./chunk-ZJCTG7HF.js";
16
16
  export {
17
17
  buildCliFlagsSummary,
18
18
  buildLabels,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  package_default,
3
3
  writeRunState
4
- } from "./chunk-ELX3YZQG.js";
4
+ } from "./chunk-ZJCTG7HF.js";
5
5
 
6
6
  // src/memory-report.ts
7
7
  import * as fs2 from "fs";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  preflightSssAllowance
3
- } from "./chunk-6GGRUTTC.js";
3
+ } from "./chunk-GJTVPP7E.js";
4
4
  import {
5
5
  statementSigningAccount
6
6
  } from "./chunk-GRPLHUYC.js";
@@ -34,13 +34,13 @@ import {
34
34
  STALE_SESSION_MESSAGE,
35
35
  getPeopleChainEndpoints,
36
36
  hasPersistedSession
37
- } from "./chunk-HGEYWE2P.js";
37
+ } from "./chunk-PKQOUCPD.js";
38
38
  import {
39
39
  setDeployContext
40
- } from "./chunk-NEWDQKA3.js";
40
+ } from "./chunk-G676QAN4.js";
41
41
  import {
42
42
  probeChunks
43
- } from "./chunk-SHQZFLPN.js";
43
+ } from "./chunk-HUT626G6.js";
44
44
  import {
45
45
  packSection
46
46
  } from "./chunk-C2TS5MER.js";
@@ -53,7 +53,7 @@ import {
53
53
  parseDomainName,
54
54
  popStatusName,
55
55
  verifyNonceAdvanced
56
- } from "./chunk-T4WUEPAS.js";
56
+ } from "./chunk-VR3LF62E.js";
57
57
  import {
58
58
  derivePoolAccounts,
59
59
  detectTestnet,
@@ -75,7 +75,7 @@ import {
75
75
  truncateAddress,
76
76
  withDeploySpan,
77
77
  withSpan
78
- } from "./chunk-ZL7KBV52.js";
78
+ } from "./chunk-2BTYPNYW.js";
79
79
  import {
80
80
  DEFAULT_ENV_ID,
81
81
  getPopSelfServeConfig,
@@ -1958,25 +1958,36 @@ async function deploy(content, domainName = null, options = {}) {
1958
1958
  });
1959
1959
  let resolvedUserSession = void 0;
1960
1960
  if (signerChoice === "resolve") {
1961
- const { resolveSigner: resolveSignerFn } = await import("./auth/index.js");
1961
+ const { resolveDeployActors } = await import("./deploy-actors.js");
1962
1962
  const { getAuthClient } = await import("./auth-config.js");
1963
1963
  const authClient = await getAuthClient(envId);
1964
+ const isTestnetEnv = envNetwork === "testnet";
1965
+ const transferEnabled = options.transferToSignedInUser !== false;
1964
1966
  try {
1965
- const resolved = await resolveSignerFn(authClient, { suri: options.suri });
1966
- options = { ...options, signer: resolved.signer, signerAddress: resolved.address };
1967
- sessionCleanup = resolved.destroy.bind(resolved);
1968
- console.log(` Using ${resolved.source} signer: ${resolved.address}`);
1969
- if (resolved.source === "session") resolvedUserSession = resolved;
1967
+ const actors = await resolveDeployActors(authClient, {
1968
+ suri: options.suri,
1969
+ transferEnabled,
1970
+ isTestnet: isTestnetEnv,
1971
+ sessionPresent: hasSession
1972
+ });
1973
+ options = {
1974
+ ...options,
1975
+ signer: actors.worker.signer,
1976
+ signerAddress: actors.worker.address,
1977
+ ...actors.recipientH160 ? { transferTo: actors.recipientH160 } : {}
1978
+ };
1979
+ sessionCleanup = actors.worker.destroy.bind(actors.worker);
1980
+ if (actors.worker.source === "session") resolvedUserSession = actors.worker;
1981
+ if (actors.recipientH160) {
1982
+ console.log(` Worker: ${actors.worker.source} signer ${actors.worker.address} \u2192 will transfer ${domainName ?? "the name"} to ${actors.recipientH160}`);
1983
+ } else {
1984
+ console.log(` Using ${actors.worker.source} signer: ${actors.worker.address}`);
1985
+ }
1970
1986
  } catch (e) {
1971
1987
  if (options.suri) throw e;
1972
1988
  if (e?.name === "SignerNotAvailableError") {
1973
- if (hasSession) {
1974
- console.error(STALE_SESSION_MESSAGE);
1975
- } else {
1976
- console.log(
1977
- " Login session unavailable or expired \u2014 falling back to pool. Run `bulletin-deploy login` to use your identity."
1978
- );
1979
- }
1989
+ if (hasSession) console.error(STALE_SESSION_MESSAGE);
1990
+ else console.log(" Login session unavailable or expired \u2014 falling back to pool. Run `bulletin-deploy login` to use your identity.");
1980
1991
  } else {
1981
1992
  throw e;
1982
1993
  }
@@ -2040,6 +2051,7 @@ async function deploy(content, domainName = null, options = {}) {
2040
2051
  setDeployAttribute("deploy.subdomain", String(parsed?.isSubdomain ?? false));
2041
2052
  if (envNetwork) setDeployAttribute("deploy.network", envNetwork);
2042
2053
  if (envSource) setDeployAttribute("deploy.environments_source", envSource);
2054
+ setDeployAttribute("deploy.transfer.enabled", options.transferTo ? "true" : "false");
2043
2055
  let cid;
2044
2056
  let ipfsCid;
2045
2057
  let mirrorPromise = Promise.resolve(null);
@@ -2085,7 +2097,7 @@ async function deploy(content, domainName = null, options = {}) {
2085
2097
  } else {
2086
2098
  preflightPublishNeeded = false;
2087
2099
  try {
2088
- dotnsPreflight = await preflight.preflight(name);
2100
+ dotnsPreflight = await preflight.preflight(name, { transferRecipientH160: options.transferTo });
2089
2101
  previousContenthashCid = await readPreviousContenthashSafe(preflight, name);
2090
2102
  setDeployAttribute("deploy.incremental", previousContenthashCid ? "true" : "false");
2091
2103
  if (options.publish && parsed && !parsed.isSubdomain) {
@@ -2332,7 +2344,12 @@ async function deploy(content, domainName = null, options = {}) {
2332
2344
  const dotns = new DotNS();
2333
2345
  await dotns.connect({
2334
2346
  ...resolveDotnsConnectOptions(options, envAssetHub, envAutoAccountMapping, envContracts, envNativeToEthRatio, envId, envPopSelfServe, envRegisterStorageDeposit),
2335
- ...options.signer && options.signerAddress ? { onPhoneSigningRequired: (label) => console.log(`
2347
+ // Phone-signing reminders apply only to a genuine session/mobile
2348
+ // signer. In the transfer flow the injected signer is a LOCAL worker
2349
+ // (zero mobile sigs), distinguished by options.transferTo being set —
2350
+ // suppress the reminder there or it falsely tells the user to check
2351
+ // their phone for a tx signed locally.
2352
+ ...options.signer && options.signerAddress && !options.transferTo ? { onPhoneSigningRequired: (label) => console.log(`
2336
2353
  Check your phone \u2192 ${label}`) } : {}
2337
2354
  });
2338
2355
  if (parsed?.isSubdomain) {
@@ -2363,6 +2380,31 @@ async function deploy(content, domainName = null, options = {}) {
2363
2380
  await publish(dotns, parsed, options.failOnPublishError);
2364
2381
  }
2365
2382
  }
2383
+ if (options.transferTo) {
2384
+ const transferTo = options.transferTo;
2385
+ await withSpan("deploy.transfer", `3. transfer ${name}.dot`, { "deploy.transfer.to": transferTo }, async () => {
2386
+ setDeployAttribute("deploy.transfer.worker", truncateAddress(options.signerAddress ?? ""));
2387
+ setDeployAttribute("deploy.transfer.to", transferTo);
2388
+ try {
2389
+ const transferRes = await dotns.transferName(name, transferTo, (s) => console.log(` ${s}`));
2390
+ setDeployAttribute("deploy.transfer.status", transferRes.status);
2391
+ if (transferRes.feeWei != null) setDeployAttribute("deploy.transfer.fee_wei", transferRes.feeWei.toString());
2392
+ console.log(` Handed ${name}.dot to ${transferTo} (${transferRes.status}${transferRes.txHash ? `, tx ${transferRes.txHash}` : ""}).`);
2393
+ } catch (e) {
2394
+ setDeployAttribute("deploy.transfer.status", "failed");
2395
+ const recover = `bulletin-deploy transfer ${name} --env ${envId}` + (options.suri ? ` --mnemonic "<your worker key>"` : "");
2396
+ try {
2397
+ dotns.disconnect();
2398
+ } catch {
2399
+ }
2400
+ throw new NonRetryableError(
2401
+ `Deploy succeeded but the handover to ${transferTo} failed: ${e.message}
2402
+ The name is owned by the worker with content set. Recover with:
2403
+ ${recover}`
2404
+ );
2405
+ }
2406
+ });
2407
+ }
2366
2408
  dotns.disconnect();
2367
2409
  });
2368
2410
  await withSpan("deploy.p2p-check", "3. p2p-check", { "deploy.domain": name }, async () => {
@@ -2,11 +2,11 @@ import {
2
2
  classifyErrorArea,
3
3
  isInteractive,
4
4
  promptYesNo
5
- } from "./chunk-LIAEGW76.js";
5
+ } from "./chunk-MI5B3UCM.js";
6
6
  import {
7
7
  VERSION,
8
8
  getCurrentSentryTraceId
9
- } from "./chunk-ZL7KBV52.js";
9
+ } from "./chunk-2BTYPNYW.js";
10
10
 
11
11
  // src/bug-report.ts
12
12
  import { execSync, execFileSync } from "child_process";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-GRPLHUYC.js";
4
4
  import {
5
5
  DOT_DAPP_ID
6
- } from "./chunk-HGEYWE2P.js";
6
+ } from "./chunk-PKQOUCPD.js";
7
7
 
8
8
  // src/sss-allowance-cache.ts
9
9
  import { mkdir, readFile, writeFile, unlink } from "fs/promises";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  captureWarning
3
- } from "./chunk-ZL7KBV52.js";
3
+ } from "./chunk-2BTYPNYW.js";
4
4
 
5
5
  // src/chunk-probe.ts
6
6
  import { Twox128, Blake2128Concat, decAnyMetadata, unifyMetadata } from "@polkadot-api/substrate-bindings";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-ZL7KBV52.js";
3
+ } from "./chunk-2BTYPNYW.js";
4
4
 
5
5
  // src/version-check.ts
6
6
  import { execSync, execFileSync } from "child_process";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-ZL7KBV52.js";
3
+ } from "./chunk-2BTYPNYW.js";
4
4
  import {
5
5
  loadEnvironments
6
6
  } from "./chunk-GL3U7K2B.js";
@@ -6,10 +6,10 @@ import {
6
6
  resolveDotnsConnectOptions,
7
7
  storeDirectory,
8
8
  storeFile
9
- } from "./chunk-NRDXKYWQ.js";
9
+ } from "./chunk-DY7RVMM5.js";
10
10
  import {
11
11
  DotNS
12
- } from "./chunk-T4WUEPAS.js";
12
+ } from "./chunk-VR3LF62E.js";
13
13
  import {
14
14
  getPopSelfServeConfig,
15
15
  loadEnvironments,
@@ -8,7 +8,7 @@ import {
8
8
  setDeploySentryTag,
9
9
  truncateAddress,
10
10
  withSpan
11
- } from "./chunk-ZL7KBV52.js";
11
+ } from "./chunk-2BTYPNYW.js";
12
12
  import {
13
13
  validateContractAddresses
14
14
  } from "./chunk-GL3U7K2B.js";
@@ -60,9 +60,16 @@ var FEE_FLOOR_REGISTER = ONE_PAS / 10n;
60
60
  var TOP_UP_TARGET = ONE_PAS / 2n;
61
61
  var SOURCE_BUFFER = ONE_PAS;
62
62
  var MINIMUM_REGISTER_STORAGE_DEPOSIT = 2000000000000n;
63
- var REGISTER_RENT_PRICE_WEI = 10n * 10n ** 18n;
64
- function bufferedRentNative(nativeToEthRatio) {
65
- return REGISTER_RENT_PRICE_WEI * 110n / (100n * nativeToEthRatio);
63
+ function registerDepositWei(userStatus, startingPriceWei) {
64
+ return userStatus === ProofOfPersonhoodStatus.NoStatus ? startingPriceWei : 0n;
65
+ }
66
+ function bufferedWeiToNative(weiValue, nativeToEthRatio) {
67
+ return weiToNative(weiValue * 110n / 100n, nativeToEthRatio);
68
+ }
69
+ function weiToNative(feeWei, nativeToEthRatio) {
70
+ if (feeWei === 0n) return 0n;
71
+ const native = feeWei / nativeToEthRatio;
72
+ return feeWei % nativeToEthRatio === 0n ? native : native + 1n;
66
73
  }
67
74
  var REPROVE_FEE_ESTIMATE = ONE_PAS / 100n;
68
75
  var REPROVE_FEE_SAFETY_MARGIN_PCT = 110n;
@@ -78,13 +85,13 @@ function resolveNativeTokenSymbol(envId) {
78
85
  if (envId.includes("rococo")) return "ROC";
79
86
  return "PAS";
80
87
  }
81
- function feeFloorFor(plannedAction, storageDeposit = MINIMUM_REGISTER_STORAGE_DEPOSIT, rentPriceNative = 0n) {
82
- if (plannedAction === "already-owned-by-us") return FEE_FLOOR_OWNED;
83
- return FEE_FLOOR_REGISTER + storageDeposit + rentPriceNative;
88
+ function feeFloorFor(plannedAction, storageDeposit = MINIMUM_REGISTER_STORAGE_DEPOSIT, rentPriceNative = 0n, transferFeeNative = 0n) {
89
+ if (plannedAction === "already-owned-by-us") return FEE_FLOOR_OWNED + transferFeeNative;
90
+ return FEE_FLOOR_REGISTER + storageDeposit + rentPriceNative + transferFeeNative;
84
91
  }
85
- function topUpTargetFor(plannedAction, storageDeposit = MINIMUM_REGISTER_STORAGE_DEPOSIT, rentPriceNative = 0n) {
86
- if (plannedAction === "already-owned-by-us") return TOP_UP_TARGET;
87
- return TOP_UP_TARGET + storageDeposit + rentPriceNative;
92
+ function topUpTargetFor(plannedAction, storageDeposit = MINIMUM_REGISTER_STORAGE_DEPOSIT, rentPriceNative = 0n, transferFeeNative = 0n) {
93
+ if (plannedAction === "already-owned-by-us") return TOP_UP_TARGET + transferFeeNative;
94
+ return TOP_UP_TARGET + storageDeposit + rentPriceNative + transferFeeNative;
88
95
  }
89
96
  var RPC_ENDPOINTS = [
90
97
  "wss://asset-hub-paseo.dotters.network",
@@ -130,6 +137,9 @@ function dotnsRetryBackoffMs(attempt, rand = Math.random) {
130
137
  const ceil = Math.min(DOTNS_RETRY_BASE_MS * 2 ** (attempt - 1), DOTNS_RETRY_MAX_MS);
131
138
  return Math.round(ceil * (0.5 + rand() * 0.5));
132
139
  }
140
+ function shouldRetryTxAttempt(attempt, maxAttempts, decision) {
141
+ return decision === "retry" && attempt < maxAttempts;
142
+ }
133
143
  function makeRetryStatusFilter(sink) {
134
144
  let buffered = false;
135
145
  return {
@@ -211,12 +221,18 @@ var DOTNS_REGISTRAR_CONTROLLER_ABI = [
211
221
  var DOTNS_REGISTRAR_ABI = [
212
222
  { inputs: [{ name: "tokenId", type: "uint256" }], name: "ownerOf", outputs: [{ name: "", type: "address" }], stateMutability: "view", type: "function" }
213
223
  ];
224
+ var DOTNS_REGISTRAR_TRANSFER_ABI = [
225
+ ...DOTNS_REGISTRAR_ABI,
226
+ { inputs: [{ name: "from", type: "address" }, { name: "to", type: "address" }, { name: "tokenId", type: "uint256" }], name: "transferFrom", outputs: [], stateMutability: "payable", type: "function" }
227
+ ];
214
228
  var POP_RULES_ABI = [
215
229
  { inputs: [{ name: "name", type: "string" }], name: "classifyName", outputs: [{ name: "requirement", type: "uint8" }, { name: "message", type: "string" }], stateMutability: "pure", type: "function" },
216
230
  { inputs: [{ name: "name", type: "string" }], name: "price", outputs: [{ name: "", type: "uint256" }], stateMutability: "view", type: "function" },
217
231
  { inputs: [{ name: "name", type: "string" }, { name: "userAddress", type: "address" }], name: "priceWithCheck", outputs: [{ name: "metadata", type: "tuple", components: [{ name: "price", type: "uint256" }, { name: "status", type: "uint8" }, { name: "userStatus", type: "uint8" }, { name: "message", type: "string" }] }], stateMutability: "view", type: "function" },
218
232
  { inputs: [{ name: "name", type: "string" }, { name: "userAddress", type: "address" }], name: "priceWithoutCheck", outputs: [{ name: "metadata", type: "tuple", components: [{ name: "price", type: "uint256" }, { name: "status", type: "uint8" }, { name: "userStatus", type: "uint8" }, { name: "message", type: "string" }] }], stateMutability: "view", type: "function" },
219
- { inputs: [{ name: "name", type: "string" }], name: "isBaseNameReserved", outputs: [{ name: "isReserved", type: "bool" }, { name: "reservationOwner", type: "address" }, { name: "expiryTimestamp", type: "uint64" }], stateMutability: "view", type: "function" }
233
+ { inputs: [{ name: "name", type: "string" }], name: "isBaseNameReserved", outputs: [{ name: "isReserved", type: "bool" }, { name: "reservationOwner", type: "address" }, { name: "expiryTimestamp", type: "uint64" }], stateMutability: "view", type: "function" },
234
+ { inputs: [{ name: "name", type: "string" }, { name: "from", type: "address" }, { name: "to", type: "address" }], name: "transferFloor", outputs: [{ name: "", type: "uint256" }], stateMutability: "view", type: "function" },
235
+ { inputs: [], name: "startingPrice", outputs: [{ name: "", type: "uint256" }], stateMutability: "view", type: "function" }
220
236
  ];
221
237
  var PERSONHOOD_ABI = [
222
238
  {
@@ -757,9 +773,13 @@ var ReviveClientWrapper = class _ReviveClientWrapper {
757
773
  } catch (e) {
758
774
  lastError = e;
759
775
  const decision = classifyTxRetryDecision(e);
760
- if (decision === "abort" || attempt === DOTNS_TX_MAX_ATTEMPTS) break;
761
- const ms = dotnsRetryBackoffMs(attempt);
762
776
  const msg = e?.message ?? String(e);
777
+ if (!shouldRetryTxAttempt(attempt, DOTNS_TX_MAX_ATTEMPTS, decision)) {
778
+ const reason = decision === "abort" ? "not retryable" : "out of attempts";
779
+ console.log(` ${label}: attempt ${attempt}/${DOTNS_TX_MAX_ATTEMPTS} failed (${msg}) \u2014 giving up (${reason})`);
780
+ break;
781
+ }
782
+ const ms = dotnsRetryBackoffMs(attempt);
763
783
  console.log(` ${label}: attempt ${attempt}/${DOTNS_TX_MAX_ATTEMPTS} failed (${msg}), retrying in ${ms}ms\u2026`);
764
784
  await new Promise((r) => setTimeout(r, ms));
765
785
  }
@@ -1453,6 +1473,49 @@ var DotNS = class {
1453
1473
  return { owned: false, owner: null };
1454
1474
  }
1455
1475
  }
1476
+ /** Live transfer-fee quote. transferFloor is a pure PopRules view — it
1477
+ * classifies the label and reads both tiers, so it works BEFORE the name is
1478
+ * registered (unlike quoteTransferFee, which reverts on an unregistered token). */
1479
+ async quoteTransferFloorNative(label, fromH160, toH160) {
1480
+ this.ensureConnected();
1481
+ const feeWei = await withTimeout(
1482
+ this.contractCall(this._contracts.POP_RULES, POP_RULES_ABI, "transferFloor", [validateDomainLabel(label), fromH160, toH160]),
1483
+ 3e4,
1484
+ "transferFloor"
1485
+ );
1486
+ return { feeWei, feeNative: weiToNative(feeWei, this._nativeToEthRatio) };
1487
+ }
1488
+ /** Hand `label`.dot from the connected signer (the worker, current owner) to
1489
+ * `toH160`, paying the transferFloor friction fee. Idempotent: a no-op if the
1490
+ * recipient already owns it; errors if a third party does. */
1491
+ async transferName(label, toH160, statusCallback = () => {
1492
+ }) {
1493
+ this.ensureConnected();
1494
+ const validated = validateDomainLabel(label);
1495
+ const tokenId = computeDomainTokenId(validated);
1496
+ const owner = await withTimeout(this.contractCall(this._contracts.DOTNS_REGISTRAR, DOTNS_REGISTRAR_TRANSFER_ABI, "ownerOf", [tokenId]), 3e4, "ownerOf");
1497
+ if (owner.toLowerCase() === toH160.toLowerCase()) {
1498
+ statusCallback("already owned by recipient");
1499
+ return { status: "skipped-already-owned" };
1500
+ }
1501
+ if (owner.toLowerCase() !== this.evmAddress.toLowerCase()) {
1502
+ throw new Error(`Cannot transfer ${validated}.dot: it is owned by ${owner}, not the worker ${this.evmAddress}.`);
1503
+ }
1504
+ const { feeWei, feeNative } = await this.quoteTransferFloorNative(validated, this.evmAddress, toH160);
1505
+ const txRes = await this.contractTransaction(
1506
+ this._contracts.DOTNS_REGISTRAR,
1507
+ feeNative,
1508
+ DOTNS_REGISTRAR_TRANSFER_ABI,
1509
+ "transferFrom",
1510
+ [this.evmAddress, toH160, tokenId],
1511
+ statusCallback
1512
+ );
1513
+ const after = await withTimeout(this.contractCall(this._contracts.DOTNS_REGISTRAR, DOTNS_REGISTRAR_TRANSFER_ABI, "ownerOf", [tokenId]), 3e4, "ownerOf");
1514
+ if (after.toLowerCase() !== toH160.toLowerCase()) {
1515
+ throw new Error(`Transfer of ${validated}.dot did not land: owner is ${after}, expected ${toH160}.`);
1516
+ }
1517
+ return { status: "ok", txHash: txRes.kind === TX_KIND_HASH ? txRes.hash : void 0, feeWei };
1518
+ }
1456
1519
  async getUserPopStatus(ownerAddress = null) {
1457
1520
  if (this._userPopStatusOverrideForTest !== null) {
1458
1521
  const result = this._userPopStatusOverrideForTest;
@@ -2161,10 +2224,10 @@ var DotNS = class {
2161
2224
  // View-only readiness check. Runs every chain read needed to predict whether
2162
2225
  // `register(label)` will succeed, so the caller can fail-fast BEFORE the
2163
2226
  // Bulletin chunk upload. Never writes to chain. See issue #100.
2164
- async preflight(label) {
2165
- return this._preflightInternal(label, false);
2227
+ async preflight(label, opts = {}) {
2228
+ return this._preflightInternal(label, false, opts.transferRecipientH160);
2166
2229
  }
2167
- async _preflightInternal(label, reproveAttempted) {
2230
+ async _preflightInternal(label, reproveAttempted, transferRecipientH160) {
2168
2231
  return withSpan("deploy.dotns.preflight", `preflight ${label}.dot`, {}, async () => {
2169
2232
  setDeployAttribute("deploy.dotns.reprove.auto", "false");
2170
2233
  this.ensureConnected();
@@ -2223,6 +2286,13 @@ var DotNS = class {
2223
2286
  signerFreeBalance
2224
2287
  };
2225
2288
  }
2289
+ let transferFeeNative = 0n;
2290
+ if (transferRecipientH160) {
2291
+ try {
2292
+ ({ feeNative: transferFeeNative } = await this.quoteTransferFloorNative(validated, this.evmAddress, transferRecipientH160));
2293
+ } catch {
2294
+ }
2295
+ }
2226
2296
  if (existingOwner !== null && existingOwner === selfAddress) {
2227
2297
  return await this.gateOnFeeBalance({
2228
2298
  label: validated,
@@ -2238,7 +2308,7 @@ var DotNS = class {
2238
2308
  canProceed: true,
2239
2309
  plannedAction: "already-owned-by-us",
2240
2310
  needsPopUpgrade: false
2241
- }, signerFreeBalance, isTestnet);
2311
+ }, signerFreeBalance, isTestnet, transferFeeNative);
2242
2312
  }
2243
2313
  if (isReserved && reservationOwner !== selfAddress) {
2244
2314
  return {
@@ -2307,7 +2377,7 @@ var DotNS = class {
2307
2377
  }
2308
2378
  if (reproveSucceeded) {
2309
2379
  console.log(` Continuing with registration of ${validated}.dot.`);
2310
- return this._preflightInternal(label, true);
2380
+ return this._preflightInternal(label, true, transferRecipientH160);
2311
2381
  }
2312
2382
  }
2313
2383
  const remediationMessage = formatPersonhoodRemediation(aliasState, this._popSelfServe, this._environmentId);
@@ -2377,7 +2447,7 @@ var DotNS = class {
2377
2447
  plannedAction: "register",
2378
2448
  needsPopUpgrade: false,
2379
2449
  targetPopStatus
2380
- }, signerFreeBalance, isTestnet);
2450
+ }, signerFreeBalance, isTestnet, transferFeeNative);
2381
2451
  });
2382
2452
  }
2383
2453
  // Final preflight stage: check the DotNS signer can pay tx fees on the
@@ -2386,15 +2456,23 @@ var DotNS = class {
2386
2456
  // phrase's Alice or Bob if the signer is short. Replaces the original
2387
2457
  // canProceed:true result with an actionable canProceed:false when even the
2388
2458
  // top-up can't get the signer above the threshold.
2389
- async gateOnFeeBalance(candidate, signerFreeBalance, isTestnet) {
2390
- const rentPriceNative = candidate.plannedAction === "register" ? bufferedRentNative(this._nativeToEthRatio) : 0n;
2391
- const feeFloor = feeFloorFor(candidate.plannedAction, this._registerStorageDeposit, rentPriceNative);
2459
+ async gateOnFeeBalance(candidate, signerFreeBalance, isTestnet, transferFeeNative = 0n) {
2460
+ let rentPriceNative = 0n;
2461
+ if (candidate.plannedAction === "register" && candidate.userStatus === ProofOfPersonhoodStatus.NoStatus) {
2462
+ const startingPriceWei = await withTimeout(
2463
+ this.contractCall(this._contracts.POP_RULES, POP_RULES_ABI, "startingPrice", []),
2464
+ 3e4,
2465
+ "startingPrice"
2466
+ );
2467
+ rentPriceNative = bufferedWeiToNative(startingPriceWei, this._nativeToEthRatio);
2468
+ }
2469
+ const feeFloor = feeFloorFor(candidate.plannedAction, this._registerStorageDeposit, rentPriceNative, transferFeeNative);
2392
2470
  let effectiveBalance = signerFreeBalance;
2393
2471
  let toppedUp;
2394
2472
  if (effectiveBalance < feeFloor && isTestnet) {
2395
2473
  setDeployAttribute("deploy.dotns.signer_below_floor", "true");
2396
2474
  console.log(` DotNS signer ${this.substrateAddress?.slice(0, 8)}... balance ${fmtPas(effectiveBalance)} PAS < ${fmtPas(feeFloor)} PAS floor \u2014 attempting testnet auto top-up...`);
2397
- const result = await this.attemptTestnetTopUp(this.substrateAddress, topUpTargetFor(candidate.plannedAction, this._registerStorageDeposit, rentPriceNative));
2475
+ const result = await this.attemptTestnetTopUp(this.substrateAddress, topUpTargetFor(candidate.plannedAction, this._registerStorageDeposit, rentPriceNative, transferFeeNative));
2398
2476
  if (result) {
2399
2477
  console.log(` Topped up ${fmtPas(result.transferred)} PAS from ${result.source}`);
2400
2478
  effectiveBalance += result.transferred;
@@ -2565,6 +2643,9 @@ export {
2565
2643
  TX_KIND_NONCE_ADVANCED,
2566
2644
  ATTR_TX_RESOLUTION_KIND,
2567
2645
  MINIMUM_REGISTER_STORAGE_DEPOSIT,
2646
+ registerDepositWei,
2647
+ bufferedWeiToNative,
2648
+ weiToNative,
2568
2649
  fmtPas,
2569
2650
  feeFloorFor,
2570
2651
  RPC_ENDPOINTS,
@@ -2581,6 +2662,7 @@ export {
2581
2662
  DOTNS_TX_MAX_ATTEMPTS,
2582
2663
  classifyTxRetryDecision,
2583
2664
  dotnsRetryBackoffMs,
2665
+ shouldRetryTxAttempt,
2584
2666
  makeRetryStatusFilter,
2585
2667
  DEFAULT_MNEMONIC,
2586
2668
  fetchNonce,
@@ -6,7 +6,7 @@ import * as path from "path";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@parity/product-deploy",
9
- version: "0.9.0-rc.6",
9
+ version: "0.10.0-rc.0",
10
10
  private: false,
11
11
  repository: {
12
12
  type: "git",
@@ -50,11 +50,11 @@ var package_default = {
50
50
  "tools/release-retry-wrapper.mjs"
51
51
  ],
52
52
  scripts: {
53
- build: "tsup src/index.ts src/deploy.ts src/dotns.ts src/pool.ts src/telemetry.ts src/memory-report.ts src/merkle.ts src/gh-pages-mirror.ts src/version-check.ts src/bug-report.ts src/run-state.ts src/environments.ts src/errors.ts src/manifest.ts src/chunk-probe.ts src/manifest-embed.ts src/manifest-fetch.ts src/manifest-roundtrip.ts src/incremental-stats.ts src/chunker.ts src/personhood/encoding.ts src/personhood/hashing.ts src/personhood/constants.ts src/personhood/member-key.ts src/personhood/people-client.ts src/personhood/proof-validity.ts src/personhood/reprove.ts src/personhood/bind-personal-id.ts src/personhood/claim-pgas.ts src/personhood/bind-paid-alias.ts src/personhood/bootstrap.ts src/personhood/chain-prereqs.ts src/manifest/types.ts src/manifest/schema.ts src/manifest/byte-budget.ts src/manifest/config-load.ts src/manifest/publish.ts src/auth/index.ts src/auth/vendor/index.ts src/auth/vendor/ui/index.ts src/auth-config.ts src/commands/login.ts src/commands/logout.ts src/commands/whoami.ts src/storage-signer.ts src/spinner.ts src/sss-allowance.ts src/sss-allowance-cache.ts --format esm --dts --clean --target node22",
53
+ build: "tsup src/index.ts src/deploy.ts src/dotns.ts src/pool.ts src/telemetry.ts src/memory-report.ts src/merkle.ts src/gh-pages-mirror.ts src/version-check.ts src/bug-report.ts src/run-state.ts src/environments.ts src/errors.ts src/manifest.ts src/chunk-probe.ts src/manifest-embed.ts src/manifest-fetch.ts src/manifest-roundtrip.ts src/incremental-stats.ts src/chunker.ts src/personhood/encoding.ts src/personhood/hashing.ts src/personhood/constants.ts src/personhood/member-key.ts src/personhood/people-client.ts src/personhood/proof-validity.ts src/personhood/reprove.ts src/personhood/bind-personal-id.ts src/personhood/claim-pgas.ts src/personhood/bind-paid-alias.ts src/personhood/bootstrap.ts src/personhood/chain-prereqs.ts src/manifest/types.ts src/manifest/schema.ts src/manifest/byte-budget.ts src/manifest/config-load.ts src/manifest/publish.ts src/auth/index.ts src/auth/vendor/index.ts src/auth/vendor/ui/index.ts src/auth-config.ts src/commands/login.ts src/commands/logout.ts src/commands/whoami.ts src/commands/transfer.ts src/storage-signer.ts src/spinner.ts src/sss-allowance.ts src/sss-allowance-cache.ts src/deploy-actors.ts --format esm --dts --clean --target node22",
54
54
  "refresh-environments": "node scripts/refresh-environments.mjs",
55
55
  postinstall: "patch-package || true",
56
56
  prepare: "npm run build",
57
- test: "npm run build && node --test test/test.js test/cli-help.test.js test/helpers/e2e-helpers.test.js test/environments.test.js test/refresh-environments.test.js test/chunk-sharing-report.test.js test/product-manifest.test.js test/cache-savings-totals.test.js test/error-pattern-signature.test.js test/exit-codes.test.js test/probe-env-health.test.js test/e2e-chain-calls.test.js test/auth-config.test.js test/whoami.test.js test/login.test.js test/logout.test.js test/auth-resolve.test.js test/storage-signer.test.js test/spinner.test.js test/sss-allowance.test.js test/sss-allowance-cache.test.js && npm run test:vendor",
57
+ test: "npm run build && node --test test/test.js test/cli-help.test.js test/helpers/e2e-helpers.test.js test/environments.test.js test/refresh-environments.test.js test/chunk-sharing-report.test.js test/product-manifest.test.js test/cache-savings-totals.test.js test/error-pattern-signature.test.js test/exit-codes.test.js test/probe-env-health.test.js test/e2e-chain-calls.test.js test/auth-config.test.js test/whoami.test.js test/login.test.js test/logout.test.js test/auth-resolve.test.js test/storage-signer.test.js test/spinner.test.js test/sss-allowance.test.js test/sss-allowance-cache.test.js test/dotns-transfer.test.js test/deploy-actors.test.js test/transfer-command.test.js test/dotns-register-fee.test.js && npm run test:vendor",
58
58
  "test:e2e": "npm run build && node --test test/e2e.test.js",
59
59
  "test:e2e:smoke": "bash scripts/e2e-pass.sh smoke",
60
60
  "test:e2e:pr": "bash scripts/e2e-pass.sh pr",
@@ -5,9 +5,9 @@ import {
5
5
  _decodeStorageValue,
6
6
  _resetProbeSession,
7
7
  probeChunks
8
- } from "./chunk-SHQZFLPN.js";
9
- import "./chunk-ZL7KBV52.js";
10
- import "./chunk-ELX3YZQG.js";
8
+ } from "./chunk-HUT626G6.js";
9
+ import "./chunk-2BTYPNYW.js";
10
+ import "./chunk-ZJCTG7HF.js";
11
11
  export {
12
12
  ChainProbeCrossValidationError,
13
13
  ChainProbeMetadataError,