@openzeppelin/ui-react 3.0.0 → 3.1.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.mjs CHANGED
@@ -1,10 +1,11 @@
1
1
  import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
2
2
  import { AnalyticsService, cn, isRecordWithProperties, logger } from "@openzeppelin/ui-utils";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { QueryClient, useQuery } from "@tanstack/react-query";
4
5
  import { Button } from "@openzeppelin/ui-components";
5
6
 
6
7
  //#region src/version.ts
7
- const VERSION = "3.0.0";
8
+ const VERSION = "3.1.0";
8
9
 
9
10
  //#endregion
10
11
  //#region src/hooks/AdapterContext.tsx
@@ -244,12 +245,12 @@ const WALLET_STATE_CONTEXT_KEY = Symbol.for("@openzeppelin/ui-react/WalletStateC
244
245
  * 4. For React contexts specifically, having the same context object is what matters,
245
246
  * and this pattern guarantees that after initialization
246
247
  */
247
- function getOrCreateSharedContext() {
248
+ function getOrCreateSharedContext$1() {
248
249
  const global = globalThis;
249
250
  if (!global[WALLET_STATE_CONTEXT_KEY]) global[WALLET_STATE_CONTEXT_KEY] = createContext(void 0);
250
251
  return global[WALLET_STATE_CONTEXT_KEY];
251
252
  }
252
- const WalletStateContext = getOrCreateSharedContext();
253
+ const WalletStateContext = getOrCreateSharedContext$1();
253
254
  /**
254
255
  * Hook to access wallet state from WalletStateProvider.
255
256
  * @throws Error if used outside of WalletStateProvider
@@ -877,6 +878,536 @@ function useWalletReconnectionHandler(selectedNetworkConfigId, selectedCapabilit
877
878
  ]);
878
879
  }
879
880
 
881
+ //#endregion
882
+ //#region src/hooks/nameResolution/resolutionConfig.ts
883
+ /**
884
+ * Default resolution config. Values sit within the spec's "seconds-to-minutes"
885
+ * caching guidance; forward debounce is 300ms (typed char-by-char), reverse is 0.
886
+ */
887
+ const DEFAULT_CONFIG = {
888
+ staleTimeMs: 6e4,
889
+ gcTimeMs: 3e5,
890
+ forwardDebounceMs: 300,
891
+ reverseDebounceMs: 0,
892
+ transientRetryCount: 2
893
+ };
894
+ /** Stable prefix for every resolution cache key — namespaces this package's keys. */
895
+ const RESOLUTION_KEY_PREFIX = "oz-name-resolution";
896
+ /**
897
+ * Build a network-scoped, namespace-separated cache key (INV-40). A name resolves
898
+ * differently per network and provenance can be chain-scoped, so `networkId` is
899
+ * part of the key; `namespace` keeps forward and reverse lookups disjoint.
900
+ *
901
+ * @param namespace - `'name'` (forward) or `'addr'` (reverse).
902
+ * @param networkId - Active network id (`''` when no network is selected).
903
+ * @param normalizedInput - Trimmed + lowercased input (INV-26).
904
+ * @returns A readonly tuple suitable for a TanStack Query `queryKey`.
905
+ */
906
+ function buildResolutionKey(namespace, networkId, normalizedInput) {
907
+ return [
908
+ RESOLUTION_KEY_PREFIX,
909
+ namespace,
910
+ networkId,
911
+ normalizedInput
912
+ ];
913
+ }
914
+ /**
915
+ * Whether a resolution error is transient (worth retrying) versus a definitive,
916
+ * stable answer (INV-47). Exhaustive over SF-1's closed error union with no
917
+ * `default`: adding a new error code to `@openzeppelin/ui-types` will fail to
918
+ * compile here until it is classified — deliberately coupling SF-2 to SF-1 INV-7.
919
+ *
920
+ * @param error - A typed {@link NameResolutionError}.
921
+ * @returns `true` for transient failures (timeout / gateway / adapter), `false`
922
+ * for definitive negatives and unsupported-network answers.
923
+ */
924
+ function isTransientError(error) {
925
+ switch (error.code) {
926
+ case "RESOLUTION_TIMEOUT":
927
+ case "EXTERNAL_GATEWAY_ERROR":
928
+ case "ADAPTER_ERROR": return true;
929
+ case "NAME_NOT_FOUND":
930
+ case "ADDRESS_NOT_FOUND":
931
+ case "UNSUPPORTED_NAME":
932
+ case "UNSUPPORTED_NETWORK": return false;
933
+ }
934
+ }
935
+ /**
936
+ * Construct a QueryClient tuned for name resolution: ambient refetch triggers are
937
+ * disabled at the client level (INV-41) so a resolved value never silently
938
+ * changes under the user on window focus or network reconnect.
939
+ */
940
+ function createResolutionQueryClient() {
941
+ return new QueryClient({ defaultOptions: { queries: {
942
+ refetchOnWindowFocus: false,
943
+ refetchOnReconnect: false
944
+ } } });
945
+ }
946
+ /**
947
+ * Process-global key for the default resolution QueryClient. Uses `Symbol.for`
948
+ * so every duplicated module instance (Vite pre-bundling, npm-installed adapters)
949
+ * shares ONE client — the same cross-bundle-singleton pattern as
950
+ * `WalletStateContext`. This is what makes the cache shared with zero wiring
951
+ * (SC-001) and enables cross-instance dedup / warm-cache reuse (INV-33 / INV-37 / INV-48).
952
+ */
953
+ const DEFAULT_CLIENT_KEY = Symbol.for("@openzeppelin/ui-react/NameResolutionQueryClient");
954
+ /**
955
+ * Lazily create (exactly once) and return the process-global resolution
956
+ * QueryClient used when no `NameResolutionProvider` is mounted (INV-48).
957
+ *
958
+ * @returns The shared default resolution QueryClient (stable across calls).
959
+ */
960
+ function getDefaultResolutionQueryClient() {
961
+ const globalScope = globalThis;
962
+ if (!globalScope[DEFAULT_CLIENT_KEY]) globalScope[DEFAULT_CLIENT_KEY] = createResolutionQueryClient();
963
+ return globalScope[DEFAULT_CLIENT_KEY];
964
+ }
965
+
966
+ //#endregion
967
+ //#region src/hooks/nameResolution/NameResolutionContext.ts
968
+ /**
969
+ * Process-global key for the context object. Uses `Symbol.for` so all duplicated
970
+ * module instances share ONE React context — the same cross-bundle-singleton
971
+ * pattern as `WalletStateContext` (bundler pre-bundling / npm-installed adapters).
972
+ */
973
+ const CONTEXT_KEY = Symbol.for("@openzeppelin/ui-react/NameResolutionContext");
974
+ function getOrCreateSharedContext() {
975
+ const globalScope = globalThis;
976
+ if (!globalScope[CONTEXT_KEY]) globalScope[CONTEXT_KEY] = createContext(null);
977
+ return globalScope[CONTEXT_KEY];
978
+ }
979
+ /**
980
+ * React context for resolution. `null` default triggers the module-singleton
981
+ * fallback in {@link useNameResolutionContext} — no Provider is required (INV-48).
982
+ */
983
+ const NameResolutionContext = getOrCreateSharedContext();
984
+ /**
985
+ * Stable fallback value used when no Provider is mounted. Cached per module so the
986
+ * returned reference is referentially stable across renders (INV-38); it wraps the
987
+ * process-global default client, so caches are still shared even across bundles.
988
+ */
989
+ let fallbackValue;
990
+ function getFallbackContextValue() {
991
+ if (!fallbackValue) fallbackValue = {
992
+ queryClient: getDefaultResolutionQueryClient(),
993
+ config: DEFAULT_CONFIG
994
+ };
995
+ return fallbackValue;
996
+ }
997
+ /**
998
+ * Read the resolution context, falling back to the zero-wiring default (global
999
+ * singleton client + {@link DEFAULT_CONFIG}) when no `NameResolutionProvider` is
1000
+ * mounted (INV-48). Never throws for a missing provider.
1001
+ *
1002
+ * @returns The active {@link NameResolutionContextValue}.
1003
+ */
1004
+ function useNameResolutionContext() {
1005
+ return useContext(NameResolutionContext) ?? getFallbackContextValue();
1006
+ }
1007
+
1008
+ //#endregion
1009
+ //#region src/hooks/nameResolution/resolutionState.ts
1010
+ /**
1011
+ * Internal error used to bridge SF-1's `ok: false` results (and defensively-caught
1012
+ * adapter throws) into TanStack Query's thrown-error channel, carrying the typed
1013
+ * {@link NameResolutionError} through so the mapping step can surface it unchanged
1014
+ * (INV-43). Never leaks past the hook boundary.
1015
+ */
1016
+ var ResolutionQueryError = class extends Error {
1017
+ resolutionError;
1018
+ /** @param resolutionError - The typed error to carry through react-query. */
1019
+ constructor(resolutionError) {
1020
+ super(`name resolution failed: ${resolutionError.code}`);
1021
+ this.name = "ResolutionQueryError";
1022
+ this.resolutionError = resolutionError;
1023
+ }
1024
+ };
1025
+ /**
1026
+ * Convert any value from react-query's error channel into a typed, closed-union
1027
+ * {@link NameResolutionError} (INV-43). A {@link ResolutionQueryError} is
1028
+ * unwrapped verbatim; anything else (a react-query internal error) is mapped to
1029
+ * `ADAPTER_ERROR` so no untyped throw reaches a component.
1030
+ *
1031
+ * @param error - The raw `query.error` value (`unknown`).
1032
+ * @returns A typed error drawn only from SF-1's closed union.
1033
+ */
1034
+ function toNameResolutionError(error) {
1035
+ if (error instanceof ResolutionQueryError) return error.resolutionError;
1036
+ return {
1037
+ code: "ADAPTER_ERROR",
1038
+ message: error instanceof Error ? error.message : String(error),
1039
+ cause: error
1040
+ };
1041
+ }
1042
+ /**
1043
+ * Map a settled query's state to exactly one non-gate {@link EngineResult} arm
1044
+ * (INV-42, INV-44). The `input` echoed here is the debounced value the query was
1045
+ * keyed on, so `data`/`error` are always paired with the input that produced them
1046
+ * (INV-24). Success without data degrades to `loading` rather than a resolved arm
1047
+ * missing its payload.
1048
+ *
1049
+ * @param input - The debounced, normalized input keying this query.
1050
+ * @param query - The settled query state.
1051
+ * @param retry - Bound `refetch` for the `error` arm (INV-34).
1052
+ * @returns The `resolved`, `error`, or `loading` arm.
1053
+ */
1054
+ function mapSettledQuery(input, query, retry) {
1055
+ if (query.isSuccess && query.data !== void 0) return {
1056
+ status: "resolved",
1057
+ input,
1058
+ data: query.data
1059
+ };
1060
+ if (query.isError) return {
1061
+ status: "error",
1062
+ input,
1063
+ error: toNameResolutionError(query.error),
1064
+ retry
1065
+ };
1066
+ return {
1067
+ status: "loading",
1068
+ input
1069
+ };
1070
+ }
1071
+
1072
+ //#endregion
1073
+ //#region src/hooks/nameResolution/useResolutionEngine.ts
1074
+ const LOG_SCOPE = "useResolutionEngine";
1075
+ /** No-op retry for synthesized errors that never entered the query path (INV-45). */
1076
+ const NOOP_RETRY = () => {};
1077
+ /**
1078
+ * Shared engine behind `useResolveName` / `useResolveAddress`. Owns input
1079
+ * normalization, debouncing, the `useQuery` wiring (explicit-client form), the
1080
+ * capability-absence gate, and the mapping to a direction-agnostic
1081
+ * {@link EngineResult}. Not exported from the package.
1082
+ */
1083
+ function useResolutionEngine(params) {
1084
+ const { input, namespace, debounceMs, enabled, shouldAttempt, getMethod } = params;
1085
+ const walletState = useContext(WalletStateContext);
1086
+ const activeRuntime = walletState?.activeRuntime ?? null;
1087
+ const activeNetworkId = walletState?.activeNetworkId ?? null;
1088
+ const isRuntimeLoading = walletState?.isRuntimeLoading ?? false;
1089
+ const { queryClient, config } = useNameResolutionContext();
1090
+ const trimmedLive = (input ?? "").trim();
1091
+ const normalizedLive = trimmedLive.toLowerCase();
1092
+ const [debouncedTrimmed, setDebouncedTrimmed] = useState(trimmedLive);
1093
+ const seededRef = useRef(false);
1094
+ useEffect(() => {
1095
+ if (!seededRef.current) {
1096
+ seededRef.current = true;
1097
+ return;
1098
+ }
1099
+ if (debounceMs <= 0) {
1100
+ setDebouncedTrimmed(trimmedLive);
1101
+ return;
1102
+ }
1103
+ const timer = setTimeout(() => setDebouncedTrimmed(trimmedLive), debounceMs);
1104
+ return () => clearTimeout(timer);
1105
+ }, [trimmedLive, debounceMs]);
1106
+ const effectiveTrimmed = debounceMs <= 0 ? trimmedLive : debouncedTrimmed;
1107
+ const normalizedKey = effectiveTrimmed.toLowerCase();
1108
+ const capability = activeRuntime?.nameResolution;
1109
+ const networkId = activeNetworkId ?? "";
1110
+ const method = capability ? getMethod(capability) : void 0;
1111
+ const liveAttemptable = capability ? shouldAttempt(capability, normalizedLive) : false;
1112
+ const keyAttemptable = capability ? shouldAttempt(capability, normalizedKey) : false;
1113
+ const queryEnabled = enabled && trimmedLive !== "" && capability != null && method != null && normalizedKey !== "" && keyAttemptable;
1114
+ const query = useQuery({
1115
+ queryKey: buildResolutionKey(namespace, networkId, normalizedKey),
1116
+ queryFn: async () => {
1117
+ if (!method) throw new ResolutionQueryError({
1118
+ code: "ADAPTER_ERROR",
1119
+ message: "resolution method unexpectedly missing"
1120
+ });
1121
+ const adapterArg = namespace === "addr" ? effectiveTrimmed : normalizedKey;
1122
+ let result;
1123
+ try {
1124
+ result = await method(adapterArg);
1125
+ } catch (cause) {
1126
+ logger.warn(LOG_SCOPE, "resolution method threw instead of returning ok:false", cause);
1127
+ throw new ResolutionQueryError({
1128
+ code: "ADAPTER_ERROR",
1129
+ message: cause instanceof Error ? cause.message : String(cause),
1130
+ cause
1131
+ });
1132
+ }
1133
+ if (!result.ok) throw new ResolutionQueryError(result.error);
1134
+ return result.value;
1135
+ },
1136
+ enabled: queryEnabled,
1137
+ staleTime: config.staleTimeMs,
1138
+ gcTime: config.gcTimeMs,
1139
+ refetchOnWindowFocus: false,
1140
+ refetchOnReconnect: false,
1141
+ retry: (failureCount, error) => (error instanceof ResolutionQueryError ? isTransientError(error.resolutionError) : true) && failureCount < config.transientRetryCount
1142
+ }, queryClient);
1143
+ if (!enabled) return { status: "idle" };
1144
+ if (trimmedLive === "") return { status: "idle" };
1145
+ if (capability) {
1146
+ if (!liveAttemptable) return { status: "idle" };
1147
+ if (!method) return unsupportedNetwork(normalizedLive, networkId);
1148
+ if (normalizedKey !== normalizedLive) return {
1149
+ status: "debouncing",
1150
+ input: normalizedLive
1151
+ };
1152
+ } else {
1153
+ if (isRuntimeLoading) return {
1154
+ status: "loading",
1155
+ input: normalizedLive
1156
+ };
1157
+ return unsupportedNetwork(normalizedLive, networkId);
1158
+ }
1159
+ const retry = () => {
1160
+ query.refetch();
1161
+ };
1162
+ return mapSettledQuery(normalizedKey, query, retry);
1163
+ }
1164
+ /**
1165
+ * Synthesize an `UNSUPPORTED_NETWORK` error arm without touching react-query
1166
+ * (INV-45). Drawn from SF-1's closed union so it is indistinguishable from an
1167
+ * adapter-returned `UNSUPPORTED_NETWORK`; `networkId` may be `''` (INV-49). Retry
1168
+ * is a no-op — there is no query to refetch.
1169
+ */
1170
+ function unsupportedNetwork(input, networkId) {
1171
+ return {
1172
+ status: "error",
1173
+ input,
1174
+ error: {
1175
+ code: "UNSUPPORTED_NETWORK",
1176
+ networkId
1177
+ },
1178
+ retry: NOOP_RETRY
1179
+ };
1180
+ }
1181
+
1182
+ //#endregion
1183
+ //#region src/hooks/nameResolution/useResolveName.ts
1184
+ /**
1185
+ * Forward-resolve a name to an address. Soft-reads the active runtime from
1186
+ * `WalletStateContext` (never throws when no provider is mounted — degrades to
1187
+ * idle / UNSUPPORTED_NETWORK) and calls `runtime.nameResolution?.resolveName`.
1188
+ * Debounces `name`, caches per (network, name) via the owned QueryClient, and is
1189
+ * protected against out-of-order responses (distinct inputs → distinct query keys).
1190
+ *
1191
+ * Returns a discriminated union keyed on `status`; it never throws for expected
1192
+ * failures (they surface in the `error` arm) and never pairs a name with a
1193
+ * different name's resolved address (INV-24).
1194
+ *
1195
+ * @param name - The name to resolve. `null` / empty / a value failing the
1196
+ * adapter's `isValidName` yields `status: 'idle'` (never `error`).
1197
+ * @param options - `debounceMs` / `enabled` overrides.
1198
+ * @returns The current {@link UseResolveNameResult}.
1199
+ */
1200
+ function useResolveName(name, options) {
1201
+ const { config } = useNameResolutionContext();
1202
+ return toNameResult(useResolutionEngine({
1203
+ input: name,
1204
+ namespace: "name",
1205
+ debounceMs: options?.debounceMs ?? config.forwardDebounceMs,
1206
+ enabled: options?.enabled ?? true,
1207
+ shouldAttempt: (cap, normalized) => cap.isValidName(normalized),
1208
+ getMethod: (cap) => cap.resolveName
1209
+ }));
1210
+ }
1211
+ /**
1212
+ * Remap the engine's generic `input` to `name` (INV-24: keyed off the debounced
1213
+ * input carried in the engine result, not the live prop). Exhaustive over the
1214
+ * union so a new status cannot silently fall through (INV-23 / INV-42).
1215
+ */
1216
+ function toNameResult(engine) {
1217
+ switch (engine.status) {
1218
+ case "idle": return { status: "idle" };
1219
+ case "debouncing": return {
1220
+ status: "debouncing",
1221
+ name: engine.input
1222
+ };
1223
+ case "loading": return {
1224
+ status: "loading",
1225
+ name: engine.input
1226
+ };
1227
+ case "resolved": return {
1228
+ status: "resolved",
1229
+ name: engine.input,
1230
+ data: engine.data
1231
+ };
1232
+ case "error": return {
1233
+ status: "error",
1234
+ name: engine.input,
1235
+ error: engine.error,
1236
+ retry: engine.retry
1237
+ };
1238
+ }
1239
+ }
1240
+
1241
+ //#endregion
1242
+ //#region src/hooks/nameResolution/useResolveAddress.ts
1243
+ /**
1244
+ * Reverse-resolve an address to a name. Soft-reads the active runtime from
1245
+ * `WalletStateContext` (never throws when no provider is mounted — degrades to
1246
+ * idle / UNSUPPORTED_NETWORK) and calls `runtime.nameResolution?.resolveAddress`.
1247
+ * Caches per (network, address) via the owned QueryClient. Not debounced by
1248
+ * default — addresses are pasted, not typed char-by-char.
1249
+ *
1250
+ * Applies no client-side address-shape check (INV-32): resolution is attempted on
1251
+ * any non-empty input and malformed addresses are rejected via the adapter's typed
1252
+ * error union. Address-shape validation is SF-3's concern.
1253
+ *
1254
+ * @param address - The address to reverse-resolve. `null` / empty yields
1255
+ * `status: 'idle'`.
1256
+ * @param options - `debounceMs` / `enabled` overrides.
1257
+ * @returns The current {@link UseResolveAddressResult}.
1258
+ */
1259
+ function useResolveAddress(address, options) {
1260
+ const { config } = useNameResolutionContext();
1261
+ return toAddressResult(useResolutionEngine({
1262
+ input: address,
1263
+ namespace: "addr",
1264
+ debounceMs: options?.debounceMs ?? config.reverseDebounceMs,
1265
+ enabled: options?.enabled ?? true,
1266
+ shouldAttempt: () => true,
1267
+ getMethod: (cap) => cap.resolveAddress
1268
+ }));
1269
+ }
1270
+ /**
1271
+ * Remap the engine's generic `input` to `address` (INV-24). A `debouncing` arm —
1272
+ * only reachable when a caller passes a non-zero reverse `debounceMs` — collapses
1273
+ * to `loading` so {@link UseResolveAddressResult} keeps no `debouncing` variant.
1274
+ */
1275
+ function toAddressResult(engine) {
1276
+ switch (engine.status) {
1277
+ case "idle": return { status: "idle" };
1278
+ case "debouncing":
1279
+ case "loading": return {
1280
+ status: "loading",
1281
+ address: engine.input
1282
+ };
1283
+ case "resolved": return {
1284
+ status: "resolved",
1285
+ address: engine.input,
1286
+ data: engine.data
1287
+ };
1288
+ case "error": return {
1289
+ status: "error",
1290
+ address: engine.input,
1291
+ error: engine.error,
1292
+ retry: engine.retry
1293
+ };
1294
+ }
1295
+ }
1296
+
1297
+ //#endregion
1298
+ //#region src/hooks/nameResolution/NameResolutionProvider.tsx
1299
+ /**
1300
+ * Optional provider that overrides resolution config and/or the QueryClient for
1301
+ * its subtree. It does NOT render a `QueryClientProvider`: the client is passed
1302
+ * explicitly to `useQuery` (INV-48), so no ambient react-query provider is needed.
1303
+ *
1304
+ * Config is merged field-by-field over {@link DEFAULT_CONFIG} (INV-30), and the
1305
+ * context value is memoized so a Provider re-render does not cascade to every hook
1306
+ * (INV-38).
1307
+ */
1308
+ function NameResolutionProvider({ children, config, queryClient }) {
1309
+ const value = useMemo(() => ({
1310
+ queryClient: queryClient ?? getDefaultResolutionQueryClient(),
1311
+ config: {
1312
+ ...DEFAULT_CONFIG,
1313
+ ...config
1314
+ }
1315
+ }), [queryClient, config]);
1316
+ return /* @__PURE__ */ jsx(NameResolutionContext.Provider, {
1317
+ value,
1318
+ children
1319
+ });
1320
+ }
1321
+
1322
+ //#endregion
1323
+ //#region src/hooks/nameResolution/useRuntimeNameResolver.ts
1324
+ /**
1325
+ * Stable empty resolver returned when no runtime / capability is available.
1326
+ * No methods ⇒ the consuming field treats a typed name as unsupported and
1327
+ * surfaces `UNSUPPORTED_NETWORK` (INV-119) while hex behavior stays legacy.
1328
+ */
1329
+ const EMPTY_RESOLVER = {};
1330
+ /**
1331
+ * Project the active runtime's `NameResolutionCapability` into the injected
1332
+ * {@link NameResolver} seam consumed by `@openzeppelin/ui-components` (SF-3, D3).
1333
+ * Mounted ambiently by the renderer:
1334
+ *
1335
+ * ```tsx
1336
+ * <NameResolverProvider {...useRuntimeNameResolver()}>{form}</NameResolverProvider>
1337
+ * ```
1338
+ *
1339
+ * Each imperative call is backed by SF-2's **owned** resolution `QueryClient`
1340
+ * via `fetchQuery`, reusing SF-2's exact query-key convention
1341
+ * (`buildResolutionKey('name', networkId, normalizedName)`) — so a name
1342
+ * resolved through the field and the same name resolved by a bare
1343
+ * `useResolveName` hit the SAME cache entry: shared cache, dedupe, and
1344
+ * out-of-order safety, never a parallel cache (INV-119).
1345
+ *
1346
+ * Degradation, in order (all method-omission, never a throw):
1347
+ * - No `WalletStateProvider` mounted → empty resolver. The context is read
1348
+ * directly (not via `useWalletState()`, which throws) so the ambient
1349
+ * renderer mount stays safe for wallet-less usage.
1350
+ * - No active runtime, or runtime without the capability → empty resolver.
1351
+ * - Capability without `resolveName` → `isValidName` only; forward unsupported.
1352
+ *
1353
+ * When the capability is present but the network itself is unsupported, the
1354
+ * adapter's `resolveName` resolves `{ ok: false, error: UNSUPPORTED_NETWORK }`
1355
+ * and that result passes through unchanged.
1356
+ *
1357
+ * @returns A referentially stable {@link NameResolver} for the active runtime.
1358
+ */
1359
+ function useRuntimeNameResolver() {
1360
+ const walletState = useContext(WalletStateContext);
1361
+ const { queryClient, config } = useNameResolutionContext();
1362
+ const capability = walletState?.activeRuntime?.nameResolution;
1363
+ const networkId = walletState?.activeNetworkId ?? "";
1364
+ return useMemo(() => {
1365
+ if (!capability) return EMPTY_RESOLVER;
1366
+ const resolveNameMethod = capability.resolveName;
1367
+ return {
1368
+ isValidName: (name) => capability.isValidName(name),
1369
+ resolveName: resolveNameMethod ? async (name) => {
1370
+ const normalized = name.trim().toLowerCase();
1371
+ try {
1372
+ return {
1373
+ ok: true,
1374
+ value: await queryClient.fetchQuery({
1375
+ queryKey: buildResolutionKey("name", networkId, normalized),
1376
+ queryFn: async () => {
1377
+ let result;
1378
+ try {
1379
+ result = await resolveNameMethod(normalized);
1380
+ } catch (cause) {
1381
+ throw new ResolutionQueryError({
1382
+ code: "ADAPTER_ERROR",
1383
+ message: cause instanceof Error ? cause.message : String(cause),
1384
+ cause
1385
+ });
1386
+ }
1387
+ if (!result.ok) throw new ResolutionQueryError(result.error);
1388
+ return result.value;
1389
+ },
1390
+ staleTime: config.staleTimeMs,
1391
+ gcTime: config.gcTimeMs,
1392
+ retry: (failureCount, error) => (error instanceof ResolutionQueryError ? isTransientError(error.resolutionError) : true) && failureCount < config.transientRetryCount
1393
+ })
1394
+ };
1395
+ } catch (error) {
1396
+ return {
1397
+ ok: false,
1398
+ error: toNameResolutionError(error)
1399
+ };
1400
+ }
1401
+ } : void 0
1402
+ };
1403
+ }, [
1404
+ capability,
1405
+ queryClient,
1406
+ config,
1407
+ networkId
1408
+ ]);
1409
+ }
1410
+
880
1411
  //#endregion
881
1412
  //#region src/components/WalletConnectionUI.tsx
882
1413
  /**
@@ -1084,5 +1615,5 @@ const NetworkSwitchManager = ({ wallet, networkCatalog, targetNetworkId, onNetwo
1084
1615
  };
1085
1616
 
1086
1617
  //#endregion
1087
- export { AnalyticsContext, AnalyticsProvider, NetworkSwitchManager, RuntimeContext, RuntimeProvider, VERSION, WalletConnectionHeader, WalletConnectionUI, WalletStateContext, WalletStateProvider, useAnalytics, useDerivedAccountStatus, useDerivedChainInfo, useDerivedConnectStatus, useDerivedDisconnect, useDerivedSwitchChainStatus, useRuntimeContext, useWalletComponents, useWalletReconnectionHandler, useWalletState };
1618
+ export { AnalyticsContext, AnalyticsProvider, DEFAULT_CONFIG, NameResolutionContext, NameResolutionProvider, NetworkSwitchManager, RuntimeContext, RuntimeProvider, VERSION, WalletConnectionHeader, WalletConnectionUI, WalletStateContext, WalletStateProvider, useAnalytics, useDerivedAccountStatus, useDerivedChainInfo, useDerivedConnectStatus, useDerivedDisconnect, useDerivedSwitchChainStatus, useNameResolutionContext, useResolveAddress, useResolveName, useRuntimeContext, useRuntimeNameResolver, useWalletComponents, useWalletReconnectionHandler, useWalletState };
1088
1619
  //# sourceMappingURL=index.mjs.map