@openzeppelin/ui-utils 1.0.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.cjs ADDED
@@ -0,0 +1,2167 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) {
13
+ __defProp(to, key, {
14
+ get: ((k) => from[k]).bind(null, key),
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ });
17
+ }
18
+ }
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
26
+
27
+ //#endregion
28
+ let clsx = require("clsx");
29
+ let tailwind_merge = require("tailwind-merge");
30
+ let uuid = require("uuid");
31
+ let validator = require("validator");
32
+ validator = __toESM(validator);
33
+
34
+ //#region src/contractInputs.ts
35
+ /**
36
+ * Returns names of adapter-declared required inputs that are missing/empty in values.
37
+ */
38
+ function getMissingRequiredContractInputs(adapter, values) {
39
+ try {
40
+ const required = (adapter.getContractDefinitionInputs ? adapter.getContractDefinitionInputs() : []).filter((field) => {
41
+ return field?.validation?.required === true;
42
+ });
43
+ const missing = [];
44
+ for (const field of required) {
45
+ const key = field.name || field.id || "";
46
+ const raw = values[key];
47
+ if (raw == null) {
48
+ missing.push(key);
49
+ continue;
50
+ }
51
+ if (typeof raw === "string" && raw.trim().length === 0) missing.push(key);
52
+ }
53
+ return missing;
54
+ } catch {
55
+ return [];
56
+ }
57
+ }
58
+ /**
59
+ * True if any adapter-declared required inputs are missing/empty.
60
+ */
61
+ function hasMissingRequiredContractInputs(adapter, values) {
62
+ if (!adapter) return false;
63
+ return getMissingRequiredContractInputs(adapter, values).length > 0;
64
+ }
65
+
66
+ //#endregion
67
+ //#region src/requiredInputs.ts
68
+ function normalizeSnapshotValue(value) {
69
+ if (value instanceof File) return {
70
+ name: value.name,
71
+ size: value.size,
72
+ lastModified: value.lastModified
73
+ };
74
+ if (typeof value === "string") return value.trim();
75
+ if (value === void 0) return null;
76
+ return value;
77
+ }
78
+ function extractRequiredFields(adapter) {
79
+ if (!adapter || typeof adapter.getContractDefinitionInputs !== "function") return [];
80
+ try {
81
+ return (adapter.getContractDefinitionInputs() || []).filter((field) => field.validation?.required);
82
+ } catch {
83
+ return [];
84
+ }
85
+ }
86
+ /**
87
+ * Builds a snapshot of required form input values.
88
+ * @param adapter - Contract adapter to get field definitions from
89
+ * @param formValues - Current form values
90
+ * @returns Snapshot of required field values, or null if no required fields
91
+ */
92
+ function buildRequiredInputSnapshot(adapter, formValues) {
93
+ if (!formValues) return null;
94
+ const requiredFields = extractRequiredFields(adapter);
95
+ if (requiredFields.length === 0) return null;
96
+ const snapshot = {};
97
+ const values = formValues;
98
+ for (const field of requiredFields) {
99
+ const key = field.name || field.id;
100
+ if (!key) continue;
101
+ snapshot[key] = normalizeSnapshotValue(values[key]);
102
+ }
103
+ return Object.keys(snapshot).length > 0 ? snapshot : null;
104
+ }
105
+ /**
106
+ * Compares two required input snapshots for equality.
107
+ * @param a - First snapshot to compare
108
+ * @param b - Second snapshot to compare
109
+ * @returns True if snapshots are equal, false otherwise
110
+ */
111
+ function requiredSnapshotsEqual(a, b) {
112
+ if (a === b) return true;
113
+ if (!a || !b) return false;
114
+ const keysA = Object.keys(a).sort();
115
+ const keysB = Object.keys(b).sort();
116
+ if (keysA.length !== keysB.length) return false;
117
+ for (let i = 0; i < keysA.length; i += 1) {
118
+ if (keysA[i] !== keysB[i]) return false;
119
+ const valueA = a[keysA[i]];
120
+ const valueB = b[keysA[i]];
121
+ if (typeof valueA === "object" && valueA !== null && typeof valueB === "object" && valueB !== null) {
122
+ if (JSON.stringify(valueA) !== JSON.stringify(valueB)) return false;
123
+ } else if (valueA !== valueB) return false;
124
+ }
125
+ return true;
126
+ }
127
+
128
+ //#endregion
129
+ //#region src/addressNormalization.ts
130
+ /**
131
+ * Normalizes a contract address by trimming whitespace and converting to lowercase.
132
+ * This is useful for case-insensitive and whitespace-insensitive address comparison.
133
+ *
134
+ * @param address - The address to normalize (string, null, or undefined)
135
+ * @returns The normalized address string, or empty string if input is falsy
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * normalizeAddress(' 0xABC123 ') // Returns '0xabc123'
140
+ * normalizeAddress('0xDEF456') // Returns '0xdef456'
141
+ * normalizeAddress(null) // Returns ''
142
+ * normalizeAddress(undefined) // Returns ''
143
+ * ```
144
+ */
145
+ function normalizeAddress(address) {
146
+ if (typeof address === "string") return address.trim().toLowerCase();
147
+ return "";
148
+ }
149
+ /**
150
+ * Compares two addresses after normalization.
151
+ * Returns true if both addresses normalize to the same value.
152
+ *
153
+ * @param address1 - First address to compare
154
+ * @param address2 - Second address to compare
155
+ * @returns True if addresses are equal after normalization
156
+ *
157
+ * @example
158
+ * ```ts
159
+ * addressesEqual(' 0xABC ', '0xabc') // Returns true
160
+ * addressesEqual('0xDEF', '0xABC') // Returns false
161
+ * addressesEqual(null, '') // Returns true
162
+ * ```
163
+ */
164
+ function addressesEqual(address1, address2) {
165
+ return normalizeAddress(address1) === normalizeAddress(address2);
166
+ }
167
+
168
+ //#endregion
169
+ //#region src/logger.ts
170
+ var Logger = class Logger {
171
+ static instance;
172
+ options = {
173
+ enabled: getDefaultLoggerEnabled(),
174
+ level: "debug"
175
+ };
176
+ constructor() {}
177
+ static getInstance() {
178
+ if (!Logger.instance) Logger.instance = new Logger();
179
+ return Logger.instance;
180
+ }
181
+ configure(options) {
182
+ this.options = {
183
+ ...this.options,
184
+ ...options
185
+ };
186
+ }
187
+ shouldLog(level) {
188
+ if (!this.options.enabled) return false;
189
+ const levels = [
190
+ "debug",
191
+ "info",
192
+ "warn",
193
+ "error"
194
+ ];
195
+ const configuredLevelIndex = levels.indexOf(this.options.level);
196
+ return levels.indexOf(level) >= configuredLevelIndex;
197
+ }
198
+ formatMessage(level, system, message) {
199
+ return `[${level.toUpperCase()}][${system}] ${message}`;
200
+ }
201
+ debug(system, message, ...args) {
202
+ if (this.shouldLog("debug")) console.log(this.formatMessage("debug", system, message), ...args);
203
+ }
204
+ info(system, message, ...args) {
205
+ if (this.shouldLog("info")) console.log(this.formatMessage("info", system, message), ...args);
206
+ }
207
+ warn(system, message, ...args) {
208
+ if (this.shouldLog("warn")) console.warn(this.formatMessage("warn", system, message), ...args);
209
+ }
210
+ error(system, message, ...args) {
211
+ if (this.shouldLog("error")) console.error(this.formatMessage("error", system, message), ...args);
212
+ }
213
+ };
214
+ const logger = Logger.getInstance();
215
+ /**
216
+ * Determine whether logging should be enabled by default.
217
+ *
218
+ * - In Vite/browser contexts, use `import.meta.env.DEV`.
219
+ * - In Node/tsup contexts, use `process.env.NODE_ENV`.
220
+ *
221
+ * Defaults to disabled outside development to avoid runtime overhead and noise.
222
+ */
223
+ function getDefaultLoggerEnabled() {
224
+ try {
225
+ const viteEnv = {}.env;
226
+ if (viteEnv) {
227
+ const exportEnv = String(viteEnv.VITE_EXPORT_ENV || "").toLowerCase();
228
+ if (exportEnv === "staging" || exportEnv === "production") return false;
229
+ if (typeof viteEnv.DEV === "boolean") return viteEnv.DEV;
230
+ }
231
+ } catch {}
232
+ if (typeof process !== "undefined" && typeof process.env !== "undefined") {
233
+ const exportEnv = String(process.env.VITE_EXPORT_ENV || "").toLowerCase();
234
+ if (exportEnv === "staging" || exportEnv === "production") return false;
235
+ const nodeEnv = process.env.NODE_ENV;
236
+ return nodeEnv === "development" || nodeEnv === "test";
237
+ }
238
+ return false;
239
+ }
240
+
241
+ //#endregion
242
+ //#region src/AppConfigService.ts
243
+ const VITE_ENV_PREFIX = "VITE_APP_CFG_";
244
+ const LOG_SYSTEM = "AppConfigService";
245
+ /**
246
+ * AppConfigService
247
+ *
248
+ * Responsible for loading, merging, and providing access to the application's
249
+ * runtime configuration (`AppRuntimeConfig`).
250
+ */
251
+ var AppConfigService = class {
252
+ config;
253
+ isInitialized = false;
254
+ /**
255
+ * Creates a new AppConfigService with default configuration.
256
+ */
257
+ constructor() {
258
+ this.config = {
259
+ networkServiceConfigs: {},
260
+ globalServiceConfigs: {},
261
+ rpcEndpoints: {},
262
+ indexerEndpoints: {},
263
+ featureFlags: {},
264
+ defaultLanguage: "en"
265
+ };
266
+ logger.info(LOG_SYSTEM, "Service initialized with default configuration.");
267
+ }
268
+ loadFromViteEnvironment(envSource) {
269
+ logger.debug(LOG_SYSTEM, "BEGIN loadFromViteEnvironment. envSource received:", envSource ? JSON.stringify(envSource) : "undefined");
270
+ if (typeof envSource === "undefined") {
271
+ logger.warn(LOG_SYSTEM, "Vite environment object (envSource) was undefined. Skipping Vite env load.");
272
+ return;
273
+ }
274
+ const env = envSource;
275
+ const loadedNetworkServiceConfigs = {};
276
+ const loadedGlobalServiceConfigs = {};
277
+ const loadedRpcEndpoints = {};
278
+ const loadedIndexerEndpoints = {};
279
+ const loadedFeatureFlags = {};
280
+ for (const key in env) if (Object.prototype.hasOwnProperty.call(env, key) && env[key] !== void 0) {
281
+ const value = String(env[key]);
282
+ if (key.startsWith(`${VITE_ENV_PREFIX}API_KEY_`)) {
283
+ const serviceIdentifier = key.substring(`${VITE_ENV_PREFIX}API_KEY_`.length).toLowerCase().replace(/_/g, "-");
284
+ if (!loadedNetworkServiceConfigs[serviceIdentifier]) loadedNetworkServiceConfigs[serviceIdentifier] = {};
285
+ loadedNetworkServiceConfigs[serviceIdentifier].apiKey = value;
286
+ } else if (key.startsWith(`${VITE_ENV_PREFIX}SERVICE_`)) {
287
+ const fullSuffix = key.substring(`${VITE_ENV_PREFIX}SERVICE_`.length);
288
+ const firstUnderscoreIndex = fullSuffix.indexOf("_");
289
+ if (firstUnderscoreIndex > 0 && firstUnderscoreIndex < fullSuffix.length - 1) {
290
+ const serviceName = fullSuffix.substring(0, firstUnderscoreIndex).toLowerCase();
291
+ const paramName = fullSuffix.substring(firstUnderscoreIndex + 1).toLowerCase().replace(/_([a-z])/g, (g) => g[1].toUpperCase());
292
+ if (serviceName && paramName) {
293
+ if (!loadedGlobalServiceConfigs[serviceName]) loadedGlobalServiceConfigs[serviceName] = {};
294
+ loadedGlobalServiceConfigs[serviceName][paramName] = value;
295
+ logger.debug(LOG_SYSTEM, `Parsed service: '${serviceName}', param: '${paramName}', value: '${value}' from key: ${key}`);
296
+ } else logger.warn(LOG_SYSTEM, `Could not effectively parse service/param from key: ${key}`);
297
+ } else logger.warn(LOG_SYSTEM, `Could not determine service and param from key (missing underscore separator): ${key}`);
298
+ } else if (key === `${VITE_ENV_PREFIX}WALLETCONNECT_PROJECT_ID`) {
299
+ if (!loadedGlobalServiceConfigs.walletconnect) loadedGlobalServiceConfigs.walletconnect = {};
300
+ loadedGlobalServiceConfigs.walletconnect.projectId = value;
301
+ logger.debug(LOG_SYSTEM, `Parsed WalletConnect Project ID directly from key: ${key}, value: ${value}`);
302
+ } else if (key.startsWith(`${VITE_ENV_PREFIX}RPC_ENDPOINT_`)) {
303
+ const networkId = key.substring(`${VITE_ENV_PREFIX}RPC_ENDPOINT_`.length).toLowerCase().replace(/_/g, "-");
304
+ if (networkId) {
305
+ loadedRpcEndpoints[networkId] = value;
306
+ logger.debug(LOG_SYSTEM, `Loaded RPC override for ${networkId}: ${value}`);
307
+ }
308
+ } else if (key.startsWith(`${VITE_ENV_PREFIX}INDEXER_ENDPOINT_`)) {
309
+ const networkId = key.substring(`${VITE_ENV_PREFIX}INDEXER_ENDPOINT_`.length).toLowerCase().replace(/_/g, "-");
310
+ if (networkId) {
311
+ loadedIndexerEndpoints[networkId] = value;
312
+ logger.debug(LOG_SYSTEM, `Loaded indexer endpoint for ${networkId}: ${value}`);
313
+ }
314
+ } else if (key.startsWith(`${VITE_ENV_PREFIX}FEATURE_FLAG_`)) {
315
+ const flagName = key.substring(`${VITE_ENV_PREFIX}FEATURE_FLAG_`.length).toLowerCase();
316
+ loadedFeatureFlags[flagName] = value.toLowerCase() === "true";
317
+ } else if (key === `${VITE_ENV_PREFIX}DEFAULT_LANGUAGE`) this.config.defaultLanguage = value;
318
+ }
319
+ this.config.networkServiceConfigs = {
320
+ ...this.config.networkServiceConfigs,
321
+ ...loadedNetworkServiceConfigs
322
+ };
323
+ if (Object.keys(loadedGlobalServiceConfigs).length > 0) {
324
+ if (!this.config.globalServiceConfigs) this.config.globalServiceConfigs = {};
325
+ for (const serviceKeyInLoaded in loadedGlobalServiceConfigs) if (Object.prototype.hasOwnProperty.call(loadedGlobalServiceConfigs, serviceKeyInLoaded)) this.config.globalServiceConfigs[serviceKeyInLoaded] = {
326
+ ...this.config.globalServiceConfigs[serviceKeyInLoaded] || {},
327
+ ...loadedGlobalServiceConfigs[serviceKeyInLoaded]
328
+ };
329
+ }
330
+ if (Object.keys(loadedRpcEndpoints).length > 0) {
331
+ if (!this.config.rpcEndpoints) this.config.rpcEndpoints = {};
332
+ for (const networkKey in loadedRpcEndpoints) if (Object.prototype.hasOwnProperty.call(loadedRpcEndpoints, networkKey)) this.config.rpcEndpoints[networkKey] = loadedRpcEndpoints[networkKey];
333
+ }
334
+ if (Object.keys(loadedIndexerEndpoints).length > 0) {
335
+ if (!this.config.indexerEndpoints) this.config.indexerEndpoints = {};
336
+ for (const networkKey in loadedIndexerEndpoints) if (Object.prototype.hasOwnProperty.call(loadedIndexerEndpoints, networkKey)) this.config.indexerEndpoints[networkKey] = loadedIndexerEndpoints[networkKey];
337
+ }
338
+ this.config.featureFlags = {
339
+ ...this.config.featureFlags,
340
+ ...loadedFeatureFlags
341
+ };
342
+ logger.info(LOG_SYSTEM, "Resolved globalServiceConfigs after Vite env processing:", this.config.globalServiceConfigs ? JSON.stringify(this.config.globalServiceConfigs) : "undefined");
343
+ logger.info(LOG_SYSTEM, "Resolved rpcEndpoints after Vite env processing:", this.config.rpcEndpoints ? JSON.stringify(this.config.rpcEndpoints) : "undefined");
344
+ logger.info(LOG_SYSTEM, "Configuration loaded/merged from provided Vite environment variables.");
345
+ }
346
+ async loadFromJson(filePath = "/app.config.json") {
347
+ try {
348
+ const response = await fetch(filePath);
349
+ if (!response.ok) {
350
+ if (response.status === 404) logger.info(LOG_SYSTEM, `Optional configuration file not found at ${filePath}. Skipping.`);
351
+ else logger.warn(LOG_SYSTEM, `Failed to fetch config from ${filePath}: ${response.status} ${response.statusText}`);
352
+ return;
353
+ }
354
+ const externalConfig = await response.json();
355
+ if (externalConfig.networkServiceConfigs) {
356
+ if (!this.config.networkServiceConfigs) this.config.networkServiceConfigs = {};
357
+ for (const key in externalConfig.networkServiceConfigs) if (Object.prototype.hasOwnProperty.call(externalConfig.networkServiceConfigs, key)) this.config.networkServiceConfigs[key] = {
358
+ ...this.config.networkServiceConfigs[key] || {},
359
+ ...externalConfig.networkServiceConfigs[key]
360
+ };
361
+ }
362
+ if (externalConfig.globalServiceConfigs) {
363
+ if (!this.config.globalServiceConfigs) this.config.globalServiceConfigs = {};
364
+ for (const serviceKey in externalConfig.globalServiceConfigs) if (Object.prototype.hasOwnProperty.call(externalConfig.globalServiceConfigs, serviceKey)) this.config.globalServiceConfigs[serviceKey] = {
365
+ ...this.config.globalServiceConfigs[serviceKey] || {},
366
+ ...externalConfig.globalServiceConfigs[serviceKey]
367
+ };
368
+ }
369
+ if (externalConfig.rpcEndpoints) {
370
+ if (!this.config.rpcEndpoints) this.config.rpcEndpoints = {};
371
+ for (const networkKey in externalConfig.rpcEndpoints) if (Object.prototype.hasOwnProperty.call(externalConfig.rpcEndpoints, networkKey)) this.config.rpcEndpoints[networkKey] = externalConfig.rpcEndpoints[networkKey];
372
+ }
373
+ if (externalConfig.indexerEndpoints) {
374
+ if (!this.config.indexerEndpoints) this.config.indexerEndpoints = {};
375
+ for (const networkKey in externalConfig.indexerEndpoints) if (Object.prototype.hasOwnProperty.call(externalConfig.indexerEndpoints, networkKey)) this.config.indexerEndpoints[networkKey] = externalConfig.indexerEndpoints[networkKey];
376
+ }
377
+ if (externalConfig.featureFlags) this.config.featureFlags = {
378
+ ...this.config.featureFlags || {},
379
+ ...externalConfig.featureFlags
380
+ };
381
+ if (typeof externalConfig.defaultLanguage === "string") this.config.defaultLanguage = externalConfig.defaultLanguage;
382
+ logger.info(LOG_SYSTEM, `Configuration loaded/merged from ${filePath}`);
383
+ } catch (error) {
384
+ logger.error(LOG_SYSTEM, `Error loading or parsing config from ${filePath}:`, error);
385
+ }
386
+ }
387
+ /**
388
+ * Initializes the service by loading configuration from the specified strategies.
389
+ * @param strategies - Array of configuration loading strategies to apply
390
+ */
391
+ async initialize(strategies) {
392
+ logger.info(LOG_SYSTEM, "Initialization sequence started with strategies:", strategies);
393
+ for (const strategy of strategies) if (strategy.type === "viteEnv") this.loadFromViteEnvironment(strategy.env);
394
+ else if (strategy.type === "json") await this.loadFromJson(strategy.path);
395
+ this.isInitialized = true;
396
+ logger.info(LOG_SYSTEM, "Initialization complete.");
397
+ }
398
+ /**
399
+ * Gets the API key for a specific explorer service.
400
+ * @param serviceIdentifier - The service identifier
401
+ * @returns The API key if configured, undefined otherwise
402
+ */
403
+ getExplorerApiKey(serviceIdentifier) {
404
+ if (!this.isInitialized) logger.warn(LOG_SYSTEM, "getExplorerApiKey called before initialization.");
405
+ return this.config.networkServiceConfigs?.[serviceIdentifier]?.apiKey;
406
+ }
407
+ /**
408
+ * Gets the configuration for a global service.
409
+ * @param serviceName - The name of the service
410
+ * @returns The service configuration if found, undefined otherwise
411
+ */
412
+ getGlobalServiceConfig(serviceName) {
413
+ if (!this.isInitialized) logger.warn(LOG_SYSTEM, "getGlobalServiceConfig called before initialization.");
414
+ return this.config.globalServiceConfigs?.[serviceName];
415
+ }
416
+ /**
417
+ * Checks if a feature flag is enabled.
418
+ * @param flagName - The name of the feature flag
419
+ * @returns True if the feature is enabled, false otherwise
420
+ */
421
+ isFeatureEnabled(flagName) {
422
+ if (!this.isInitialized) logger.warn(LOG_SYSTEM, "isFeatureEnabled called before initialization.");
423
+ return this.config.featureFlags?.[flagName.toLowerCase()] ?? false;
424
+ }
425
+ /**
426
+ * Gets a global service parameter value.
427
+ * @param serviceName The name of the service
428
+ * @param paramName The name of the parameter
429
+ * @returns The parameter value (can be any type including objects, arrays) or undefined if not found
430
+ */
431
+ getGlobalServiceParam(serviceName, paramName) {
432
+ if (!this.isInitialized) {
433
+ logger.warn(LOG_SYSTEM, "getGlobalServiceParam called before initialization.");
434
+ return;
435
+ }
436
+ return (this.config.globalServiceConfigs?.[serviceName.toLowerCase()])?.[paramName];
437
+ }
438
+ /**
439
+ * Gets the RPC endpoint override for a specific network.
440
+ * @param networkId - The network identifier
441
+ * @returns The RPC endpoint configuration if found, undefined otherwise
442
+ */
443
+ getRpcEndpointOverride(networkId) {
444
+ if (!this.isInitialized) logger.warn(LOG_SYSTEM, "getRpcEndpointOverride called before initialization.");
445
+ return this.config.rpcEndpoints?.[networkId];
446
+ }
447
+ /**
448
+ * Get the indexer endpoint override for a specific network.
449
+ * Indexer endpoints are used for querying historical blockchain data.
450
+ * @param networkId The network identifier (e.g., 'stellar-testnet')
451
+ * @returns The indexer endpoint configuration, or undefined if not configured
452
+ */
453
+ getIndexerEndpointOverride(networkId) {
454
+ if (!this.isInitialized) logger.warn(LOG_SYSTEM, "getIndexerEndpointOverride called before initialization.");
455
+ return this.config.indexerEndpoints?.[networkId];
456
+ }
457
+ /**
458
+ * Returns the entire configuration object.
459
+ * Primarily for debugging or for parts of the app that need a broader view.
460
+ * Use specific getters like `getExplorerApiKey` or `isFeatureEnabled` where possible.
461
+ */
462
+ getConfig() {
463
+ return this.config;
464
+ }
465
+ /**
466
+ * Gets a nested configuration object with type safety.
467
+ *
468
+ * This is a helper method to safely access complex nested configuration objects
469
+ * with proper TypeScript type checking.
470
+ *
471
+ * @param serviceName The name of the service (e.g., 'walletui')
472
+ * @param paramName The parameter name that contains the nested object (e.g., 'config')
473
+ * Pass an empty string to get the entire service configuration.
474
+ * @returns The typed nested configuration object or undefined if not found
475
+ *
476
+ * @example
477
+ * // Get a typed UI kit configuration:
478
+ * const uiConfig = appConfigService.getTypedNestedConfig<UiKitConfiguration>('walletui', 'config');
479
+ * if (uiConfig) {
480
+ * console.log(uiConfig.kitName); // Properly typed
481
+ * }
482
+ *
483
+ * // Get entire service configuration:
484
+ * const allAnalytics = appConfigService.getTypedNestedConfig<AnalyticsConfig>('analytics', '');
485
+ */
486
+ getTypedNestedConfig(serviceName, paramName) {
487
+ if (!this.isInitialized) {
488
+ logger.warn(LOG_SYSTEM, "getTypedNestedConfig called before initialization.");
489
+ return;
490
+ }
491
+ try {
492
+ if (paramName === "") {
493
+ const serviceConfig = this.config.globalServiceConfigs?.[serviceName.toLowerCase()];
494
+ if (serviceConfig && typeof serviceConfig === "object") return serviceConfig;
495
+ return;
496
+ }
497
+ const param = this.getGlobalServiceParam(serviceName, paramName);
498
+ if (param && typeof param === "object") return param;
499
+ return;
500
+ } catch (error) {
501
+ logger.warn(LOG_SYSTEM, `Error accessing nested configuration for ${serviceName}.${paramName}:`, error);
502
+ return;
503
+ }
504
+ }
505
+ /**
506
+ * Checks if a nested configuration exists and has a specific property.
507
+ *
508
+ * @param serviceName The name of the service
509
+ * @param paramName The parameter name containing the nested object
510
+ * @param propName The property name to check for
511
+ * @returns True if the property exists in the nested configuration
512
+ *
513
+ * @example
514
+ * if (appConfigService.hasNestedConfigProperty('walletui', 'config', 'showInjectedConnector')) {
515
+ * // Do something when the property exists
516
+ * }
517
+ */
518
+ hasNestedConfigProperty(serviceName, paramName, propName) {
519
+ const nestedConfig = this.getTypedNestedConfig(serviceName, paramName);
520
+ return nestedConfig !== void 0 && Object.prototype.hasOwnProperty.call(nestedConfig, propName);
521
+ }
522
+ /**
523
+ * Gets wallet UI configuration for a specific ecosystem.
524
+ * Uses ecosystem-namespaced format with optional default fallback.
525
+ *
526
+ * @param ecosystemId The ecosystem ID (e.g., 'stellar', 'evm', 'solana')
527
+ * @returns The wallet UI configuration for the ecosystem, or undefined
528
+ *
529
+ * @example
530
+ * Configuration format:
531
+ * {
532
+ * "globalServiceConfigs": {
533
+ * "walletui": {
534
+ * "stellar": { "kitName": "stellar-wallets-kit", "kitConfig": {} },
535
+ * "evm": { "kitName": "rainbowkit", "kitConfig": {} },
536
+ * "default": { "kitName": "custom", "kitConfig": {} }
537
+ * }
538
+ * }
539
+ * }
540
+ * const stellarConfig = appConfigService.getWalletUIConfig('stellar');
541
+ */
542
+ getWalletUIConfig(ecosystemId) {
543
+ if (!this.isInitialized) {
544
+ logger.warn(LOG_SYSTEM, "getWalletUIConfig called before initialization.");
545
+ return;
546
+ }
547
+ try {
548
+ const walletUIService = this.config.globalServiceConfigs?.walletui;
549
+ if (!walletUIService) return;
550
+ if (ecosystemId && walletUIService[ecosystemId] && typeof walletUIService[ecosystemId] === "object") {
551
+ logger.debug(LOG_SYSTEM, `Found ecosystem-specific wallet UI config for ${ecosystemId}`);
552
+ return walletUIService[ecosystemId];
553
+ }
554
+ if (walletUIService.default && typeof walletUIService.default === "object") {
555
+ logger.debug(LOG_SYSTEM, `Using default wallet UI config for ecosystem ${ecosystemId}`);
556
+ return walletUIService.default;
557
+ }
558
+ return;
559
+ } catch (error) {
560
+ logger.warn(LOG_SYSTEM, `Error accessing wallet UI configuration for ecosystem ${ecosystemId}:`, error);
561
+ return;
562
+ }
563
+ }
564
+ };
565
+ const appConfigService = new AppConfigService();
566
+
567
+ //#endregion
568
+ //#region src/UserRpcConfigService.ts
569
+ /**
570
+ * Service for managing user-configured RPC endpoints.
571
+ * Stores RPC configurations in localStorage for persistence across sessions.
572
+ */
573
+ var UserRpcConfigService = class {
574
+ static STORAGE_PREFIX = "tfb_rpc_config_";
575
+ static eventListeners = /* @__PURE__ */ new Map();
576
+ /**
577
+ * Emits an RPC configuration event to all registered listeners
578
+ */
579
+ static emitEvent(event) {
580
+ const listeners = this.eventListeners.get(event.networkId) || /* @__PURE__ */ new Set();
581
+ const globalListeners = this.eventListeners.get("*") || /* @__PURE__ */ new Set();
582
+ [...listeners, ...globalListeners].forEach((listener) => {
583
+ try {
584
+ listener(event);
585
+ } catch (error) {
586
+ logger.error("UserRpcConfigService", "Error in event listener:", error);
587
+ }
588
+ });
589
+ }
590
+ /**
591
+ * Subscribes to RPC configuration changes for a specific network or all networks
592
+ * @param networkId The network identifier or '*' for all networks
593
+ * @param listener The callback to invoke when RPC config changes
594
+ * @returns Unsubscribe function
595
+ */
596
+ static subscribe(networkId, listener) {
597
+ if (!this.eventListeners.has(networkId)) this.eventListeners.set(networkId, /* @__PURE__ */ new Set());
598
+ this.eventListeners.get(networkId).add(listener);
599
+ return () => {
600
+ const listeners = this.eventListeners.get(networkId);
601
+ if (listeners) {
602
+ listeners.delete(listener);
603
+ if (listeners.size === 0) this.eventListeners.delete(networkId);
604
+ }
605
+ };
606
+ }
607
+ /**
608
+ * Saves a user RPC configuration for a specific network.
609
+ * @param networkId The network identifier
610
+ * @param config The RPC configuration to save
611
+ */
612
+ static saveUserRpcConfig(networkId, config) {
613
+ try {
614
+ const storageKey = `${this.STORAGE_PREFIX}${networkId}`;
615
+ localStorage.setItem(storageKey, JSON.stringify(config));
616
+ logger.info("UserRpcConfigService", `Saved RPC config for network ${networkId}`);
617
+ this.emitEvent({
618
+ type: "rpc-config-changed",
619
+ networkId,
620
+ config
621
+ });
622
+ } catch (error) {
623
+ logger.error("UserRpcConfigService", "Failed to save RPC config:", error);
624
+ }
625
+ }
626
+ /**
627
+ * Retrieves a user RPC configuration for a specific network.
628
+ * @param networkId The network identifier
629
+ * @returns The stored configuration or null if not found
630
+ */
631
+ static getUserRpcConfig(networkId) {
632
+ try {
633
+ const storageKey = `${this.STORAGE_PREFIX}${networkId}`;
634
+ const stored = localStorage.getItem(storageKey);
635
+ if (!stored) return null;
636
+ const config = JSON.parse(stored);
637
+ logger.info("UserRpcConfigService", `Retrieved RPC config for network ${networkId}`);
638
+ return config;
639
+ } catch (error) {
640
+ logger.error("UserRpcConfigService", "Failed to retrieve RPC config:", error);
641
+ return null;
642
+ }
643
+ }
644
+ /**
645
+ * Clears a user RPC configuration for a specific network.
646
+ * @param networkId The network identifier
647
+ */
648
+ static clearUserRpcConfig(networkId) {
649
+ try {
650
+ const storageKey = `${this.STORAGE_PREFIX}${networkId}`;
651
+ localStorage.removeItem(storageKey);
652
+ logger.info("UserRpcConfigService", `Cleared RPC config for network ${networkId}`);
653
+ this.emitEvent({
654
+ type: "rpc-config-cleared",
655
+ networkId
656
+ });
657
+ } catch (error) {
658
+ logger.error("UserRpcConfigService", "Failed to clear RPC config:", error);
659
+ }
660
+ }
661
+ /**
662
+ * Clears all user RPC configurations.
663
+ */
664
+ static clearAllUserRpcConfigs() {
665
+ try {
666
+ const keysToRemove = [];
667
+ for (let i = 0; i < localStorage.length; i++) {
668
+ const key = localStorage.key(i);
669
+ if (key?.startsWith(this.STORAGE_PREFIX)) keysToRemove.push(key);
670
+ }
671
+ keysToRemove.forEach((key) => localStorage.removeItem(key));
672
+ logger.info("UserRpcConfigService", `Cleared ${keysToRemove.length} RPC configs`);
673
+ } catch (error) {
674
+ logger.error("UserRpcConfigService", "Failed to clear all RPC configs:", error);
675
+ }
676
+ }
677
+ };
678
+ const userRpcConfigService = UserRpcConfigService;
679
+
680
+ //#endregion
681
+ //#region src/UserExplorerConfigService.ts
682
+ /**
683
+ * Service for managing user-configured block explorer endpoints and API keys.
684
+ * Stores explorer configurations in localStorage for persistence across sessions.
685
+ */
686
+ var UserExplorerConfigService = class {
687
+ static STORAGE_PREFIX = "tfb_explorer_config_";
688
+ static eventListeners = /* @__PURE__ */ new Map();
689
+ /**
690
+ * Emits an explorer configuration event to all registered listeners
691
+ */
692
+ static emitEvent(event) {
693
+ const listeners = this.eventListeners.get(event.networkId) || /* @__PURE__ */ new Set();
694
+ const globalListeners = this.eventListeners.get("*") || /* @__PURE__ */ new Set();
695
+ [...listeners, ...globalListeners].forEach((listener) => {
696
+ try {
697
+ listener(event);
698
+ } catch (error) {
699
+ logger.error("UserExplorerConfigService", "Error in event listener:", error);
700
+ }
701
+ });
702
+ }
703
+ /**
704
+ * Subscribes to explorer configuration changes for a specific network or all networks
705
+ * @param networkId The network identifier or '*' for all networks
706
+ * @param listener The callback to invoke when explorer config changes
707
+ * @returns Unsubscribe function
708
+ */
709
+ static subscribe(networkId, listener) {
710
+ if (!this.eventListeners.has(networkId)) this.eventListeners.set(networkId, /* @__PURE__ */ new Set());
711
+ this.eventListeners.get(networkId).add(listener);
712
+ return () => {
713
+ const listeners = this.eventListeners.get(networkId);
714
+ if (listeners) {
715
+ listeners.delete(listener);
716
+ if (listeners.size === 0) this.eventListeners.delete(networkId);
717
+ }
718
+ };
719
+ }
720
+ /**
721
+ * Saves a user explorer configuration for a specific network.
722
+ * @param networkId The network identifier
723
+ * @param config The explorer configuration to save
724
+ */
725
+ static saveUserExplorerConfig(networkId, config) {
726
+ try {
727
+ const storageKey = `${this.STORAGE_PREFIX}${networkId}`;
728
+ localStorage.setItem(storageKey, JSON.stringify(config));
729
+ logger.info("UserExplorerConfigService", `Saved explorer config for network ${networkId}`);
730
+ this.emitEvent({
731
+ type: "explorer-config-changed",
732
+ networkId,
733
+ config
734
+ });
735
+ } catch (error) {
736
+ logger.error("UserExplorerConfigService", "Failed to save explorer config:", error);
737
+ }
738
+ }
739
+ /**
740
+ * Retrieves a user explorer configuration for a specific network.
741
+ * First checks for global settings, then falls back to network-specific settings.
742
+ * @param networkId The network identifier
743
+ * @returns The stored configuration or null if not found
744
+ */
745
+ static getUserExplorerConfig(networkId) {
746
+ try {
747
+ const globalKey = `${this.STORAGE_PREFIX}__global__`;
748
+ const globalStored = localStorage.getItem(globalKey);
749
+ if (globalStored) {
750
+ const globalConfig = JSON.parse(globalStored);
751
+ if (globalConfig.applyToAllNetworks) {
752
+ logger.info("UserExplorerConfigService", `Using global explorer config for network ${networkId}`);
753
+ return globalConfig;
754
+ }
755
+ }
756
+ const storageKey = `${this.STORAGE_PREFIX}${networkId}`;
757
+ const stored = localStorage.getItem(storageKey);
758
+ if (!stored) return null;
759
+ const config = JSON.parse(stored);
760
+ logger.info("UserExplorerConfigService", `Retrieved explorer config for network ${networkId}`);
761
+ return config;
762
+ } catch (error) {
763
+ logger.error("UserExplorerConfigService", "Failed to retrieve explorer config:", error);
764
+ return null;
765
+ }
766
+ }
767
+ /**
768
+ * Clears a user explorer configuration for a specific network.
769
+ * @param networkId The network identifier
770
+ */
771
+ static clearUserExplorerConfig(networkId) {
772
+ try {
773
+ const storageKey = `${this.STORAGE_PREFIX}${networkId}`;
774
+ localStorage.removeItem(storageKey);
775
+ logger.info("UserExplorerConfigService", `Cleared explorer config for network ${networkId}`);
776
+ this.emitEvent({
777
+ type: "explorer-config-cleared",
778
+ networkId
779
+ });
780
+ } catch (error) {
781
+ logger.error("UserExplorerConfigService", "Failed to clear explorer config:", error);
782
+ }
783
+ }
784
+ /**
785
+ * Clears all user explorer configurations.
786
+ */
787
+ static clearAllUserExplorerConfigs() {
788
+ try {
789
+ const keysToRemove = [];
790
+ for (let i = 0; i < localStorage.length; i++) {
791
+ const key = localStorage.key(i);
792
+ if (key?.startsWith(this.STORAGE_PREFIX)) keysToRemove.push(key);
793
+ }
794
+ keysToRemove.forEach((key) => {
795
+ const networkId = key.substring(this.STORAGE_PREFIX.length);
796
+ localStorage.removeItem(key);
797
+ this.emitEvent({
798
+ type: "explorer-config-cleared",
799
+ networkId
800
+ });
801
+ });
802
+ logger.info("UserExplorerConfigService", `Cleared ${keysToRemove.length} explorer configs`);
803
+ } catch (error) {
804
+ logger.error("UserExplorerConfigService", "Failed to clear all explorer configs:", error);
805
+ }
806
+ }
807
+ /**
808
+ * Gets all network IDs that have explorer configurations.
809
+ * @returns Array of network IDs
810
+ */
811
+ static getConfiguredNetworkIds() {
812
+ try {
813
+ const networkIds = [];
814
+ for (let i = 0; i < localStorage.length; i++) {
815
+ const key = localStorage.key(i);
816
+ if (key?.startsWith(this.STORAGE_PREFIX)) {
817
+ const networkId = key.substring(this.STORAGE_PREFIX.length);
818
+ if (networkId !== "__global__") networkIds.push(networkId);
819
+ }
820
+ }
821
+ return networkIds;
822
+ } catch (error) {
823
+ logger.error("UserExplorerConfigService", "Failed to get configured network IDs:", error);
824
+ return [];
825
+ }
826
+ }
827
+ };
828
+ const userExplorerConfigService = UserExplorerConfigService;
829
+
830
+ //#endregion
831
+ //#region src/UserNetworkServiceConfigService.ts
832
+ /**
833
+ * Service for managing user-defined network service configurations.
834
+ *
835
+ * This service provides a generic, chain-agnostic way to store and retrieve
836
+ * per-network, per-service user configurations.
837
+ *
838
+ * Configurations are stored in localStorage with the key format:
839
+ * `tfb_service_config_{serviceId}__{networkId}`
840
+ *
841
+ * @example
842
+ * ```typescript
843
+ * // Save RPC configuration for Sepolia
844
+ * userNetworkServiceConfigService.save('ethereum-sepolia', 'rpc', {
845
+ * rpcUrl: 'https://sepolia.infura.io/v3/your-key'
846
+ * });
847
+ *
848
+ * // Retrieve configuration
849
+ * const config = userNetworkServiceConfigService.get('ethereum-sepolia', 'rpc');
850
+ *
851
+ * // Subscribe to changes
852
+ * const unsubscribe = userNetworkServiceConfigService.subscribe(
853
+ * 'ethereum-sepolia',
854
+ * 'rpc',
855
+ * (event) => {
856
+ * console.log('Config changed:', event.config);
857
+ * }
858
+ * );
859
+ * ```
860
+ *
861
+ * @class UserNetworkServiceConfigService
862
+ */
863
+ var UserNetworkServiceConfigService = class {
864
+ static STORAGE_PREFIX = "tfb_service_config_";
865
+ static listeners = /* @__PURE__ */ new Map();
866
+ /**
867
+ * Generates a localStorage key for a network-service combination.
868
+ *
869
+ * @private
870
+ * @param networkId - The network ID
871
+ * @param serviceId - The service ID
872
+ * @returns The storage key string
873
+ */
874
+ static key(networkId, serviceId) {
875
+ return `${this.STORAGE_PREFIX}${serviceId}__${networkId}`;
876
+ }
877
+ /**
878
+ * Subscribes to configuration change events for a specific network and/or service.
879
+ *
880
+ * Use '*' as a wildcard to listen to all networks or all services.
881
+ * The listener will be called whenever a matching configuration changes or is cleared.
882
+ *
883
+ * @param networkId - Network ID to listen to, or '*' for all networks
884
+ * @param serviceId - Service ID to listen to, or '*' for all services
885
+ * @param listener - Callback function to invoke when matching events occur
886
+ * @returns Unsubscribe function to remove the listener
887
+ *
888
+ * @example
889
+ * ```typescript
890
+ * // Listen to all RPC config changes across all networks
891
+ * const unsubscribe = userNetworkServiceConfigService.subscribe('*', 'rpc', (event) => {
892
+ * console.log(`${event.networkId} RPC config changed`);
893
+ * });
894
+ *
895
+ * // Later, unsubscribe
896
+ * unsubscribe();
897
+ * ```
898
+ */
899
+ static subscribe(networkId, serviceId, listener) {
900
+ const k = `${networkId}:${serviceId}`;
901
+ if (!this.listeners.has(k)) this.listeners.set(k, /* @__PURE__ */ new Set());
902
+ this.listeners.get(k).add(listener);
903
+ return () => {
904
+ const set = this.listeners.get(k);
905
+ if (set) {
906
+ set.delete(listener);
907
+ if (set.size === 0) this.listeners.delete(k);
908
+ }
909
+ };
910
+ }
911
+ /**
912
+ * Emits an event to all matching subscribers.
913
+ * Subscribers are matched based on exact network/service IDs or wildcards.
914
+ *
915
+ * @private
916
+ * @param event - The event to emit
917
+ */
918
+ static emit(event) {
919
+ const targets = [
920
+ `${event.networkId}:${event.serviceId}`,
921
+ `${event.networkId}:*`,
922
+ `*:${event.serviceId}`,
923
+ `*:*`
924
+ ];
925
+ for (const k of targets) {
926
+ const set = this.listeners.get(k);
927
+ if (!set) continue;
928
+ for (const fn of set) try {
929
+ fn(event);
930
+ } catch (e) {
931
+ logger.error("UserNetworkServiceConfigService", "Error in event listener:", e);
932
+ }
933
+ }
934
+ }
935
+ /**
936
+ * Saves a service configuration for a specific network.
937
+ *
938
+ * The configuration is stored in localStorage and all matching subscribers
939
+ * are notified via a 'service-config-changed' event.
940
+ *
941
+ * @param networkId - The network ID (e.g., 'ethereum-sepolia')
942
+ * @param serviceId - The service ID (e.g., 'rpc', 'explorer', 'contract-definitions')
943
+ * @param config - The configuration object to save
944
+ *
945
+ * @example
946
+ * ```typescript
947
+ * userNetworkServiceConfigService.save('ethereum-sepolia', 'rpc', {
948
+ * rpcUrl: 'https://sepolia.infura.io/v3/your-key'
949
+ * });
950
+ * ```
951
+ */
952
+ static save(networkId, serviceId, config) {
953
+ try {
954
+ localStorage.setItem(this.key(networkId, serviceId), JSON.stringify(config));
955
+ logger.info("UserNetworkServiceConfigService", `Saved config for ${serviceId} on network ${networkId}`);
956
+ this.emit({
957
+ type: "service-config-changed",
958
+ networkId,
959
+ serviceId,
960
+ config
961
+ });
962
+ } catch (e) {
963
+ logger.error("UserNetworkServiceConfigService", "Failed to save config:", e);
964
+ }
965
+ }
966
+ /**
967
+ * Retrieves a saved service configuration for a specific network.
968
+ *
969
+ * @param networkId - The network ID (e.g., 'ethereum-sepolia')
970
+ * @param serviceId - The service ID (e.g., 'rpc', 'explorer', 'contract-definitions')
971
+ * @returns The configuration object, or null if not found or if retrieval fails
972
+ *
973
+ * @example
974
+ * ```typescript
975
+ * const config = userNetworkServiceConfigService.get('ethereum-sepolia', 'rpc');
976
+ * if (config) {
977
+ * console.log('RPC URL:', config.rpcUrl);
978
+ * }
979
+ * ```
980
+ */
981
+ static get(networkId, serviceId) {
982
+ try {
983
+ const raw = localStorage.getItem(this.key(networkId, serviceId));
984
+ return raw ? JSON.parse(raw) : null;
985
+ } catch (e) {
986
+ logger.error("UserNetworkServiceConfigService", "Failed to retrieve config:", e);
987
+ return null;
988
+ }
989
+ }
990
+ /**
991
+ * Clears a saved service configuration for a specific network.
992
+ *
993
+ * Removes the configuration from localStorage and notifies all matching subscribers
994
+ * via a 'service-config-cleared' event.
995
+ *
996
+ * @param networkId - The network ID (e.g., 'ethereum-sepolia')
997
+ * @param serviceId - The service ID (e.g., 'rpc', 'explorer', 'contract-definitions')
998
+ *
999
+ * @example
1000
+ * ```typescript
1001
+ * userNetworkServiceConfigService.clear('ethereum-sepolia', 'rpc');
1002
+ * ```
1003
+ */
1004
+ static clear(networkId, serviceId) {
1005
+ try {
1006
+ localStorage.removeItem(this.key(networkId, serviceId));
1007
+ logger.info("UserNetworkServiceConfigService", `Cleared config for ${serviceId} on network ${networkId}`);
1008
+ this.emit({
1009
+ type: "service-config-cleared",
1010
+ networkId,
1011
+ serviceId
1012
+ });
1013
+ } catch (e) {
1014
+ logger.error("UserNetworkServiceConfigService", "Failed to clear config:", e);
1015
+ }
1016
+ }
1017
+ };
1018
+ /**
1019
+ * Singleton instance of UserNetworkServiceConfigService.
1020
+ * This is the preferred way to access the service.
1021
+ *
1022
+ * @example
1023
+ * ```typescript
1024
+ * import { userNetworkServiceConfigService } from '@openzeppelin/ui-utils';
1025
+ *
1026
+ * userNetworkServiceConfigService.save('ethereum-sepolia', 'rpc', { rpcUrl: '...' });
1027
+ * ```
1028
+ */
1029
+ const userNetworkServiceConfigService = UserNetworkServiceConfigService;
1030
+
1031
+ //#endregion
1032
+ //#region src/fieldDefaults.ts
1033
+ /**
1034
+ * Get a default value for a field type.
1035
+ * This is a chain-agnostic utility that provides appropriate default values
1036
+ * based on the UI field type.
1037
+ *
1038
+ * @param fieldType - The UI field type
1039
+ * @returns The appropriate default value for that field type
1040
+ */
1041
+ function getDefaultValueForType(fieldType) {
1042
+ switch (fieldType) {
1043
+ case "checkbox": return false;
1044
+ case "number":
1045
+ case "amount": return 0;
1046
+ case "array": return [];
1047
+ case "object": return {};
1048
+ case "array-object": return [];
1049
+ case "map": return [];
1050
+ case "blockchain-address":
1051
+ case "bigint":
1052
+ case "text":
1053
+ case "textarea":
1054
+ case "bytes":
1055
+ case "email":
1056
+ case "password":
1057
+ case "select":
1058
+ case "radio":
1059
+ case "date":
1060
+ case "hidden":
1061
+ default: return "";
1062
+ }
1063
+ }
1064
+
1065
+ //#endregion
1066
+ //#region src/fieldValidation.ts
1067
+ /**
1068
+ * Enhances field validation with numeric bounds based on parameter type.
1069
+ * Only applies bounds if they are not already set in the validation object.
1070
+ *
1071
+ * @param validation - Existing validation rules (may be undefined)
1072
+ * @param parameterType - The blockchain parameter type (e.g., 'uint32', 'U32', 'Uint<0..255>')
1073
+ * @param boundsMap - Chain-specific map of type names to min/max bounds
1074
+ * @returns Enhanced validation object with numeric bounds applied
1075
+ *
1076
+ * @example
1077
+ * ```typescript
1078
+ * const stellarBounds = { U32: { min: 0, max: 4_294_967_295 } };
1079
+ * const validation = enhanceNumericValidation(undefined, 'U32', stellarBounds);
1080
+ * // Returns: { min: 0, max: 4_294_967_295 }
1081
+ * ```
1082
+ */
1083
+ function enhanceNumericValidation(validation, parameterType, boundsMap) {
1084
+ const result = { ...validation ?? {} };
1085
+ const bounds = boundsMap[parameterType];
1086
+ if (!bounds) return result;
1087
+ if (bounds.min !== void 0 && result.min === void 0) result.min = bounds.min;
1088
+ if (bounds.max !== void 0 && result.max === void 0) result.max = bounds.max;
1089
+ return result;
1090
+ }
1091
+
1092
+ //#endregion
1093
+ //#region src/typeguards.ts
1094
+ /**
1095
+ * Type guard to check if a value is a non-null object (Record<string, unknown>).
1096
+ * Useful for safely accessing properties on an 'unknown' type after this check.
1097
+ * @param value - The value to check.
1098
+ * @returns True if the value is a non-null object, false otherwise.
1099
+ */
1100
+ function isRecordWithProperties(value) {
1101
+ return typeof value === "object" && value !== null;
1102
+ }
1103
+ /**
1104
+ * Type guard to check if a value is a plain object (not an array, not null).
1105
+ * This is useful for distinguishing between objects and arrays, since arrays are technically objects in JavaScript.
1106
+ * @param value - The value to check.
1107
+ * @returns True if the value is a plain object (not array, not null), false otherwise.
1108
+ */
1109
+ function isPlainObject(value) {
1110
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1111
+ }
1112
+
1113
+ //#endregion
1114
+ //#region src/cn.ts
1115
+ /**
1116
+ * Combines class names using clsx and tailwind-merge.
1117
+ * @param inputs - Class values to combine
1118
+ * @returns Merged class name string
1119
+ */
1120
+ function cn(...inputs) {
1121
+ return (0, tailwind_merge.twMerge)((0, clsx.clsx)(inputs));
1122
+ }
1123
+
1124
+ //#endregion
1125
+ //#region src/formatting.ts
1126
+ /**
1127
+ * String and date formatting utility functions
1128
+ * These utilities help with common formatting operations
1129
+ */
1130
+ /**
1131
+ * Truncates a string (like an Ethereum address) in the middle
1132
+ * @param str The string to truncate
1133
+ * @param startChars Number of characters to show at the beginning
1134
+ * @param endChars Number of characters to show at the end
1135
+ * @returns The truncated string with ellipsis in the middle
1136
+ */
1137
+ function truncateMiddle(str, startChars = 6, endChars = 4) {
1138
+ if (!str) return "";
1139
+ if (str.length <= startChars + endChars) return str;
1140
+ return `${str.substring(0, startChars)}...${str.substring(str.length - endChars)}`;
1141
+ }
1142
+ /**
1143
+ * Formats a timestamp as a relative time string (e.g., "2h ago", "just now")
1144
+ * @param date The date to format
1145
+ * @returns A human-readable relative time string
1146
+ */
1147
+ function formatTimestamp(date) {
1148
+ const diffMs = (/* @__PURE__ */ new Date()).getTime() - date.getTime();
1149
+ const diffMinutes = Math.floor(diffMs / (1e3 * 60));
1150
+ const diffHours = Math.floor(diffMs / (1e3 * 60 * 60));
1151
+ const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
1152
+ if (diffMinutes < 1) return "just now";
1153
+ if (diffMinutes < 60) return `${diffMinutes}m ago`;
1154
+ if (diffHours < 24) return `${diffHours}h ago`;
1155
+ if (diffDays < 7) return `${diffDays}d ago`;
1156
+ return date.toLocaleDateString();
1157
+ }
1158
+ /**
1159
+ * Detects whether a string contains hex-encoded or base64-encoded binary data.
1160
+ * Useful for auto-detecting the encoding format of user inputs across blockchain adapters.
1161
+ *
1162
+ * @param value - The string to analyze
1163
+ * @returns 'hex' if the string appears to be hexadecimal, 'base64' if it appears to be base64
1164
+ *
1165
+ * @example
1166
+ * ```typescript
1167
+ * detectBytesEncoding("48656c6c6f") // → 'hex'
1168
+ * detectBytesEncoding("SGVsbG8=") // → 'base64'
1169
+ * detectBytesEncoding("0x48656c6c6f") // → 'hex' (after stripping 0x prefix)
1170
+ * ```
1171
+ */
1172
+ function detectBytesEncoding(value) {
1173
+ const trimmed = value?.trim() ?? "";
1174
+ const without0x = trimmed.startsWith("0x") || trimmed.startsWith("0X") ? trimmed.slice(2) : trimmed;
1175
+ const hexRegex = /^[0-9a-fA-F]+$/;
1176
+ const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
1177
+ if (hexRegex.test(without0x) && without0x.length > 0 && without0x.length % 2 === 0) return "hex";
1178
+ if (base64Regex.test(trimmed) && trimmed.length % 4 === 0) try {
1179
+ const decoded = atob(trimmed);
1180
+ if (btoa(decoded).replace(/=+$/, "") === trimmed.replace(/=+$/, "")) return "base64";
1181
+ } catch {}
1182
+ return "hex";
1183
+ }
1184
+
1185
+ //#endregion
1186
+ //#region src/generateId.ts
1187
+ /**
1188
+ * General utility functions, which are not specific to any blockchain
1189
+ * It's important to keep these functions as simple as possible and avoid any
1190
+ * dependencies from other packages.
1191
+ */
1192
+ /**
1193
+ * Generates a unique ID for form fields, components, etc.
1194
+ * Uses crypto.getRandomValues() for browser-compatible random ID generation.
1195
+ *
1196
+ * @param prefix Optional prefix to add before the UUID
1197
+ * @returns A string ID that is guaranteed to be unique
1198
+ */
1199
+ function generateId(prefix) {
1200
+ const uuid$1 = (0, uuid.v4)();
1201
+ return prefix ? `${prefix}_${uuid$1}` : uuid$1;
1202
+ }
1203
+
1204
+ //#endregion
1205
+ //#region src/validators.ts
1206
+ /**
1207
+ * URL validation utilities
1208
+ */
1209
+ /**
1210
+ * Validates if a string is a valid URL (supports http, https, and ftp protocols).
1211
+ * Relies solely on the URL constructor for validation.
1212
+ *
1213
+ * @param urlString - The string to validate
1214
+ * @returns True if the URL is valid, false otherwise
1215
+ */
1216
+ function isValidUrl(urlString) {
1217
+ if (!urlString || typeof urlString !== "string") return false;
1218
+ try {
1219
+ new URL(urlString);
1220
+ return true;
1221
+ } catch {
1222
+ return false;
1223
+ }
1224
+ }
1225
+ /**
1226
+ * Gets a user-friendly error message for invalid URLs.
1227
+ *
1228
+ * @returns Standard error message for invalid URLs
1229
+ */
1230
+ function getInvalidUrlMessage() {
1231
+ return "Please enter a valid URL (e.g., https://example.com)";
1232
+ }
1233
+
1234
+ //#endregion
1235
+ //#region src/async.ts
1236
+ /**
1237
+ * Utility to add delay between operations
1238
+ * @param ms - Milliseconds to delay
1239
+ * @returns Promise that resolves after the specified delay
1240
+ */
1241
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1242
+ /**
1243
+ * Executes operations in batches with rate limiting to prevent API overload
1244
+ * @param operations - Array of functions that return promises
1245
+ * @param batchSize - Number of operations to execute in parallel per batch (default: 2)
1246
+ * @param delayMs - Delay in milliseconds between batches (default: 100)
1247
+ * @returns Promise that resolves to an array of results from all operations
1248
+ */
1249
+ async function rateLimitedBatch(operations, batchSize = 2, delayMs = 100) {
1250
+ const results = [];
1251
+ for (let i = 0; i < operations.length; i += batchSize) {
1252
+ const batch = operations.slice(i, i + batchSize);
1253
+ const batchResults = await Promise.all(batch.map((operation) => operation()));
1254
+ results.push(...batchResults);
1255
+ if (i + batchSize < operations.length) await delay(delayMs);
1256
+ }
1257
+ return results;
1258
+ }
1259
+ /**
1260
+ * Wraps a promise with a timeout. Rejects with a descriptive Error after timeoutMs.
1261
+ *
1262
+ * @param promise The promise to wrap
1263
+ * @param timeoutMs Timeout in milliseconds
1264
+ * @param label Optional label to include in the timeout error message
1265
+ */
1266
+ function withTimeout(promise, timeoutMs, label) {
1267
+ return new Promise((resolve, reject) => {
1268
+ const timer = setTimeout(() => {
1269
+ reject(/* @__PURE__ */ new Error(`${label ?? "operation"} timed out after ${timeoutMs}ms`));
1270
+ }, timeoutMs);
1271
+ promise.then((value) => {
1272
+ clearTimeout(timer);
1273
+ resolve(value);
1274
+ }).catch((err) => {
1275
+ clearTimeout(timer);
1276
+ reject(err);
1277
+ });
1278
+ });
1279
+ }
1280
+ /**
1281
+ * Default concurrency limit for parallel operations.
1282
+ * Set to a reasonable value that balances performance and service limits.
1283
+ */
1284
+ const DEFAULT_CONCURRENCY_LIMIT = 10;
1285
+ /**
1286
+ * Execute an array of promise-returning functions with a concurrency limit.
1287
+ *
1288
+ * Uses a worker pool approach that maintains up to `limit` concurrent operations.
1289
+ * As soon as one operation completes, the next one starts immediately, maximizing
1290
+ * throughput while respecting the concurrency limit.
1291
+ *
1292
+ * Results are returned in the same order as the input tasks, regardless of
1293
+ * completion order.
1294
+ *
1295
+ * @param tasks Array of functions that return promises
1296
+ * @param limit Maximum number of concurrent executions (default: 10)
1297
+ * @returns Promise resolving to array of results in same order as input tasks
1298
+ *
1299
+ * @example
1300
+ * ```typescript
1301
+ * // Fetch 100 role members with max 10 concurrent RPC requests
1302
+ * const tasks = memberIndices.map((index) => () => getRoleMember(contract, role, index));
1303
+ * const members = await promiseAllWithLimit(tasks, 10);
1304
+ * ```
1305
+ */
1306
+ async function promiseAllWithLimit(tasks, limit = DEFAULT_CONCURRENCY_LIMIT) {
1307
+ if (tasks.length === 0) return [];
1308
+ if (limit >= tasks.length) return Promise.all(tasks.map((task) => task()));
1309
+ const results = new Array(tasks.length);
1310
+ let currentIndex = 0;
1311
+ async function worker() {
1312
+ while (currentIndex < tasks.length) {
1313
+ const index = currentIndex++;
1314
+ const task = tasks[index];
1315
+ results[index] = await task();
1316
+ }
1317
+ }
1318
+ const workers = Array.from({ length: Math.min(limit, tasks.length) }, () => worker());
1319
+ await Promise.all(workers);
1320
+ return results;
1321
+ }
1322
+ /**
1323
+ * Execute an array of promise-returning functions with a concurrency limit,
1324
+ * settling all promises (similar to Promise.allSettled but with concurrency control).
1325
+ *
1326
+ * Unlike promiseAllWithLimit, this function does not fail fast on errors.
1327
+ * All tasks will be executed regardless of individual failures.
1328
+ *
1329
+ * @param tasks Array of functions that return promises
1330
+ * @param limit Maximum number of concurrent executions (default: 10)
1331
+ * @returns Promise resolving to array of settled results in same order as input tasks
1332
+ *
1333
+ * @example
1334
+ * ```typescript
1335
+ * const tasks = items.map((item) => () => fetchItem(item.id));
1336
+ * const results = await promiseAllSettledWithLimit(tasks, 10);
1337
+ *
1338
+ * for (const result of results) {
1339
+ * if (result.status === 'fulfilled') {
1340
+ * console.log('Success:', result.value);
1341
+ * } else {
1342
+ * console.log('Failed:', result.reason);
1343
+ * }
1344
+ * }
1345
+ * ```
1346
+ */
1347
+ async function promiseAllSettledWithLimit(tasks, limit = DEFAULT_CONCURRENCY_LIMIT) {
1348
+ if (tasks.length === 0) return [];
1349
+ if (limit >= tasks.length) return Promise.allSettled(tasks.map((task) => task()));
1350
+ const results = new Array(tasks.length);
1351
+ let currentIndex = 0;
1352
+ async function worker() {
1353
+ while (currentIndex < tasks.length) {
1354
+ const index = currentIndex++;
1355
+ const task = tasks[index];
1356
+ try {
1357
+ results[index] = {
1358
+ status: "fulfilled",
1359
+ value: await task()
1360
+ };
1361
+ } catch (reason) {
1362
+ results[index] = {
1363
+ status: "rejected",
1364
+ reason
1365
+ };
1366
+ }
1367
+ }
1368
+ }
1369
+ const workers = Array.from({ length: Math.min(limit, tasks.length) }, () => worker());
1370
+ await Promise.all(workers);
1371
+ return results;
1372
+ }
1373
+
1374
+ //#endregion
1375
+ //#region src/hash.ts
1376
+ /**
1377
+ * Simple browser-compatible hash utilities
1378
+ * These functions provide deterministic hashing for content comparison
1379
+ * and are not intended for cryptographic purposes.
1380
+ */
1381
+ /**
1382
+ * Creates a simple hash from a string using a non-cryptographic algorithm
1383
+ * Suitable for content comparison, caching keys, and quick fingerprinting
1384
+ *
1385
+ * @param str - The string to hash
1386
+ * @returns A hexadecimal hash string (always positive)
1387
+ *
1388
+ * @example
1389
+ * ```typescript
1390
+ * const hash1 = simpleHash('{"name": "test"}');
1391
+ * const hash2 = simpleHash('{"name": "test"}');
1392
+ * console.log(hash1 === hash2); // true - deterministic
1393
+ * ```
1394
+ */
1395
+ function simpleHash(str) {
1396
+ let hash = 0;
1397
+ for (let i = 0; i < str.length; i++) {
1398
+ const char = str.charCodeAt(i);
1399
+ hash = (hash << 5) - hash + char;
1400
+ hash = hash & hash;
1401
+ }
1402
+ return Math.abs(hash).toString(16);
1403
+ }
1404
+
1405
+ //#endregion
1406
+ //#region src/bytesValidation.ts
1407
+ /**
1408
+ * Validates bytes input using the established validator.js library.
1409
+ *
1410
+ * This function provides comprehensive validation for blockchain bytes data including:
1411
+ * - Hex encoding validation (with optional 0x prefix)
1412
+ * - Base64 encoding validation
1413
+ * - Byte length validation
1414
+ * - Format detection
1415
+ *
1416
+ * @param value - The input string to validate
1417
+ * @param options - Validation options
1418
+ * @returns Validation result with details
1419
+ *
1420
+ * @example
1421
+ * ```typescript
1422
+ * validateBytes('48656c6c6f') // → { isValid: true, detectedFormat: 'hex', byteSize: 5 }
1423
+ * validateBytes('SGVsbG8=') // → { isValid: true, detectedFormat: 'base64', byteSize: 5 }
1424
+ * validateBytes('invalid') // → { isValid: false, error: '...' }
1425
+ * ```
1426
+ */
1427
+ function validateBytes(value, options = {}) {
1428
+ const { acceptedFormats = "both", maxBytes, allowHexPrefix = true } = options;
1429
+ if (!value || value.trim() === "") return {
1430
+ isValid: true,
1431
+ cleanedValue: "",
1432
+ byteSize: 0
1433
+ };
1434
+ const cleanValue = value.trim().replace(/\s+/g, "");
1435
+ const hasHexPrefix = cleanValue.startsWith("0x");
1436
+ const withoutPrefix = hasHexPrefix ? cleanValue.slice(2) : cleanValue;
1437
+ if (withoutPrefix === "") return {
1438
+ isValid: false,
1439
+ error: "Bytes value cannot be empty",
1440
+ cleanedValue: cleanValue
1441
+ };
1442
+ let detectedFormat = null;
1443
+ let byteSize = 0;
1444
+ if (validator.default.isHexadecimal(withoutPrefix)) {
1445
+ detectedFormat = "hex";
1446
+ if (withoutPrefix.length % 2 !== 0) return {
1447
+ isValid: false,
1448
+ error: "Hex string must have even number of characters",
1449
+ cleanedValue: cleanValue,
1450
+ detectedFormat
1451
+ };
1452
+ byteSize = withoutPrefix.length / 2;
1453
+ } else if (validator.default.isBase64(withoutPrefix)) {
1454
+ detectedFormat = "base64";
1455
+ try {
1456
+ byteSize = atob(withoutPrefix).length;
1457
+ } catch {
1458
+ return {
1459
+ isValid: false,
1460
+ error: "Invalid base64 encoding",
1461
+ cleanedValue: cleanValue
1462
+ };
1463
+ }
1464
+ }
1465
+ if (!detectedFormat) return {
1466
+ isValid: false,
1467
+ error: "Invalid format. Expected hex or base64 encoding",
1468
+ cleanedValue: cleanValue
1469
+ };
1470
+ if (acceptedFormats !== "both") {
1471
+ if (acceptedFormats === "hex" && detectedFormat !== "hex") return {
1472
+ isValid: false,
1473
+ error: "Only hex format is accepted for this field",
1474
+ cleanedValue: cleanValue,
1475
+ detectedFormat
1476
+ };
1477
+ if (acceptedFormats === "base64" && detectedFormat !== "base64") return {
1478
+ isValid: false,
1479
+ error: "Only base64 format is accepted for this field",
1480
+ cleanedValue: cleanValue,
1481
+ detectedFormat
1482
+ };
1483
+ }
1484
+ if (detectedFormat === "hex" && hasHexPrefix && !allowHexPrefix) return {
1485
+ isValid: false,
1486
+ error: "0x prefix not allowed for this field",
1487
+ cleanedValue: cleanValue,
1488
+ detectedFormat
1489
+ };
1490
+ if (maxBytes && byteSize > maxBytes) return {
1491
+ isValid: false,
1492
+ error: `Maximum ${maxBytes} bytes allowed (${detectedFormat === "hex" ? `${maxBytes * 2} hex characters` : `${maxBytes} bytes`})`,
1493
+ cleanedValue: cleanValue,
1494
+ detectedFormat,
1495
+ byteSize
1496
+ };
1497
+ return {
1498
+ isValid: true,
1499
+ cleanedValue: cleanValue,
1500
+ detectedFormat,
1501
+ byteSize
1502
+ };
1503
+ }
1504
+ /**
1505
+ * Simple validation function that returns boolean or error string
1506
+ * (for compatibility with existing React Hook Form validation)
1507
+ *
1508
+ * @param value - The input string to validate
1509
+ * @param options - Validation options
1510
+ * @returns true if valid, error string if invalid
1511
+ */
1512
+ function validateBytesSimple(value, options = {}) {
1513
+ const result = validateBytes(value, options);
1514
+ return result.isValid ? true : result.error || "Invalid bytes format";
1515
+ }
1516
+ /**
1517
+ * Extracts the size from a Bytes<N> type string, or returns undefined for dynamic Uint8Array
1518
+ *
1519
+ * @param type - Type string (e.g., "Bytes<32>", "Uint8Array", "bytes")
1520
+ * @returns Size in bytes if fixed-size, undefined if dynamic
1521
+ *
1522
+ * @example
1523
+ * ```typescript
1524
+ * getBytesSize('Bytes<32>') // → 32
1525
+ * getBytesSize('Bytes<64>') // → 64
1526
+ * getBytesSize('Uint8Array') // → undefined
1527
+ * getBytesSize('bytes') // → undefined
1528
+ * ```
1529
+ */
1530
+ function getBytesSize(type) {
1531
+ const match = type.match(/^Bytes<(\d+)>$/i);
1532
+ if (match) return Number.parseInt(match[1], 10);
1533
+ }
1534
+
1535
+ //#endregion
1536
+ //#region src/bytesConversion.ts
1537
+ /**
1538
+ * Cross-platform bytes conversion utilities that work in both browser and Node.js
1539
+ * without requiring Buffer polyfills.
1540
+ */
1541
+ /**
1542
+ * Convert a hex string to Uint8Array using native browser APIs
1543
+ * @param hex - Hex string (with or without 0x prefix)
1544
+ * @returns Uint8Array representation
1545
+ */
1546
+ function hexToBytes(hex) {
1547
+ const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
1548
+ if (cleanHex.length % 2 !== 0) throw new Error("Hex string must have even length");
1549
+ if (!/^[0-9a-fA-F]*$/.test(cleanHex)) throw new Error("Invalid hex characters in string");
1550
+ const bytes = new Uint8Array(cleanHex.length / 2);
1551
+ for (let i = 0; i < cleanHex.length; i += 2) bytes[i / 2] = parseInt(cleanHex.substring(i, i + 2), 16);
1552
+ return bytes;
1553
+ }
1554
+ /**
1555
+ * Convert a base64 string to Uint8Array using native browser APIs
1556
+ * Handles data URLs by stripping the prefix
1557
+ * @param base64 - Base64 encoded string (with optional data URL prefix)
1558
+ * @returns Uint8Array representation
1559
+ */
1560
+ function base64ToBytes(base64) {
1561
+ const cleaned = base64.includes(",") ? base64.split(",")[1] : base64;
1562
+ const binaryString = atob(cleaned);
1563
+ const len = binaryString.length;
1564
+ const bytes = new Uint8Array(len);
1565
+ for (let i = 0; i < len; i++) bytes[i] = binaryString.charCodeAt(i);
1566
+ return bytes;
1567
+ }
1568
+ /**
1569
+ * Convert Uint8Array to hex string
1570
+ * @param bytes - Uint8Array to convert
1571
+ * @param withPrefix - Whether to include '0x' prefix
1572
+ * @returns Hex string representation
1573
+ */
1574
+ function bytesToHex(bytes, withPrefix = false) {
1575
+ const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
1576
+ return withPrefix ? `0x${hex}` : hex;
1577
+ }
1578
+ /**
1579
+ * Convert string to bytes based on detected encoding (hex or base64)
1580
+ * @param value - The string value to convert
1581
+ * @param encoding - The detected encoding type
1582
+ * @returns Uint8Array representation
1583
+ */
1584
+ function stringToBytes(value, encoding) {
1585
+ switch (encoding) {
1586
+ case "hex": return hexToBytes(value);
1587
+ case "base64": return base64ToBytes(value);
1588
+ default: throw new Error(`Unsupported encoding: ${encoding}. Supported encodings: hex, base64`);
1589
+ }
1590
+ }
1591
+
1592
+ //#endregion
1593
+ //#region src/environment.ts
1594
+ /**
1595
+ * Utility functions for environment detection
1596
+ */
1597
+ /**
1598
+ * Check if the application is running in development or test environment
1599
+ * @returns True if NODE_ENV is 'development' or 'test'
1600
+ */
1601
+ function isDevelopmentOrTestEnvironment() {
1602
+ return process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test";
1603
+ }
1604
+ /**
1605
+ * Check if the application is running in production environment
1606
+ * @returns True if NODE_ENV is 'production'
1607
+ */
1608
+ function isProductionEnvironment() {
1609
+ return process.env.NODE_ENV === "production";
1610
+ }
1611
+
1612
+ //#endregion
1613
+ //#region src/RouterService.ts
1614
+ /**
1615
+ * Default implementation that relies on the browser Location API.
1616
+ * The builder app can replace this with a router-bound implementation if needed.
1617
+ */
1618
+ var BrowserRouterService = class {
1619
+ currentLocation() {
1620
+ if (typeof window === "undefined") return "";
1621
+ return window.location.href;
1622
+ }
1623
+ getParam(name) {
1624
+ if (typeof window === "undefined") return null;
1625
+ return new URLSearchParams(window.location.search).get(name);
1626
+ }
1627
+ navigate(path) {
1628
+ if (typeof window === "undefined") return;
1629
+ if (path.startsWith("http://") || path.startsWith("https://")) window.location.assign(path);
1630
+ else {
1631
+ const url = new URL(path, window.location.origin);
1632
+ window.history.pushState({}, "", url.toString());
1633
+ window.dispatchEvent(new PopStateEvent("popstate"));
1634
+ }
1635
+ }
1636
+ };
1637
+ /**
1638
+ * Singleton instance for global consumption.
1639
+ */
1640
+ const routerService = new BrowserRouterService();
1641
+
1642
+ //#endregion
1643
+ //#region src/AnalyticsService.ts
1644
+ /**
1645
+ * Google Analytics service for tracking user interactions.
1646
+ * Manages Google Analytics initialization and event tracking.
1647
+ * Only active when the analytics_enabled feature flag is true.
1648
+ *
1649
+ * This is a generic service that provides core analytics functionality.
1650
+ * App-specific tracking methods should be implemented in app-level hooks
1651
+ * that use the generic `trackEvent` method.
1652
+ *
1653
+ * @example
1654
+ * ```typescript
1655
+ * // Initialize analytics (typically done once at app startup)
1656
+ * AnalyticsService.initialize('G-XXXXXXXXXX');
1657
+ *
1658
+ * // Track a custom event
1659
+ * AnalyticsService.trackEvent('button_clicked', { button_name: 'submit' });
1660
+ *
1661
+ * // Track page view
1662
+ * AnalyticsService.trackPageView('Dashboard', '/dashboard');
1663
+ * ```
1664
+ */
1665
+ var AnalyticsService = class {
1666
+ static initialized = false;
1667
+ /**
1668
+ * Initialize Google Analytics
1669
+ * @param tagId - Google Analytics tag ID (e.g., G-N3DZK5FCT1)
1670
+ */
1671
+ static initialize(tagId) {
1672
+ if (!tagId) {
1673
+ logger.warn("AnalyticsService", "No tag ID provided");
1674
+ return;
1675
+ }
1676
+ if (!this.isEnabled()) {
1677
+ logger.info("AnalyticsService", "Analytics is disabled via feature flag");
1678
+ return;
1679
+ }
1680
+ if (this.initialized) {
1681
+ logger.info("AnalyticsService", "Already initialized");
1682
+ return;
1683
+ }
1684
+ try {
1685
+ this.loadGtagScript(tagId);
1686
+ this.initializeGtag(tagId);
1687
+ this.initialized = true;
1688
+ logger.info("AnalyticsService", "Initialized successfully");
1689
+ } catch (error) {
1690
+ logger.error("AnalyticsService", "Failed to initialize:", error);
1691
+ }
1692
+ }
1693
+ /**
1694
+ * Check if analytics is enabled via feature flag
1695
+ */
1696
+ static isEnabled() {
1697
+ return appConfigService.isFeatureEnabled("analytics_enabled");
1698
+ }
1699
+ /**
1700
+ * Reset the analytics service state (primarily for testing)
1701
+ */
1702
+ static reset() {
1703
+ this.initialized = false;
1704
+ }
1705
+ /**
1706
+ * Generic event tracking method.
1707
+ * Use this to track any custom event with arbitrary parameters.
1708
+ *
1709
+ * @param eventName - Name of the event (e.g., 'button_clicked', 'form_submitted')
1710
+ * @param parameters - Key-value pairs of event parameters
1711
+ *
1712
+ * @example
1713
+ * ```typescript
1714
+ * AnalyticsService.trackEvent('ecosystem_selected', { ecosystem: 'evm' });
1715
+ * AnalyticsService.trackEvent('wizard_step', { step_number: 2, step_name: 'configure' });
1716
+ * ```
1717
+ */
1718
+ static trackEvent(eventName, parameters) {
1719
+ if (!this.isEnabled()) return;
1720
+ try {
1721
+ if (typeof window.gtag === "function") window.gtag("event", eventName, parameters);
1722
+ else logger.warn("AnalyticsService", "gtag is not available");
1723
+ } catch (error) {
1724
+ logger.error("AnalyticsService", `Failed to track event '${eventName}':`, error);
1725
+ }
1726
+ }
1727
+ /**
1728
+ * Track page view event.
1729
+ * Common event shared across all apps.
1730
+ *
1731
+ * @param pageName - Human-readable name of the page
1732
+ * @param pagePath - URL path of the page
1733
+ *
1734
+ * @example
1735
+ * ```typescript
1736
+ * AnalyticsService.trackPageView('Dashboard', '/dashboard');
1737
+ * ```
1738
+ */
1739
+ static trackPageView(pageName, pagePath) {
1740
+ this.trackEvent("page_view", {
1741
+ page_title: pageName,
1742
+ page_path: pagePath
1743
+ });
1744
+ }
1745
+ /**
1746
+ * Track network selection event.
1747
+ * Common event shared across all apps that involve network selection.
1748
+ *
1749
+ * @param networkId - Selected network ID
1750
+ * @param ecosystem - Ecosystem the network belongs to (e.g., 'evm', 'stellar')
1751
+ *
1752
+ * @example
1753
+ * ```typescript
1754
+ * AnalyticsService.trackNetworkSelection('ethereum-mainnet', 'evm');
1755
+ * ```
1756
+ */
1757
+ static trackNetworkSelection(networkId, ecosystem) {
1758
+ this.trackEvent("network_selected", {
1759
+ network_id: networkId,
1760
+ ecosystem
1761
+ });
1762
+ }
1763
+ /**
1764
+ * Load the Google Analytics gtag script
1765
+ * @private
1766
+ */
1767
+ static loadGtagScript(tagId) {
1768
+ if (document.querySelector(`script[src*="gtag/js?id=${tagId}"]`)) return;
1769
+ if (!window.dataLayer) window.dataLayer = [];
1770
+ window.gtag = window.gtag || function gtag() {
1771
+ window.dataLayer.push(arguments);
1772
+ };
1773
+ const script = document.createElement("script");
1774
+ script.async = true;
1775
+ script.src = `https://www.googletagmanager.com/gtag/js?id=${tagId}`;
1776
+ document.head.appendChild(script);
1777
+ }
1778
+ /**
1779
+ * Initialize gtag with configuration
1780
+ * @private
1781
+ */
1782
+ static initializeGtag(tagId) {
1783
+ if (typeof window.gtag === "function") {
1784
+ window.gtag("js", /* @__PURE__ */ new Date());
1785
+ window.gtag("config", tagId);
1786
+ }
1787
+ }
1788
+ };
1789
+
1790
+ //#endregion
1791
+ //#region src/deepLink.ts
1792
+ /**
1793
+ * Parses URL query parameters into a key-value object.
1794
+ * @returns Object containing all URL query parameters
1795
+ */
1796
+ function parseDeepLink() {
1797
+ const params = new URLSearchParams(window.location.search);
1798
+ const result = {};
1799
+ params.forEach((value, key) => {
1800
+ result[key] = value;
1801
+ });
1802
+ return result;
1803
+ }
1804
+ /**
1805
+ * Gets the forced service from deep link parameters.
1806
+ * @param params - Deep link parameters object
1807
+ * @returns Service name if specified, null otherwise
1808
+ */
1809
+ function getForcedService(params) {
1810
+ return params.service ?? null;
1811
+ }
1812
+ /**
1813
+ * Computes the effective provider preference based on priority order.
1814
+ * @param input - Configuration object with provider options
1815
+ * @returns The effective provider and its source
1816
+ */
1817
+ function computeEffectiveProviderPreference(input) {
1818
+ if (input.forcedService && input.forcedService.length > 0) return {
1819
+ effectiveProvider: input.forcedService,
1820
+ source: "urlForced"
1821
+ };
1822
+ if (input.uiSelection && input.uiSelection.length > 0) return {
1823
+ effectiveProvider: input.uiSelection,
1824
+ source: "ui"
1825
+ };
1826
+ if (input.appDefault && input.appDefault.length > 0) return {
1827
+ effectiveProvider: input.appDefault,
1828
+ source: "appConfig"
1829
+ };
1830
+ return {
1831
+ effectiveProvider: input.adapterDefaultOrder[0],
1832
+ source: "adapterDefault"
1833
+ };
1834
+ }
1835
+
1836
+ //#endregion
1837
+ //#region src/sanitize.ts
1838
+ /**
1839
+ * Minimal HTML sanitizer for client-side rendering of adapter-provided notes.
1840
+ *
1841
+ * - Strips <script>/<style> blocks and closing tags
1842
+ * - Removes inline event handlers (on*)
1843
+ * - Neutralizes javascript: URLs in href/src
1844
+ * - Whitelists a small set of tags: a,b,strong,i,em,code,br,ul,ol,li,p
1845
+ *
1846
+ * This utility is intentionally small and dependency-free. If we decide to
1847
+ * allow richer HTML, we can swap this implementation with a vetted library
1848
+ * (e.g., DOMPurify) behind the same function signature.
1849
+ */
1850
+ function sanitizeHtml(html) {
1851
+ if (!html) return "";
1852
+ let out = html.replace(/<\/(?:script|style)>/gi, "").replace(/<(?:script|style)[\s\S]*?>[\s\S]*?<\/(?:script|style)>/gi, "");
1853
+ out = out.replace(/\son[a-z]+\s*=\s*"[^"]*"/gi, "");
1854
+ out = out.replace(/\son[a-z]+\s*=\s*'[^']*'/gi, "");
1855
+ out = out.replace(/\son[a-z]+\s*=\s*[^\s>]+/gi, "");
1856
+ out = out.replace(/(href|src)\s*=\s*"javascript:[^"]*"/gi, "$1=\"#\"");
1857
+ out = out.replace(/(href|src)\s*=\s*'javascript:[^']*'/gi, "$1=\"#\"");
1858
+ out = out.replace(/<(?!\/?(?:a|b|strong|i|em|code|br|ul|ol|li|p)\b)[^>]*>/gi, "");
1859
+ return out;
1860
+ }
1861
+
1862
+ //#endregion
1863
+ //#region src/access/snapshot.ts
1864
+ /**
1865
+ * Validates an access snapshot structure
1866
+ * @param snapshot The snapshot to validate
1867
+ * @returns True if valid, false otherwise
1868
+ */
1869
+ function validateSnapshot(snapshot) {
1870
+ if (!snapshot || typeof snapshot !== "object") return false;
1871
+ if (!Array.isArray(snapshot.roles)) return false;
1872
+ const roleIds = /* @__PURE__ */ new Set();
1873
+ for (const roleAssignment of snapshot.roles) {
1874
+ if (!roleAssignment || typeof roleAssignment !== "object") return false;
1875
+ if (!roleAssignment.role || typeof roleAssignment.role !== "object") return false;
1876
+ const roleId = roleAssignment.role.id;
1877
+ if (!roleId || typeof roleId !== "string" || roleId.trim() === "") return false;
1878
+ if (roleIds.has(roleId)) return false;
1879
+ roleIds.add(roleId);
1880
+ if (!Array.isArray(roleAssignment.members)) return false;
1881
+ const memberSet = /* @__PURE__ */ new Set();
1882
+ for (const member of roleAssignment.members) {
1883
+ if (typeof member !== "string" || member.trim() === "") return false;
1884
+ if (memberSet.has(member)) return false;
1885
+ memberSet.add(member);
1886
+ }
1887
+ }
1888
+ if (snapshot.ownership !== void 0) {
1889
+ if (!snapshot.ownership || typeof snapshot.ownership !== "object") return false;
1890
+ if (snapshot.ownership.owner !== null && (typeof snapshot.ownership.owner !== "string" || snapshot.ownership.owner.trim() === "")) return false;
1891
+ }
1892
+ return true;
1893
+ }
1894
+ /**
1895
+ * Serializes an access snapshot to JSON string
1896
+ * @param snapshot The snapshot to serialize
1897
+ * @returns JSON string representation
1898
+ * @throws Error if snapshot is invalid
1899
+ */
1900
+ function serializeSnapshot(snapshot) {
1901
+ if (!validateSnapshot(snapshot)) throw new Error("Invalid snapshot structure");
1902
+ return JSON.stringify(snapshot, null, 2);
1903
+ }
1904
+ /**
1905
+ * Deserializes a JSON string to an access snapshot
1906
+ * @param json The JSON string to deserialize
1907
+ * @returns Access snapshot object
1908
+ * @throws Error if JSON is invalid or snapshot structure is invalid
1909
+ */
1910
+ function deserializeSnapshot(json) {
1911
+ let parsed;
1912
+ try {
1913
+ parsed = JSON.parse(json);
1914
+ } catch (error) {
1915
+ throw new Error(`Invalid JSON: ${error instanceof Error ? error.message : "Unknown error"}`);
1916
+ }
1917
+ if (!validateSnapshot(parsed)) throw new Error("Invalid snapshot structure after deserialization");
1918
+ return parsed;
1919
+ }
1920
+ /**
1921
+ * Creates an empty snapshot
1922
+ * @returns Empty snapshot with no roles and no ownership
1923
+ */
1924
+ function createEmptySnapshot() {
1925
+ return { roles: [] };
1926
+ }
1927
+ /**
1928
+ * Finds a role assignment by role ID
1929
+ * @param snapshot The snapshot to search
1930
+ * @param roleId The role ID to find
1931
+ * @returns The role assignment if found, undefined otherwise
1932
+ */
1933
+ function findRoleAssignment(snapshot, roleId) {
1934
+ return snapshot.roles.find((assignment) => assignment.role.id === roleId);
1935
+ }
1936
+ /**
1937
+ * Checks if a snapshot has any roles
1938
+ * @param snapshot The snapshot to check
1939
+ * @returns True if snapshot has at least one role assignment
1940
+ */
1941
+ function hasRoles(snapshot) {
1942
+ return snapshot.roles.length > 0;
1943
+ }
1944
+ /**
1945
+ * Checks if a snapshot has ownership information
1946
+ * @param snapshot The snapshot to check
1947
+ * @returns True if snapshot has ownership information
1948
+ */
1949
+ function hasOwnership(snapshot) {
1950
+ return snapshot.ownership !== void 0 && snapshot.ownership !== null;
1951
+ }
1952
+ /**
1953
+ * Gets the total number of role members across all roles
1954
+ * @param snapshot The snapshot to count
1955
+ * @returns Total number of unique members across all roles
1956
+ */
1957
+ function getTotalMemberCount(snapshot) {
1958
+ const allMembers = /* @__PURE__ */ new Set();
1959
+ for (const roleAssignment of snapshot.roles) for (const member of roleAssignment.members) allMembers.add(member);
1960
+ return allMembers.size;
1961
+ }
1962
+ /**
1963
+ * Gets all unique members across all roles
1964
+ * @param snapshot The snapshot to extract members from
1965
+ * @returns Array of unique member addresses
1966
+ */
1967
+ function getAllMembers(snapshot) {
1968
+ const allMembers = /* @__PURE__ */ new Set();
1969
+ for (const roleAssignment of snapshot.roles) for (const member of roleAssignment.members) allMembers.add(member);
1970
+ return Array.from(allMembers);
1971
+ }
1972
+ /**
1973
+ * Compares two snapshots and returns differences
1974
+ * @param snapshot1 First snapshot
1975
+ * @param snapshot2 Second snapshot
1976
+ * @returns Object describing differences
1977
+ */
1978
+ function compareSnapshots(snapshot1, snapshot2) {
1979
+ const rolesAdded = [];
1980
+ const rolesRemoved = [];
1981
+ const rolesModified = [];
1982
+ const roleMap1 = /* @__PURE__ */ new Map();
1983
+ const roleMap2 = /* @__PURE__ */ new Map();
1984
+ for (const assignment of snapshot1.roles) roleMap1.set(assignment.role.id, assignment);
1985
+ for (const assignment of snapshot2.roles) roleMap2.set(assignment.role.id, assignment);
1986
+ for (const [roleId, assignment] of roleMap2) if (!roleMap1.has(roleId)) rolesAdded.push(assignment);
1987
+ for (const [roleId, assignment] of roleMap1) if (!roleMap2.has(roleId)) rolesRemoved.push(assignment);
1988
+ for (const [roleId, assignment1] of roleMap1) {
1989
+ const assignment2 = roleMap2.get(roleId);
1990
+ if (assignment2) {
1991
+ const members1 = new Set(assignment1.members);
1992
+ const members2 = new Set(assignment2.members);
1993
+ const membersAdded = assignment2.members.filter((m) => !members1.has(m));
1994
+ const membersRemoved = assignment1.members.filter((m) => !members2.has(m));
1995
+ if (membersAdded.length > 0 || membersRemoved.length > 0) rolesModified.push({
1996
+ role: assignment1.role,
1997
+ membersAdded,
1998
+ membersRemoved
1999
+ });
2000
+ }
2001
+ }
2002
+ return {
2003
+ rolesAdded,
2004
+ rolesRemoved,
2005
+ rolesModified,
2006
+ ownershipChanged: snapshot1.ownership?.owner !== snapshot2.ownership?.owner
2007
+ };
2008
+ }
2009
+
2010
+ //#endregion
2011
+ //#region src/access/errors.ts
2012
+ /**
2013
+ * Type guard to check if an error is an AccessControlError
2014
+ *
2015
+ * @param error The error to check
2016
+ * @returns True if the error has the AccessControlError structure
2017
+ *
2018
+ * @example
2019
+ * ```typescript
2020
+ * try {
2021
+ * await service.grantRole(...);
2022
+ * } catch (error) {
2023
+ * if (isAccessControlError(error)) {
2024
+ * console.log('Access control error:', error.contractAddress);
2025
+ * }
2026
+ * }
2027
+ * ```
2028
+ */
2029
+ function isAccessControlError(error) {
2030
+ return error instanceof Error && "contractAddress" in error && (typeof error.contractAddress === "string" || error.contractAddress === void 0);
2031
+ }
2032
+ /**
2033
+ * Helper to safely extract error message from unknown error type
2034
+ *
2035
+ * @param error The error to extract message from
2036
+ * @returns The error message string
2037
+ *
2038
+ * @example
2039
+ * ```typescript
2040
+ * try {
2041
+ * await someOperation();
2042
+ * } catch (error) {
2043
+ * const message = getErrorMessage(error);
2044
+ * logger.error('Operation failed:', message);
2045
+ * }
2046
+ * ```
2047
+ */
2048
+ function getErrorMessage(error) {
2049
+ if (error instanceof Error) return error.message;
2050
+ return String(error);
2051
+ }
2052
+ /**
2053
+ * Helper to create a user-friendly error message with full context
2054
+ *
2055
+ * This function formats an AccessControlError into a readable multi-line message
2056
+ * that includes all relevant context (contract address, roles, operations, etc.).
2057
+ *
2058
+ * @param error The AccessControlError to format
2059
+ * @returns Formatted error message string with all context
2060
+ *
2061
+ * @example
2062
+ * ```typescript
2063
+ * import { PermissionDenied } from '@openzeppelin/ui-types';
2064
+ * import { formatAccessControlError } from '@openzeppelin/ui-utils';
2065
+ *
2066
+ * try {
2067
+ * await service.grantRole(...);
2068
+ * } catch (error) {
2069
+ * if (error instanceof PermissionDenied) {
2070
+ * const formatted = formatAccessControlError(error);
2071
+ * showErrorToUser(formatted);
2072
+ * }
2073
+ * }
2074
+ * ```
2075
+ *
2076
+ * Output format:
2077
+ * ```
2078
+ * [ErrorName] Error message
2079
+ * Contract: 0x123...
2080
+ * [Additional context based on error type]
2081
+ * ```
2082
+ */
2083
+ function formatAccessControlError(error) {
2084
+ let message = `[${error.name}] ${error.message}`;
2085
+ if (error.contractAddress) message += `\nContract: ${error.contractAddress}`;
2086
+ const errorWithProperties = error;
2087
+ if (error.name === "PermissionDenied") {
2088
+ if (errorWithProperties.requiredRole) message += `\nRequired Role: ${errorWithProperties.requiredRole}`;
2089
+ if (errorWithProperties.callerAddress) message += `\nCaller: ${errorWithProperties.callerAddress}`;
2090
+ } else if (error.name === "IndexerUnavailable") {
2091
+ if (errorWithProperties.networkId) message += `\nNetwork: ${errorWithProperties.networkId}`;
2092
+ if (errorWithProperties.endpointUrl) message += `\nEndpoint: ${errorWithProperties.endpointUrl}`;
2093
+ } else if (error.name === "ConfigurationInvalid") {
2094
+ if (errorWithProperties.configField) message += `\nInvalid Field: ${errorWithProperties.configField}`;
2095
+ if (errorWithProperties.providedValue !== void 0) message += `\nProvided Value: ${JSON.stringify(errorWithProperties.providedValue)}`;
2096
+ } else if (error.name === "OperationFailed") {
2097
+ if (errorWithProperties.operation) message += `\nOperation: ${errorWithProperties.operation}`;
2098
+ if (errorWithProperties.cause) message += `\nCause: ${errorWithProperties.cause.message}`;
2099
+ } else if (error.name === "UnsupportedContractFeatures") {
2100
+ if (errorWithProperties.missingFeatures && Array.isArray(errorWithProperties.missingFeatures)) message += `\nMissing Features: ${errorWithProperties.missingFeatures.join(", ")}`;
2101
+ }
2102
+ return message;
2103
+ }
2104
+
2105
+ //#endregion
2106
+ exports.AnalyticsService = AnalyticsService;
2107
+ exports.AppConfigService = AppConfigService;
2108
+ exports.DEFAULT_CONCURRENCY_LIMIT = DEFAULT_CONCURRENCY_LIMIT;
2109
+ exports.UserExplorerConfigService = UserExplorerConfigService;
2110
+ exports.UserNetworkServiceConfigService = UserNetworkServiceConfigService;
2111
+ exports.UserRpcConfigService = UserRpcConfigService;
2112
+ exports.addressesEqual = addressesEqual;
2113
+ exports.appConfigService = appConfigService;
2114
+ exports.base64ToBytes = base64ToBytes;
2115
+ exports.buildRequiredInputSnapshot = buildRequiredInputSnapshot;
2116
+ exports.bytesToHex = bytesToHex;
2117
+ exports.cn = cn;
2118
+ exports.compareSnapshots = compareSnapshots;
2119
+ exports.computeEffectiveProviderPreference = computeEffectiveProviderPreference;
2120
+ exports.createEmptySnapshot = createEmptySnapshot;
2121
+ exports.delay = delay;
2122
+ exports.deserializeSnapshot = deserializeSnapshot;
2123
+ exports.detectBytesEncoding = detectBytesEncoding;
2124
+ exports.enhanceNumericValidation = enhanceNumericValidation;
2125
+ exports.findRoleAssignment = findRoleAssignment;
2126
+ exports.formatAccessControlError = formatAccessControlError;
2127
+ exports.formatTimestamp = formatTimestamp;
2128
+ exports.generateId = generateId;
2129
+ exports.getAllMembers = getAllMembers;
2130
+ exports.getBytesSize = getBytesSize;
2131
+ exports.getDefaultValueForType = getDefaultValueForType;
2132
+ exports.getErrorMessage = getErrorMessage;
2133
+ exports.getForcedService = getForcedService;
2134
+ exports.getInvalidUrlMessage = getInvalidUrlMessage;
2135
+ exports.getMissingRequiredContractInputs = getMissingRequiredContractInputs;
2136
+ exports.getTotalMemberCount = getTotalMemberCount;
2137
+ exports.hasMissingRequiredContractInputs = hasMissingRequiredContractInputs;
2138
+ exports.hasOwnership = hasOwnership;
2139
+ exports.hasRoles = hasRoles;
2140
+ exports.hexToBytes = hexToBytes;
2141
+ exports.isAccessControlError = isAccessControlError;
2142
+ exports.isDevelopmentOrTestEnvironment = isDevelopmentOrTestEnvironment;
2143
+ exports.isPlainObject = isPlainObject;
2144
+ exports.isProductionEnvironment = isProductionEnvironment;
2145
+ exports.isRecordWithProperties = isRecordWithProperties;
2146
+ exports.isValidUrl = isValidUrl;
2147
+ exports.logger = logger;
2148
+ exports.normalizeAddress = normalizeAddress;
2149
+ exports.parseDeepLink = parseDeepLink;
2150
+ exports.promiseAllSettledWithLimit = promiseAllSettledWithLimit;
2151
+ exports.promiseAllWithLimit = promiseAllWithLimit;
2152
+ exports.rateLimitedBatch = rateLimitedBatch;
2153
+ exports.requiredSnapshotsEqual = requiredSnapshotsEqual;
2154
+ exports.routerService = routerService;
2155
+ exports.sanitizeHtml = sanitizeHtml;
2156
+ exports.serializeSnapshot = serializeSnapshot;
2157
+ exports.simpleHash = simpleHash;
2158
+ exports.stringToBytes = stringToBytes;
2159
+ exports.truncateMiddle = truncateMiddle;
2160
+ exports.userExplorerConfigService = userExplorerConfigService;
2161
+ exports.userNetworkServiceConfigService = userNetworkServiceConfigService;
2162
+ exports.userRpcConfigService = userRpcConfigService;
2163
+ exports.validateBytes = validateBytes;
2164
+ exports.validateBytesSimple = validateBytesSimple;
2165
+ exports.validateSnapshot = validateSnapshot;
2166
+ exports.withTimeout = withTimeout;
2167
+ //# sourceMappingURL=index.cjs.map