@thenamespace/ens-components 1.3.0 → 1.4.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.
package/dist/index.d.ts CHANGED
@@ -172,6 +172,45 @@ declare const yearsFromSeconds: (seconds: number) => number;
172
172
  */
173
173
  declare const formatDurationSummary: (durationSeconds: number) => string;
174
174
 
175
+ /**
176
+ * Pure helpers for formatting ETH/USD pricing in the registration UI.
177
+ *
178
+ * Display rules:
179
+ * - ETH is rendered at a fixed 4 decimals.
180
+ * - Non-zero values smaller than 0.0001 ETH render as the literal "<0.0001"
181
+ * so cheap-gas rows stay informative without claiming a misleading exact
182
+ * figure. The USD subtitle is derived from wei (when available) so it
183
+ * stays numerically honest even when the ETH cell is rounded.
184
+ * - "Free" and "N/A" are sentinel strings — they bypass formatting and
185
+ * suppress the USD subtitle.
186
+ */
187
+ declare const ETH_SENTINELS: readonly ["Free", "N/A"];
188
+ type EthSentinel = (typeof ETH_SENTINELS)[number];
189
+ declare const isSentinel: (amount: number | string) => amount is EthSentinel;
190
+ /**
191
+ * Format an ETH amount for display at a fixed 4 decimals.
192
+ *
193
+ * Any positive value below the 4-decimal threshold renders as the literal
194
+ * "<0.0001" — honest about the rounding direction, and (crucially) avoids
195
+ * the misleading 1:1 read against the wei-derived USD subtitle when the
196
+ * true amount is much smaller than 0.0001 ETH.
197
+ */
198
+ declare const formatEth: (eth: number) => string;
199
+ /**
200
+ * Convert wei → USD. Note the `Number(bigint)` cast loses exactness above
201
+ * 2^53 wei (~0.009 ETH); the resulting error is < 1e-7 relative, which is
202
+ * invisible after `.toFixed(2)` for any realistic ENS registration total.
203
+ */
204
+ declare const usdFromWei: (wei: bigint, rate: number) => number;
205
+ /**
206
+ * Compute the `≈ $X.XX` subtitle for a fee row.
207
+ *
208
+ * Prefers `weiAmount` (precise) over the displayed string (which may have
209
+ * been rounded by `formatEth`). Returns `null` when the row is loading,
210
+ * unpriced, a sentinel ("Free"/"N/A"), or otherwise unparseable.
211
+ */
212
+ declare const computeUsd: (amount: number | string, weiAmount: bigint | undefined, ethUsdRate: number | null | undefined, isChecking: boolean) => string | null;
213
+
175
214
  interface EnsRecordsFormProps {
176
215
  resolverChainId?: number;
177
216
  resolverAddress?: Address;
@@ -459,20 +498,20 @@ interface ProgressBarProps {
459
498
  }
460
499
  declare function ProgressBar({ progress }: ProgressBarProps): react_jsx_runtime.JSX.Element;
461
500
 
501
+ interface FeeRow {
502
+ amount: number | string;
503
+ isChecking: boolean;
504
+ /** Optional precise wei value. When provided, USD is computed from wei
505
+ * rather than from the (possibly rounded) display string. See
506
+ * `usdFromWei` for the precision bound. */
507
+ weiAmount?: bigint;
508
+ }
462
509
  interface PricingDisplayProps {
463
- primaryFee: {
510
+ primaryFee: FeeRow & {
464
511
  label: string;
465
- amount: number | string;
466
- isChecking: boolean;
467
- };
468
- networkFees?: {
469
- amount: number | string;
470
- isChecking: boolean;
471
- };
472
- total: {
473
- amount: number | string;
474
- isChecking: boolean;
475
512
  };
513
+ networkFees?: FeeRow;
514
+ total: FeeRow;
476
515
  expiryPicker?: {
477
516
  durationSeconds: number;
478
517
  onDurationChange: (seconds: number) => void;
@@ -628,6 +667,15 @@ interface RentPriceResponse {
628
667
  wei: bigint;
629
668
  eth: number;
630
669
  }
670
+ interface RegistrationFeeEstimate {
671
+ wei: bigint;
672
+ eth: number;
673
+ gasEstimate: bigint;
674
+ gasPrice: bigint;
675
+ /** True when the precise eth_estimateGas+stateOverride path failed and we
676
+ * fell back to a heuristic. UI can mark the value as approximate. */
677
+ isHeuristic: boolean;
678
+ }
631
679
  interface RegistrationRequest {
632
680
  label: string;
633
681
  owner: Address;
@@ -641,6 +689,7 @@ declare const useRegisterENS: ({ isTestnet }: {
641
689
  }) => {
642
690
  isEnsAvailable: (label: string) => Promise<boolean>;
643
691
  getRegistrationPrice: (label: string, durationInSeconds?: number) => Promise<RentPriceResponse>;
692
+ estimateRegistrationFees: (request: RegistrationRequest) => Promise<RegistrationFeeEstimate>;
644
693
  sendCommitmentTx: (request: RegistrationRequest) => Promise<Hash>;
645
694
  sendRegisterTx: (request: RegistrationRequest) => Promise<{
646
695
  txHash: Hash;
@@ -879,5 +928,5 @@ declare const useAvatarClient: ({ isTestnet, domain }: UseAvatarClientParams) =>
879
928
  getErrorMessage: (err: unknown, imageType?: UploadImageType) => string;
880
929
  };
881
930
 
882
- export { Accordion, Alert, Button, Card, ChainIcon, ConnectAndSetChain, ContenthashIcon, ContenthashProtocol, ContractErrorLabel, Dropdown, DurationPicker, ENS_RESOLVER_ABI, EnsNameRegistrationForm, EnsRecordsForm, Icon, Input, ListingNetwork, ListingType, MIN_REGISTRATION_SECONDS, MULTICALL, Modal, ONE_DAY, ONE_YEAR, OffchainSubnameForm, PricingDisplay, ProfileHeader, ProgressBar, SET_ADDRESS_FUNC, SET_CONTENTHASH_FUNC, SET_TEXT_FUNC, SelectRecordsForm, ShurikenSpinner, SubnameMintForm, Text, TextRecordCategory, Textarea, ThemeProvider, Tooltip, TransactionPendingScreen, TxProgress, capitalize, convertEVMChainIdToCoinType, convertToMulticallResolverData, convertToResolverData, createEnsReferer, debounce, deepCopy, diffToEnsRecords, ensureFloatInput, equalsIgnoreCase, formatDurationSummary, formatFloat, getAvatarUploadErrorMessage, getBlockExplorer, getBlockExplorerAddressUrl, getBlockExplorerName, getBlockExplorerTransactionUrl, getChainIdForListingNetwork, getEnsAppUrl, getEnsRecordsDiff, getImageUploadErrorMessage, getSupportedAddressByChainId, getSupportedAddressByCoin, getSupportedAddressByName, getSupportedAddressMap, getSupportedChashByProtocol, getSupportedText, isCommitmentToNewErr, isContenthashValid, isUserDeniedError, roundDurationWithDay, secondsFromYears, secondsToDateInput, supportedAddresses, supportedContenthashRecords, supportedTexts, useAvatarClient, useENSResolver, useEthDollarValue, useMintManager, useMintSubname, useOffchainManager, useRegisterENS, useTheme, useWaitTransaction, validateEnsRecords, wait, yearsFromSeconds };
883
- export type { AccordionProps, AlertPosition, AlertProps, AlertVariant, ButtonProps, ButtonSize, ButtonVariant, ChainName$1 as ChainName, ConnectAndSetChainProps, ContractErrorLabelProps, DropdownProps, DurationPickerProps, EnsAddressRecord$1 as EnsAddressRecord, EnsContenthashRecord, EnsRecords$1 as EnsRecords, EnsRecordsDiff, EnsTextRecord$1 as EnsTextRecord, EstimatedFees, IconName, IconProps, InputProps, InputSize, InputType, ModalPresentation, ModalProps, ModalResponsivePresentation, ModalSize, NameListing, OffchainSubnameCreatedData, PricingDisplayProps, ProfileHeaderProps, RecordValidationError, RegistrationRequest, ShurikenSpinnerProps, SupportedContenthashRecord, SupportedEnsAddress, SupportedText, SupportedTextRecord, TextCategory, TextColor, TextProps, TextSize, TextWeight, TextareaProps, TextareaSize, ThemeContextValue, ThemeName, ThemeProviderProps, TooltipPosition, TooltipProps, UploadAvatarParams, UploadImageType };
931
+ export { Accordion, Alert, Button, Card, ChainIcon, ConnectAndSetChain, ContenthashIcon, ContenthashProtocol, ContractErrorLabel, Dropdown, DurationPicker, ENS_RESOLVER_ABI, ETH_SENTINELS, EnsNameRegistrationForm, EnsRecordsForm, Icon, Input, ListingNetwork, ListingType, MIN_REGISTRATION_SECONDS, MULTICALL, Modal, ONE_DAY, ONE_YEAR, OffchainSubnameForm, PricingDisplay, ProfileHeader, ProgressBar, SET_ADDRESS_FUNC, SET_CONTENTHASH_FUNC, SET_TEXT_FUNC, SelectRecordsForm, ShurikenSpinner, SubnameMintForm, Text, TextRecordCategory, Textarea, ThemeProvider, Tooltip, TransactionPendingScreen, TxProgress, capitalize, computeUsd, convertEVMChainIdToCoinType, convertToMulticallResolverData, convertToResolverData, createEnsReferer, debounce, deepCopy, diffToEnsRecords, ensureFloatInput, equalsIgnoreCase, formatDurationSummary, formatEth, formatFloat, getAvatarUploadErrorMessage, getBlockExplorer, getBlockExplorerAddressUrl, getBlockExplorerName, getBlockExplorerTransactionUrl, getChainIdForListingNetwork, getEnsAppUrl, getEnsRecordsDiff, getImageUploadErrorMessage, getSupportedAddressByChainId, getSupportedAddressByCoin, getSupportedAddressByName, getSupportedAddressMap, getSupportedChashByProtocol, getSupportedText, isCommitmentToNewErr, isContenthashValid, isSentinel, isUserDeniedError, roundDurationWithDay, secondsFromYears, secondsToDateInput, supportedAddresses, supportedContenthashRecords, supportedTexts, usdFromWei, useAvatarClient, useENSResolver, useEthDollarValue, useMintManager, useMintSubname, useOffchainManager, useRegisterENS, useTheme, useWaitTransaction, validateEnsRecords, wait, yearsFromSeconds };
932
+ export type { AccordionProps, AlertPosition, AlertProps, AlertVariant, ButtonProps, ButtonSize, ButtonVariant, ChainName$1 as ChainName, ConnectAndSetChainProps, ContractErrorLabelProps, DropdownProps, DurationPickerProps, EnsAddressRecord$1 as EnsAddressRecord, EnsContenthashRecord, EnsRecords$1 as EnsRecords, EnsRecordsDiff, EnsTextRecord$1 as EnsTextRecord, EstimatedFees, EthSentinel, IconName, IconProps, InputProps, InputSize, InputType, ModalPresentation, ModalProps, ModalResponsivePresentation, ModalSize, NameListing, OffchainSubnameCreatedData, PricingDisplayProps, ProfileHeaderProps, RecordValidationError, RegistrationFeeEstimate, RegistrationRequest, ShurikenSpinnerProps, SupportedContenthashRecord, SupportedEnsAddress, SupportedText, SupportedTextRecord, TextCategory, TextColor, TextProps, TextSize, TextWeight, TextareaProps, TextareaSize, ThemeContextValue, ThemeName, ThemeProviderProps, TooltipPosition, TooltipProps, UploadAvatarParams, UploadImageType };
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import * as zlib from 'zlib';
4
4
  import * as crypto$5 from 'crypto';
5
5
  import * as net from 'net';
6
6
  import * as viem from 'viem';
7
- import { ContractFunctionExecutionError, parseAbi, namehash as namehash$1, encodeFunctionData, toHex, toBytes as toBytes$1, pad, isAddress as isAddress$1, keccak256, formatEther, zeroAddress, zeroHash } from 'viem';
7
+ import { ContractFunctionExecutionError, parseAbi, namehash as namehash$1, encodeFunctionData, toHex, toBytes as toBytes$1, pad, isAddress as isAddress$1, keccak256, parseEther, padHex, formatEther, zeroAddress, concatHex, zeroHash } from 'viem';
8
8
  import * as chains$1 from 'viem/chains';
9
9
  import { baseSepolia, sepolia, mainnet, optimism, base as base$3, zoraSepolia, zora, celoAlfajores, celo, polygonMumbai, polygon, arbitrumSepolia, arbitrum, optimismSepolia } from 'viem/chains';
10
10
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
@@ -11686,6 +11686,24 @@ const formatDurationSummary = (durationSeconds) => {
11686
11686
  return parts.slice(0, 2).join(", ") || "0 days";
11687
11687
  };
11688
11688
 
11689
+ const ETH_SENTINELS = ["Free", "N/A"];
11690
+ const isSentinel = (amount) => amount === "Free" || amount === "N/A";
11691
+ const formatEth = (eth) => {
11692
+ if (!isFinite(eth) || eth <= 0) return "0.0000";
11693
+ if (eth < 1e-4) return "<0.0001";
11694
+ return eth.toFixed(4);
11695
+ };
11696
+ const usdFromWei = (wei, rate) => Number(wei) * rate / 1e18;
11697
+ const computeUsd = (amount, weiAmount, ethUsdRate, isChecking) => {
11698
+ if (!ethUsdRate || isChecking || isSentinel(amount)) return null;
11699
+ if (weiAmount !== void 0 && weiAmount > 0n) {
11700
+ return usdFromWei(weiAmount, ethUsdRate).toFixed(2);
11701
+ }
11702
+ const eth = parseFloat(String(amount).replace(/^~/, ""));
11703
+ if (isNaN(eth) || eth <= 0) return null;
11704
+ return (eth * ethUsdRate).toFixed(2);
11705
+ };
11706
+
11689
11707
  const isValidEmvAddress = (value) => {
11690
11708
  return isAddress$1(value);
11691
11709
  };
@@ -12017,6 +12035,7 @@ const DurationPicker = ({
12017
12035
  ] });
12018
12036
  };
12019
12037
 
12038
+ const NBSP = "\xA0";
12020
12039
  const PricingDisplay = ({
12021
12040
  primaryFee,
12022
12041
  networkFees,
@@ -12026,14 +12045,10 @@ const PricingDisplay = ({
12026
12045
  className = ""
12027
12046
  }) => {
12028
12047
  const totalLoading = total.isChecking || primaryFee.isChecking || networkFees?.isChecking;
12029
- const totalUsd = React__default.useMemo(() => {
12030
- if (!ethUsdRate || totalLoading || total.amount === "Free" || total.amount === "N/A") {
12031
- return null;
12032
- }
12033
- const eth = parseFloat(String(total.amount));
12034
- if (isNaN(eth) || eth <= 0) return null;
12035
- return (eth * ethUsdRate).toFixed(2);
12036
- }, [ethUsdRate, total.amount, totalLoading]);
12048
+ const totalUsd = React__default.useMemo(
12049
+ () => computeUsd(total.amount, total.weiAmount, ethUsdRate, !!totalLoading),
12050
+ [ethUsdRate, total.amount, total.weiAmount, totalLoading]
12051
+ );
12037
12052
  return /* @__PURE__ */ jsxs("div", { className: `ens-registration-pricing ${className}`, children: [
12038
12053
  expiryPicker && /* @__PURE__ */ jsx("div", { className: "ens-expiry-picker mb-2", children: /* @__PURE__ */ jsx(
12039
12054
  DurationPicker,
@@ -12045,23 +12060,17 @@ const PricingDisplay = ({
12045
12060
  ) }),
12046
12061
  /* @__PURE__ */ jsxs("div", { className: "d-flex justify-content-between align-items-center mb-1", children: [
12047
12062
  /* @__PURE__ */ jsx(Text, { size: "sm", color: "grey", children: primaryFee.label }),
12048
- primaryFee.isChecking ? /* @__PURE__ */ jsx(ShurikenSpinner, { size: 16 }) : /* @__PURE__ */ jsx(Text, { size: "sm", color: "grey", children: primaryFee.amount === "Free" ? "Free" : `${primaryFee.amount} ETH` })
12063
+ /* @__PURE__ */ jsx(Text, { size: "sm", color: "grey", children: primaryFee.isChecking ? /* @__PURE__ */ jsx(ShurikenSpinner, { size: 14 }) : isSentinel(primaryFee.amount) ? primaryFee.amount : `${primaryFee.amount} ETH` })
12049
12064
  ] }),
12050
12065
  networkFees && /* @__PURE__ */ jsxs("div", { className: "d-flex justify-content-between align-items-center mb-1", children: [
12051
12066
  /* @__PURE__ */ jsx(Text, { size: "sm", color: "grey", children: "Est. network fees" }),
12052
- networkFees.isChecking ? /* @__PURE__ */ jsx(ShurikenSpinner, { size: 16 }) : /* @__PURE__ */ jsxs(Text, { size: "sm", color: "grey", children: [
12053
- networkFees.amount,
12054
- " ETH"
12055
- ] })
12067
+ /* @__PURE__ */ jsx(Text, { size: "sm", color: "grey", children: networkFees.isChecking ? /* @__PURE__ */ jsx(ShurikenSpinner, { size: 14 }) : networkFees.amount === "N/A" ? "N/A" : `${networkFees.amount} ETH` })
12056
12068
  ] }),
12057
12069
  /* @__PURE__ */ jsxs("div", { className: "d-flex justify-content-between align-items-center mt-2 total-fee", children: [
12058
12070
  /* @__PURE__ */ jsx(Text, { size: "lg", weight: "bold", children: "Total" }),
12059
- totalLoading ? /* @__PURE__ */ jsx(ShurikenSpinner, { size: 20 }) : /* @__PURE__ */ jsxs("div", { style: { textAlign: "right" }, children: [
12060
- /* @__PURE__ */ jsx(Text, { size: "lg", weight: "bold", children: total.amount === "Free" ? "Free" : `${total.amount} ETH` }),
12061
- totalUsd && /* @__PURE__ */ jsxs(Text, { size: "xs", color: "grey", children: [
12062
- "\u2248 $",
12063
- totalUsd
12064
- ] })
12071
+ /* @__PURE__ */ jsxs("div", { style: { textAlign: "right" }, children: [
12072
+ /* @__PURE__ */ jsx(Text, { size: "lg", weight: "bold", children: totalLoading ? /* @__PURE__ */ jsx(ShurikenSpinner, { size: 18 }) : isSentinel(total.amount) ? total.amount : `${total.amount} ETH` }),
12073
+ /* @__PURE__ */ jsx(Text, { size: "xs", color: "grey", children: totalUsd ? `\u2248 $${totalUsd}` : NBSP })
12065
12074
  ] })
12066
12075
  ] })
12067
12076
  ] });
@@ -64136,6 +64145,18 @@ const ABIS = {
64136
64145
  RESOLVER
64137
64146
  };
64138
64147
 
64148
+ const COMMITMENTS_SLOT = 1n;
64149
+ const FIVE_MINUTES_SECONDS = 5 * 60;
64150
+ const HEURISTIC_COMMIT_GAS = 50000n;
64151
+ const HEURISTIC_REGISTER_BASE_GAS = 240000n;
64152
+ const HEURISTIC_GAS_PER_RECORD = 50000n;
64153
+ const isStateOverrideRejection = (err) => {
64154
+ const e = err;
64155
+ const code = e?.code ?? e?.cause?.code;
64156
+ if (code === -32602 || code === -32601 || code === -32e3) return true;
64157
+ const msg = `${e?.shortMessage ?? ""} ${e?.message ?? ""}`.toLowerCase();
64158
+ return msg.includes("state override") || msg.includes("stateoverride") || msg.includes("too many arguments") || msg.includes("invalid argument 2") || msg.includes("3rd parameter") || msg.includes("does not support");
64159
+ };
64139
64160
  const NAMESPACE_REFERRER_ADDRESS = "0xb7B18611b8C51B4B3F400BaF09DB49E61e0aF044";
64140
64161
  const ENS_REGISTRY_ABI = parseAbi([
64141
64162
  "function owner(bytes32) view returns (address)"
@@ -64160,7 +64181,7 @@ const useRegisterENS = ({ isTestnet }) => {
64160
64181
  const totalPrice = price.base + price.premium;
64161
64182
  return {
64162
64183
  wei: totalPrice,
64163
- eth: formatFloat(formatEther(totalPrice, "wei"), 4)
64184
+ eth: parseFloat(formatEther(totalPrice))
64164
64185
  };
64165
64186
  };
64166
64187
  const isEnsAvailable = async (label) => {
@@ -64234,6 +64255,87 @@ const useRegisterENS = ({ isTestnet }) => {
64234
64255
  const tx = await walletClient.writeContract(contractRequest);
64235
64256
  return { txHash: tx, price };
64236
64257
  };
64258
+ const getEffectiveGasPrice = () => publicClient.getGasPrice();
64259
+ const commitmentStorageSlot = (commitment) => keccak256(
64260
+ concatHex([padHex(commitment, { size: 32 }), padHex(toHex(COMMITMENTS_SLOT), { size: 32 })])
64261
+ );
64262
+ const estimateRegistrationFees = async (request) => {
64263
+ const fullName = `${request.label}.eth`;
64264
+ const resolverData = convertToResolverData(fullName, request.records);
64265
+ const controller = getEthController();
64266
+ const commitmentParams = {
64267
+ label: request.label,
64268
+ owner: request.owner,
64269
+ duration: BigInt(request.durationInSeconds),
64270
+ secret: keccak256(toBytes$1(request.secret)),
64271
+ resolver: getPublicResolver(),
64272
+ data: resolverData,
64273
+ reverseRecord: 0,
64274
+ referrer: getRegReferrer(request)
64275
+ };
64276
+ const commitment = await publicClient.readContract({
64277
+ functionName: "makeCommitment",
64278
+ abi: ABIS.ETH_REGISTRAR_CONTOLLER,
64279
+ address: controller,
64280
+ args: [commitmentParams]
64281
+ });
64282
+ const price = await getRegistrationPrice(request.label, request.durationInSeconds);
64283
+ const fiveMinAgo = BigInt(Math.floor(Date.now() / 1e3) - FIVE_MINUTES_SECONDS);
64284
+ const balanceOverride = price.wei * 2n + parseEther("1000000");
64285
+ const gasPricePromise = getEffectiveGasPrice();
64286
+ const commitGasPromise = publicClient.estimateContractGas({
64287
+ address: controller,
64288
+ abi: ABIS.ETH_REGISTRAR_CONTOLLER,
64289
+ functionName: "commit",
64290
+ args: [commitment],
64291
+ account: request.owner
64292
+ }).catch(() => HEURISTIC_COMMIT_GAS);
64293
+ const registerGasPromise = publicClient.estimateContractGas({
64294
+ address: controller,
64295
+ abi: ABIS.ETH_REGISTRAR_CONTOLLER,
64296
+ functionName: "register",
64297
+ args: [commitmentParams],
64298
+ account: request.owner,
64299
+ value: price.wei,
64300
+ stateOverride: [
64301
+ {
64302
+ address: controller,
64303
+ stateDiff: [
64304
+ {
64305
+ slot: commitmentStorageSlot(commitment),
64306
+ value: padHex(toHex(fiveMinAgo), { size: 32 })
64307
+ }
64308
+ ]
64309
+ },
64310
+ { address: request.owner, balance: balanceOverride }
64311
+ ]
64312
+ }).then((g) => ({ gas: g, isHeuristic: false })).catch((err) => {
64313
+ if (typeof console !== "undefined") {
64314
+ console.warn(
64315
+ "[useRegisterENS] register-gas estimation failed; using heuristic.",
64316
+ isStateOverrideRejection(err) ? "(state override unsupported)" : "",
64317
+ err
64318
+ );
64319
+ }
64320
+ const recordCount = (request.records.addresses?.length ?? 0) + (request.records.texts?.length ?? 0);
64321
+ const heuristic = HEURISTIC_REGISTER_BASE_GAS + BigInt(recordCount) * HEURISTIC_GAS_PER_RECORD;
64322
+ return { gas: heuristic, isHeuristic: true };
64323
+ });
64324
+ const [commitGas, registerResult, gasPrice] = await Promise.all([
64325
+ commitGasPromise,
64326
+ registerGasPromise,
64327
+ gasPricePromise
64328
+ ]);
64329
+ const totalGas = commitGas + registerResult.gas;
64330
+ const totalWei = totalGas * gasPrice;
64331
+ return {
64332
+ wei: totalWei,
64333
+ eth: parseFloat(formatEther(totalWei)),
64334
+ gasEstimate: totalGas,
64335
+ gasPrice,
64336
+ isHeuristic: registerResult.isHeuristic
64337
+ };
64338
+ };
64237
64339
  const getEthController = () => distExports$1.getEnsContracts(isTestnet).ethRegistrarController;
64238
64340
  const getEnsRegistry = () => distExports$1.getEnsContracts(isTestnet).ensRegistry;
64239
64341
  const getPublicResolver = () => distExports$1.getEnsContracts(isTestnet).publicResolver;
@@ -64244,6 +64346,7 @@ const useRegisterENS = ({ isTestnet }) => {
64244
64346
  return {
64245
64347
  isEnsAvailable,
64246
64348
  getRegistrationPrice,
64349
+ estimateRegistrationFees,
64247
64350
  sendCommitmentTx,
64248
64351
  sendRegisterTx
64249
64352
  };
@@ -89487,19 +89590,15 @@ const RegistrationSummary = ({
89487
89590
  const { isConnected } = useAccount();
89488
89591
  const { ethUsdRate } = useEthDollarValue();
89489
89592
  const { isEnsAvailable, getRegistrationPrice } = useRegisterENS({ isTestnet });
89490
- const { regPrice, regFees, regTotal } = useMemo(() => {
89491
- let regPrice2 = 0;
89492
- let regFees2 = 0;
89493
- let total = 0;
89494
- if (price) {
89495
- regPrice2 += price.eth;
89496
- total += price.eth;
89497
- }
89498
- if (transactionFees) {
89499
- regFees2 += transactionFees.price.eth;
89500
- total += transactionFees.price.eth;
89501
- }
89502
- return { regFees: regFees2, regPrice: regPrice2, regTotal: formatFloat(total, 5) };
89593
+ const { regPrice, regFees, regTotal, regTotalWei } = useMemo(() => {
89594
+ const priceEth = price?.eth ?? 0;
89595
+ const feesEth = transactionFees?.price.eth ?? 0;
89596
+ const heuristicPrefix = transactionFees?.isHeuristic ? "~" : "";
89597
+ const regPrice2 = priceEth > 0 ? formatEth(priceEth) : "0.0000";
89598
+ const regFees2 = transactionFees?.failed ? "N/A" : `${heuristicPrefix}${formatEth(feesEth)}`;
89599
+ const regTotal2 = transactionFees?.failed ? "N/A" : `${heuristicPrefix}${formatEth(priceEth + feesEth)}`;
89600
+ const regTotalWei2 = (price?.wei ?? 0n) + (transactionFees?.price.wei ?? 0n);
89601
+ return { regPrice: regPrice2, regFees: regFees2, regTotal: regTotal2, regTotalWei: regTotalWei2 };
89503
89602
  }, [price, transactionFees]);
89504
89603
  const checkAvailability = async (labelToCheck) => {
89505
89604
  try {
@@ -89521,6 +89620,14 @@ const RegistrationSummary = ({
89521
89620
  onPriceChange({ isChecking: false, eth: -1, wei: 0n });
89522
89621
  }
89523
89622
  };
89623
+ useEffect(() => {
89624
+ if (label.length >= MIN_ENS_LEN$2) {
89625
+ onNameValidationChange({ isChecking: true, isTaken: false });
89626
+ onPriceChange({ isChecking: true, eth: 0, wei: 0n });
89627
+ checkAvailability(label);
89628
+ checkRegistrationPrice(label, durationSeconds);
89629
+ }
89630
+ }, []);
89524
89631
  const debouncedCheckAvailability = useCallback(
89525
89632
  debounce((labelToCheck) => checkAvailability(labelToCheck), 500),
89526
89633
  []
@@ -89562,7 +89669,7 @@ const RegistrationSummary = ({
89562
89669
  );
89563
89670
  const nextBtnDisabled = label.length < MIN_ENS_LEN$2 || nameValidation.isChecking || nameValidation.isTaken;
89564
89671
  const totalPriceLoading = transactionFees?.isChecking || price.isChecking;
89565
- const transactionFeesLoading = transactionFees?.isChecking || false;
89672
+ const transactionFeesLoading = transactionFees?.isChecking || price.isChecking;
89566
89673
  return /* @__PURE__ */ jsxs("div", { className: "ens-registration-summary", children: [
89567
89674
  !hideBanner && /* @__PURE__ */ jsx("div", { className: "d-flex justify-content-center", children: /* @__PURE__ */ jsx(
89568
89675
  "img",
@@ -89613,13 +89720,11 @@ const RegistrationSummary = ({
89613
89720
  amount: regPrice,
89614
89721
  isChecking: price.isChecking
89615
89722
  },
89616
- networkFees: {
89617
- amount: regFees,
89618
- isChecking: transactionFeesLoading
89619
- },
89723
+ networkFees: transactionFees ? { amount: regFees, isChecking: transactionFeesLoading } : void 0,
89620
89724
  total: {
89621
89725
  amount: regTotal,
89622
- isChecking: totalPriceLoading
89726
+ isChecking: totalPriceLoading,
89727
+ weiAmount: regTotalWei
89623
89728
  },
89624
89729
  expiryPicker: {
89625
89730
  durationSeconds,
@@ -89846,13 +89951,19 @@ const CommitmentStep = ({
89846
89951
  return;
89847
89952
  }
89848
89953
  try {
89849
- await waitTx({ hash: tx });
89954
+ const receipt = await waitTx({ hash: tx });
89955
+ const commitFeeWei = receipt.gasUsed * (receipt.effectiveGasPrice || 0n);
89850
89956
  setCommitTxStatus({ sent: true, completed: true, hash: tx });
89851
89957
  setTimeout(() => {
89852
89958
  onStateUpdated({
89853
89959
  ...state,
89854
89960
  step: ProcessSteps.TimerStarted,
89855
- commitment: { tx, completed: true, time: (/* @__PURE__ */ new Date()).getTime() }
89961
+ commitment: {
89962
+ tx,
89963
+ completed: true,
89964
+ time: (/* @__PURE__ */ new Date()).getTime(),
89965
+ feeWei: commitFeeWei
89966
+ }
89856
89967
  });
89857
89968
  setCommitTxStatus({ sent: false, completed: false, hash: "" });
89858
89969
  }, 1e3);
@@ -90072,9 +90183,10 @@ const RegistrationStep = ({
90072
90183
  try {
90073
90184
  const receipt = await waitTx({ hash: tx });
90074
90185
  setCommitTxStatus({ sent: true, completed: true, hash: tx });
90075
- const gasUsed = receipt.gasUsed;
90076
- const gasPrice = receipt.effectiveGasPrice || BigInt(0);
90077
- const transactionFeesEth = formatEther(gasUsed * gasPrice);
90186
+ const registerFeeWei = receipt.gasUsed * (receipt.effectiveGasPrice || 0n);
90187
+ const commitFeeWei = state.commitment?.feeWei ?? 0n;
90188
+ const totalFeeWei = registerFeeWei + commitFeeWei;
90189
+ const transactionFeesEth = formatEther(totalFeeWei);
90078
90190
  const totalCost = (registrationPrice + parseFloat(transactionFeesEth)).toString();
90079
90191
  const expiryDate = new Date(Date.now() + state.durationInSeconds * 1e3);
90080
90192
  const formattedExpiryDate = expiryDate.toLocaleDateString("en-US", {
@@ -91062,19 +91174,25 @@ const RegistrationProcess = ({
91062
91174
  ] });
91063
91175
  };
91064
91176
 
91177
+ const REG_SECRET_PLACEHOLDER = "0x0000000000000000000000000000000000000000000000000000000000000001";
91178
+ const FEE_ESTIMATE_OWNER_PLACEHOLDER = "0x0000000000000000000000000000000000000001";
91065
91179
  const getLabel = (name) => {
91066
91180
  if (!name) return "";
91067
91181
  if (name.split(".").length !== 1) return name.split(".")[0];
91068
91182
  return name;
91069
91183
  };
91070
91184
  const EnsNameRegistrationForm = (props) => {
91185
+ const { address: connectedAddress } = useAccount();
91186
+ const { estimateRegistrationFees } = useRegisterENS({ isTestnet: props.isTestnet });
91071
91187
  const [label, setLabel] = useState(getLabel(props.name));
91072
91188
  const [step, setStep] = useState(0 /* Summary */);
91073
91189
  const [durationSeconds, setDurationSeconds] = useState(() => secondsFromYears(/* @__PURE__ */ new Date(), 1));
91074
91190
  const [regTxFees, setRegTxFees] = useState({
91075
91191
  estimatedGas: 0,
91076
91192
  isChecking: false,
91077
- price: { wei: 0n, eth: 1e-4 }
91193
+ failed: false,
91194
+ isHeuristic: false,
91195
+ price: { wei: 0n, eth: 0 }
91078
91196
  });
91079
91197
  const [price, setPrice] = useState({ isChecking: false, wei: 0n, eth: 0 });
91080
91198
  const [nameValidation, setNameValidation] = useState({ isChecking: false, isTaken: false });
@@ -91092,6 +91210,74 @@ const EnsNameRegistrationForm = (props) => {
91092
91210
  [ensRecords, ensRecordTemplate]
91093
91211
  );
91094
91212
  const [successData, setSuccessData] = useState();
91213
+ const feeRequestRef = useRef(0);
91214
+ const estimateFnRef = useRef(estimateRegistrationFees);
91215
+ const referrerRef = useRef(props.referrer);
91216
+ estimateFnRef.current = estimateRegistrationFees;
91217
+ referrerRef.current = props.referrer;
91218
+ const debouncedEstimate = useMemo(
91219
+ () => debounce(
91220
+ (requestId, params) => {
91221
+ estimateFnRef.current({
91222
+ label: params.label,
91223
+ owner: params.owner,
91224
+ durationInSeconds: params.durationInSeconds,
91225
+ secret: REG_SECRET_PLACEHOLDER,
91226
+ records: params.records,
91227
+ referrer: referrerRef.current
91228
+ }).then((result) => {
91229
+ if (feeRequestRef.current !== requestId) return;
91230
+ setRegTxFees({
91231
+ isChecking: false,
91232
+ failed: false,
91233
+ isHeuristic: result.isHeuristic,
91234
+ estimatedGas: Number(result.gasEstimate),
91235
+ price: { wei: result.wei, eth: result.eth }
91236
+ });
91237
+ }).catch(() => {
91238
+ if (feeRequestRef.current !== requestId) return;
91239
+ setRegTxFees({
91240
+ isChecking: false,
91241
+ failed: true,
91242
+ isHeuristic: false,
91243
+ estimatedGas: 0,
91244
+ price: { wei: 0n, eth: 0 }
91245
+ });
91246
+ });
91247
+ },
91248
+ 500
91249
+ ),
91250
+ []
91251
+ );
91252
+ useEffect(() => {
91253
+ if (!label || label.length < 3 || nameValidation.isChecking || nameValidation.isTaken || price.isChecking || price.eth <= 0) {
91254
+ feeRequestRef.current += 1;
91255
+ setRegTxFees((prev) => prev.isChecking ? { ...prev, isChecking: false } : prev);
91256
+ return;
91257
+ }
91258
+ const requestId = feeRequestRef.current + 1;
91259
+ feeRequestRef.current = requestId;
91260
+ setRegTxFees(
91261
+ (prev) => prev.isChecking && !prev.failed ? prev : { ...prev, isChecking: true, failed: false }
91262
+ );
91263
+ debouncedEstimate(requestId, {
91264
+ label,
91265
+ // Estimation works without a wallet — gas price comes from the network
91266
+ // and the owner's balance is state-overridden in the estimator.
91267
+ owner: connectedAddress ?? FEE_ESTIMATE_OWNER_PLACEHOLDER,
91268
+ durationInSeconds: durationSeconds,
91269
+ records: ensRecords
91270
+ });
91271
+ }, [
91272
+ connectedAddress,
91273
+ label,
91274
+ durationSeconds,
91275
+ ensRecords,
91276
+ nameValidation.isChecking,
91277
+ nameValidation.isTaken,
91278
+ price.isChecking,
91279
+ price.eth
91280
+ ]);
91095
91281
  const handleSaveRecords = () => {
91096
91282
  setEnsRecords(deepCopy(ensRecordTemplate));
91097
91283
  setShowProfile(false);
@@ -93646,5 +93832,5 @@ const useTheme = () => {
93646
93832
  return ctx;
93647
93833
  };
93648
93834
 
93649
- export { Accordion, Alert, Button, Card, ChainIcon, ConnectAndSetChain, ContenthashIcon, ContenthashProtocol, ContractErrorLabel, Dropdown, DurationPicker, ENS_RESOLVER_ABI, EnsNameRegistrationForm, EnsRecordsForm, Icon, Input, ListingNetwork, ListingType, MIN_REGISTRATION_SECONDS, MULTICALL, Modal, ONE_DAY, ONE_YEAR, OffchainSubnameForm, PricingDisplay, ProfileHeader, ProgressBar, SET_ADDRESS_FUNC, SET_CONTENTHASH_FUNC, SET_TEXT_FUNC, SelectRecordsForm, ShurikenSpinner, SubnameMintForm, Text, TextRecordCategory, Textarea, ThemeProvider, Tooltip, TransactionPendingScreen, TxProgress, capitalize, convertEVMChainIdToCoinType, convertToMulticallResolverData, convertToResolverData, createEnsReferer, debounce, deepCopy, diffToEnsRecords, ensureFloatInput, equalsIgnoreCase, formatDurationSummary, formatFloat, getAvatarUploadErrorMessage, getBlockExplorer, getBlockExplorerAddressUrl, getBlockExplorerName, getBlockExplorerTransactionUrl, getChainIdForListingNetwork, getEnsAppUrl, getEnsRecordsDiff, getImageUploadErrorMessage, getSupportedAddressByChainId, getSupportedAddressByCoin, getSupportedAddressByName, getSupportedAddressMap, getSupportedChashByProtocol, getSupportedText, isCommitmentToNewErr, isContenthashValid, isUserDeniedError, roundDurationWithDay, secondsFromYears, secondsToDateInput, supportedAddresses, supportedContenthashRecords, supportedTexts, useAvatarClient, useENSResolver, useEthDollarValue, useMintManager, useMintSubname, useOffchainManager, useRegisterENS, useTheme, useWaitTransaction, validateEnsRecords, wait, yearsFromSeconds };
93835
+ export { Accordion, Alert, Button, Card, ChainIcon, ConnectAndSetChain, ContenthashIcon, ContenthashProtocol, ContractErrorLabel, Dropdown, DurationPicker, ENS_RESOLVER_ABI, ETH_SENTINELS, EnsNameRegistrationForm, EnsRecordsForm, Icon, Input, ListingNetwork, ListingType, MIN_REGISTRATION_SECONDS, MULTICALL, Modal, ONE_DAY, ONE_YEAR, OffchainSubnameForm, PricingDisplay, ProfileHeader, ProgressBar, SET_ADDRESS_FUNC, SET_CONTENTHASH_FUNC, SET_TEXT_FUNC, SelectRecordsForm, ShurikenSpinner, SubnameMintForm, Text, TextRecordCategory, Textarea, ThemeProvider, Tooltip, TransactionPendingScreen, TxProgress, capitalize, computeUsd, convertEVMChainIdToCoinType, convertToMulticallResolverData, convertToResolverData, createEnsReferer, debounce, deepCopy, diffToEnsRecords, ensureFloatInput, equalsIgnoreCase, formatDurationSummary, formatEth, formatFloat, getAvatarUploadErrorMessage, getBlockExplorer, getBlockExplorerAddressUrl, getBlockExplorerName, getBlockExplorerTransactionUrl, getChainIdForListingNetwork, getEnsAppUrl, getEnsRecordsDiff, getImageUploadErrorMessage, getSupportedAddressByChainId, getSupportedAddressByCoin, getSupportedAddressByName, getSupportedAddressMap, getSupportedChashByProtocol, getSupportedText, isCommitmentToNewErr, isContenthashValid, isSentinel, isUserDeniedError, roundDurationWithDay, secondsFromYears, secondsToDateInput, supportedAddresses, supportedContenthashRecords, supportedTexts, usdFromWei, useAvatarClient, useENSResolver, useEthDollarValue, useMintManager, useMintSubname, useOffchainManager, useRegisterENS, useTheme, useWaitTransaction, validateEnsRecords, wait, yearsFromSeconds };
93650
93836
  //# sourceMappingURL=index.js.map