@provex/components 1.2.3

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.
@@ -0,0 +1,237 @@
1
+ import { Hex } from 'viem';
2
+ import { ChainId } from '@provex/utils/chain';
3
+
4
+ /**
5
+ * @fileoverview URL state management hook for React Router.
6
+ *
7
+ * Provides a type-safe way to manage URL query parameters as application state.
8
+ * The URL is the single source of truth - no useState needed.
9
+ *
10
+ * @remarks
11
+ * **Key Design Principles:**
12
+ * - URL is the source of truth (no bi-directional sync)
13
+ * - Type-safe parsing and serialization
14
+ * - Built-in parsers for common types (bigint, hex, chainId, boolean)
15
+ * - Preserves other URL params when updating
16
+ *
17
+ * **When to Use:**
18
+ * - Filter state that should persist in URL (currencies, payment methods)
19
+ * - Page state that should be shareable (deposit ID, chain)
20
+ * - Replacing useState + useEffect URL sync anti-patterns
21
+ *
22
+ * @see MultiSelectFilter for a component that uses this hook pattern
23
+ */
24
+
25
+ /**
26
+ * Configuration for a single URL parameter managed by useUrlState.
27
+ *
28
+ * @template T - The TypeScript type of the parsed value
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * // Simple string param
33
+ * const providerConfig: UrlParamConfig<string> = {
34
+ * key: 'provider',
35
+ * defaultValue: 'venmo',
36
+ * }
37
+ *
38
+ * // Param with custom parse/serialize
39
+ * const pageConfig: UrlParamConfig<number> = {
40
+ * key: 'page',
41
+ * defaultValue: 1,
42
+ * parse: (raw) => parseInt(raw, 10),
43
+ * serialize: (val) => String(val),
44
+ * }
45
+ * ```
46
+ */
47
+ interface UrlParamConfig<T> {
48
+ /** The URL query parameter key (e.g., "provider", "currency", "page") */
49
+ key: string;
50
+ /** Default value when the URL param is missing or invalid */
51
+ defaultValue: T;
52
+ /**
53
+ * Custom parser to transform URL string to typed value.
54
+ * If not provided, the raw string is returned (for string types).
55
+ * Return undefined to fall back to defaultValue.
56
+ */
57
+ parse?: (raw: string) => T | undefined;
58
+ /**
59
+ * Custom serializer to transform typed value to URL string.
60
+ * If not provided, String(value) is used.
61
+ */
62
+ serialize?: (value: T) => string;
63
+ }
64
+ /**
65
+ * Type helper to extract the value type from a UrlParamConfig.
66
+ */
67
+ type ConfigValue<C> = C extends UrlParamConfig<infer T> ? T : never;
68
+ /**
69
+ * Type helper to create the state object type from a config record.
70
+ * Maps each config key to its parsed value type.
71
+ */
72
+ type UrlStateValues<C extends Record<string, UrlParamConfig<unknown>>> = {
73
+ [K in keyof C]: ConfigValue<C[K]>;
74
+ };
75
+ /**
76
+ * Return type of useUrlState hook.
77
+ * Includes all state values plus a setParam function for updates.
78
+ */
79
+ interface UseUrlStateResult<C extends Record<string, UrlParamConfig<unknown>>> {
80
+ /** Current state values derived from URL params */
81
+ state: UrlStateValues<C>;
82
+ /**
83
+ * Update a single URL parameter.
84
+ * This updates the URL which triggers a re-render with new state.
85
+ *
86
+ * @param key - The config key (not the URL param key)
87
+ * @param value - The new value to set
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * const { state, setParam } = useUrlState(config)
92
+ * setParam('provider', 'revolut') // Updates ?provider=revolut
93
+ * ```
94
+ */
95
+ setParam: <K extends keyof C>(key: K, value: ConfigValue<C[K]>) => void;
96
+ /** The raw URL hash (without the # prefix), or null if none */
97
+ hash: string | null;
98
+ /** The raw search params object for advanced use cases */
99
+ searchParams: URLSearchParams;
100
+ }
101
+ /**
102
+ * Built-in parsers for common URL parameter types.
103
+ * Use these with the `parse` option in UrlParamConfig.
104
+ *
105
+ * @example
106
+ * ```ts
107
+ * const config = {
108
+ * chainId: {
109
+ * key: 'chain',
110
+ * defaultValue: 8453 as ChainId,
111
+ * parse: urlParsers.chainId,
112
+ * serialize: urlSerializers.chainId,
113
+ * },
114
+ * }
115
+ * ```
116
+ */
117
+ declare const urlParsers: {
118
+ /**
119
+ * Parse a chain name or ID to ChainId.
120
+ * Accepts: "base", "pulsechain", "8453", "369"
121
+ */
122
+ chainId: (raw: string) => ChainId | undefined;
123
+ /**
124
+ * Parse a string to bigint.
125
+ * Returns undefined if not a valid integer string.
126
+ */
127
+ bigint: (raw: string) => bigint | undefined;
128
+ /**
129
+ * Parse a string to number.
130
+ * Returns undefined if not a valid number.
131
+ */
132
+ number: (raw: string) => number | undefined;
133
+ /**
134
+ * Parse a string to integer.
135
+ * Returns undefined if not a valid integer.
136
+ */
137
+ integer: (raw: string) => number | undefined;
138
+ /**
139
+ * Parse a hex string (validates 0x prefix).
140
+ * Returns undefined if not a valid hex string.
141
+ */
142
+ hex: (raw: string) => Hex | undefined;
143
+ /**
144
+ * Parse a boolean string.
145
+ * Accepts: "true", "false", "1", "0"
146
+ */
147
+ boolean: (raw: string) => boolean | undefined;
148
+ /**
149
+ * Parse an uppercase string (e.g., for currency tickers).
150
+ */
151
+ uppercase: (raw: string) => string;
152
+ /**
153
+ * Parse a lowercase string (e.g., for provider keys).
154
+ */
155
+ lowercase: (raw: string) => string;
156
+ };
157
+ /**
158
+ * Built-in serializers for common URL parameter types.
159
+ * Use these with the `serialize` option in UrlParamConfig.
160
+ */
161
+ declare const urlSerializers: {
162
+ /**
163
+ * Serialize ChainId to chain name.
164
+ */
165
+ chainId: (chainId: ChainId) => string;
166
+ /**
167
+ * Serialize bigint to string.
168
+ */
169
+ bigint: (value: bigint) => string;
170
+ /**
171
+ * Serialize boolean to string.
172
+ */
173
+ boolean: (value: boolean) => string;
174
+ };
175
+ /**
176
+ * Hook for managing multiple URL query parameters as typed state.
177
+ * Uses URL as the single source of truth - no internal useState.
178
+ *
179
+ * **Key benefits:**
180
+ * - No bi-directional state sync (URL is the only source of truth)
181
+ * - Type-safe parsing and serialization
182
+ * - Preserves other URL params when updating
183
+ * - Shareable URLs with full state
184
+ * - Access to URL hash fragment
185
+ * - Built-in parsers for common types
186
+ *
187
+ * **When to use:**
188
+ * - UI state that should persist in the URL (filters, selections, pagination)
189
+ * - State that should be shareable via URL
190
+ * - Replacing useState + useEffect URL sync anti-patterns
191
+ *
192
+ * @template C - Record of config objects keyed by state property names
193
+ * @param config - Configuration object mapping state keys to URL param configs
194
+ * @returns Object with current state values, setParam function, hash, and raw searchParams
195
+ *
196
+ * @example
197
+ * ```tsx
198
+ * // Define config outside component (stable reference)
199
+ * const depositDetailsConfig = {
200
+ * depositId: {
201
+ * key: 'id',
202
+ * defaultValue: null as bigint | null,
203
+ * parse: urlParsers.bigint,
204
+ * serialize: urlSerializers.bigint,
205
+ * },
206
+ * chainId: {
207
+ * key: 'chain',
208
+ * defaultValue: 8453 as ChainId,
209
+ * parse: urlParsers.chainId,
210
+ * serialize: urlSerializers.chainId,
211
+ * },
212
+ * escrow: {
213
+ * key: 'escrow',
214
+ * defaultValue: null as Hex | null,
215
+ * parse: urlParsers.hex,
216
+ * },
217
+ * } as const
218
+ *
219
+ * function DepositDetails() {
220
+ * const { state, setParam, hash } = useUrlState(depositDetailsConfig)
221
+ *
222
+ * // state.depositId, state.chainId, state.escrow derived from URL
223
+ * // hash contains any #fragment if present
224
+ *
225
+ * return (
226
+ * <div>
227
+ * <p>Deposit: {state.depositId?.toString()}</p>
228
+ * <p>Chain: {state.chainId}</p>
229
+ * {hash && <p>Section: {hash}</p>}
230
+ * </div>
231
+ * )
232
+ * }
233
+ * ```
234
+ */
235
+ declare function useUrlState<C extends Record<string, UrlParamConfig<unknown>>>(config: C): UseUrlStateResult<C>;
236
+
237
+ export { type UrlParamConfig, type UseUrlStateResult, urlParsers, urlSerializers, useUrlState };
@@ -0,0 +1,237 @@
1
+ import { Hex } from 'viem';
2
+ import { ChainId } from '@provex/utils/chain';
3
+
4
+ /**
5
+ * @fileoverview URL state management hook for React Router.
6
+ *
7
+ * Provides a type-safe way to manage URL query parameters as application state.
8
+ * The URL is the single source of truth - no useState needed.
9
+ *
10
+ * @remarks
11
+ * **Key Design Principles:**
12
+ * - URL is the source of truth (no bi-directional sync)
13
+ * - Type-safe parsing and serialization
14
+ * - Built-in parsers for common types (bigint, hex, chainId, boolean)
15
+ * - Preserves other URL params when updating
16
+ *
17
+ * **When to Use:**
18
+ * - Filter state that should persist in URL (currencies, payment methods)
19
+ * - Page state that should be shareable (deposit ID, chain)
20
+ * - Replacing useState + useEffect URL sync anti-patterns
21
+ *
22
+ * @see MultiSelectFilter for a component that uses this hook pattern
23
+ */
24
+
25
+ /**
26
+ * Configuration for a single URL parameter managed by useUrlState.
27
+ *
28
+ * @template T - The TypeScript type of the parsed value
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * // Simple string param
33
+ * const providerConfig: UrlParamConfig<string> = {
34
+ * key: 'provider',
35
+ * defaultValue: 'venmo',
36
+ * }
37
+ *
38
+ * // Param with custom parse/serialize
39
+ * const pageConfig: UrlParamConfig<number> = {
40
+ * key: 'page',
41
+ * defaultValue: 1,
42
+ * parse: (raw) => parseInt(raw, 10),
43
+ * serialize: (val) => String(val),
44
+ * }
45
+ * ```
46
+ */
47
+ interface UrlParamConfig<T> {
48
+ /** The URL query parameter key (e.g., "provider", "currency", "page") */
49
+ key: string;
50
+ /** Default value when the URL param is missing or invalid */
51
+ defaultValue: T;
52
+ /**
53
+ * Custom parser to transform URL string to typed value.
54
+ * If not provided, the raw string is returned (for string types).
55
+ * Return undefined to fall back to defaultValue.
56
+ */
57
+ parse?: (raw: string) => T | undefined;
58
+ /**
59
+ * Custom serializer to transform typed value to URL string.
60
+ * If not provided, String(value) is used.
61
+ */
62
+ serialize?: (value: T) => string;
63
+ }
64
+ /**
65
+ * Type helper to extract the value type from a UrlParamConfig.
66
+ */
67
+ type ConfigValue<C> = C extends UrlParamConfig<infer T> ? T : never;
68
+ /**
69
+ * Type helper to create the state object type from a config record.
70
+ * Maps each config key to its parsed value type.
71
+ */
72
+ type UrlStateValues<C extends Record<string, UrlParamConfig<unknown>>> = {
73
+ [K in keyof C]: ConfigValue<C[K]>;
74
+ };
75
+ /**
76
+ * Return type of useUrlState hook.
77
+ * Includes all state values plus a setParam function for updates.
78
+ */
79
+ interface UseUrlStateResult<C extends Record<string, UrlParamConfig<unknown>>> {
80
+ /** Current state values derived from URL params */
81
+ state: UrlStateValues<C>;
82
+ /**
83
+ * Update a single URL parameter.
84
+ * This updates the URL which triggers a re-render with new state.
85
+ *
86
+ * @param key - The config key (not the URL param key)
87
+ * @param value - The new value to set
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * const { state, setParam } = useUrlState(config)
92
+ * setParam('provider', 'revolut') // Updates ?provider=revolut
93
+ * ```
94
+ */
95
+ setParam: <K extends keyof C>(key: K, value: ConfigValue<C[K]>) => void;
96
+ /** The raw URL hash (without the # prefix), or null if none */
97
+ hash: string | null;
98
+ /** The raw search params object for advanced use cases */
99
+ searchParams: URLSearchParams;
100
+ }
101
+ /**
102
+ * Built-in parsers for common URL parameter types.
103
+ * Use these with the `parse` option in UrlParamConfig.
104
+ *
105
+ * @example
106
+ * ```ts
107
+ * const config = {
108
+ * chainId: {
109
+ * key: 'chain',
110
+ * defaultValue: 8453 as ChainId,
111
+ * parse: urlParsers.chainId,
112
+ * serialize: urlSerializers.chainId,
113
+ * },
114
+ * }
115
+ * ```
116
+ */
117
+ declare const urlParsers: {
118
+ /**
119
+ * Parse a chain name or ID to ChainId.
120
+ * Accepts: "base", "pulsechain", "8453", "369"
121
+ */
122
+ chainId: (raw: string) => ChainId | undefined;
123
+ /**
124
+ * Parse a string to bigint.
125
+ * Returns undefined if not a valid integer string.
126
+ */
127
+ bigint: (raw: string) => bigint | undefined;
128
+ /**
129
+ * Parse a string to number.
130
+ * Returns undefined if not a valid number.
131
+ */
132
+ number: (raw: string) => number | undefined;
133
+ /**
134
+ * Parse a string to integer.
135
+ * Returns undefined if not a valid integer.
136
+ */
137
+ integer: (raw: string) => number | undefined;
138
+ /**
139
+ * Parse a hex string (validates 0x prefix).
140
+ * Returns undefined if not a valid hex string.
141
+ */
142
+ hex: (raw: string) => Hex | undefined;
143
+ /**
144
+ * Parse a boolean string.
145
+ * Accepts: "true", "false", "1", "0"
146
+ */
147
+ boolean: (raw: string) => boolean | undefined;
148
+ /**
149
+ * Parse an uppercase string (e.g., for currency tickers).
150
+ */
151
+ uppercase: (raw: string) => string;
152
+ /**
153
+ * Parse a lowercase string (e.g., for provider keys).
154
+ */
155
+ lowercase: (raw: string) => string;
156
+ };
157
+ /**
158
+ * Built-in serializers for common URL parameter types.
159
+ * Use these with the `serialize` option in UrlParamConfig.
160
+ */
161
+ declare const urlSerializers: {
162
+ /**
163
+ * Serialize ChainId to chain name.
164
+ */
165
+ chainId: (chainId: ChainId) => string;
166
+ /**
167
+ * Serialize bigint to string.
168
+ */
169
+ bigint: (value: bigint) => string;
170
+ /**
171
+ * Serialize boolean to string.
172
+ */
173
+ boolean: (value: boolean) => string;
174
+ };
175
+ /**
176
+ * Hook for managing multiple URL query parameters as typed state.
177
+ * Uses URL as the single source of truth - no internal useState.
178
+ *
179
+ * **Key benefits:**
180
+ * - No bi-directional state sync (URL is the only source of truth)
181
+ * - Type-safe parsing and serialization
182
+ * - Preserves other URL params when updating
183
+ * - Shareable URLs with full state
184
+ * - Access to URL hash fragment
185
+ * - Built-in parsers for common types
186
+ *
187
+ * **When to use:**
188
+ * - UI state that should persist in the URL (filters, selections, pagination)
189
+ * - State that should be shareable via URL
190
+ * - Replacing useState + useEffect URL sync anti-patterns
191
+ *
192
+ * @template C - Record of config objects keyed by state property names
193
+ * @param config - Configuration object mapping state keys to URL param configs
194
+ * @returns Object with current state values, setParam function, hash, and raw searchParams
195
+ *
196
+ * @example
197
+ * ```tsx
198
+ * // Define config outside component (stable reference)
199
+ * const depositDetailsConfig = {
200
+ * depositId: {
201
+ * key: 'id',
202
+ * defaultValue: null as bigint | null,
203
+ * parse: urlParsers.bigint,
204
+ * serialize: urlSerializers.bigint,
205
+ * },
206
+ * chainId: {
207
+ * key: 'chain',
208
+ * defaultValue: 8453 as ChainId,
209
+ * parse: urlParsers.chainId,
210
+ * serialize: urlSerializers.chainId,
211
+ * },
212
+ * escrow: {
213
+ * key: 'escrow',
214
+ * defaultValue: null as Hex | null,
215
+ * parse: urlParsers.hex,
216
+ * },
217
+ * } as const
218
+ *
219
+ * function DepositDetails() {
220
+ * const { state, setParam, hash } = useUrlState(depositDetailsConfig)
221
+ *
222
+ * // state.depositId, state.chainId, state.escrow derived from URL
223
+ * // hash contains any #fragment if present
224
+ *
225
+ * return (
226
+ * <div>
227
+ * <p>Deposit: {state.depositId?.toString()}</p>
228
+ * <p>Chain: {state.chainId}</p>
229
+ * {hash && <p>Section: {hash}</p>}
230
+ * </div>
231
+ * )
232
+ * }
233
+ * ```
234
+ */
235
+ declare function useUrlState<C extends Record<string, UrlParamConfig<unknown>>>(config: C): UseUrlStateResult<C>;
236
+
237
+ export { type UrlParamConfig, type UseUrlStateResult, urlParsers, urlSerializers, useUrlState };
@@ -0,0 +1,149 @@
1
+ import { useRef, useMemo, useCallback } from 'react';
2
+ import { useSearchParams, useLocation } from 'react-router-dom';
3
+ import { chains } from '@provex/utils/chain';
4
+
5
+ // src/useUrlState.ts
6
+ var urlParsers = {
7
+ /**
8
+ * Parse a chain name or ID to ChainId.
9
+ * Accepts: "base", "pulsechain", "8453", "369"
10
+ */
11
+ chainId: (raw) => {
12
+ const byName = chains.find((c) => c.name.toLowerCase() === raw.toLowerCase());
13
+ if (byName) return byName.id;
14
+ const parsed = parseInt(raw, 10);
15
+ if (!isNaN(parsed)) {
16
+ const byId = chains.find((c) => c.id === parsed);
17
+ if (byId) return byId.id;
18
+ }
19
+ return void 0;
20
+ },
21
+ /**
22
+ * Parse a string to bigint.
23
+ * Returns undefined if not a valid integer string.
24
+ */
25
+ bigint: (raw) => {
26
+ try {
27
+ return BigInt(raw);
28
+ } catch {
29
+ return void 0;
30
+ }
31
+ },
32
+ /**
33
+ * Parse a string to number.
34
+ * Returns undefined if not a valid number.
35
+ */
36
+ number: (raw) => {
37
+ const parsed = Number(raw);
38
+ return isNaN(parsed) ? void 0 : parsed;
39
+ },
40
+ /**
41
+ * Parse a string to integer.
42
+ * Returns undefined if not a valid integer.
43
+ */
44
+ integer: (raw) => {
45
+ const parsed = parseInt(raw, 10);
46
+ return isNaN(parsed) ? void 0 : parsed;
47
+ },
48
+ /**
49
+ * Parse a hex string (validates 0x prefix).
50
+ * Returns undefined if not a valid hex string.
51
+ */
52
+ hex: (raw) => {
53
+ if (raw.startsWith("0x") && /^0x[0-9a-fA-F]*$/.test(raw)) {
54
+ return raw;
55
+ }
56
+ return void 0;
57
+ },
58
+ /**
59
+ * Parse a boolean string.
60
+ * Accepts: "true", "false", "1", "0"
61
+ */
62
+ boolean: (raw) => {
63
+ if (raw === "true" || raw === "1") return true;
64
+ if (raw === "false" || raw === "0") return false;
65
+ return void 0;
66
+ },
67
+ /**
68
+ * Parse an uppercase string (e.g., for currency tickers).
69
+ */
70
+ uppercase: (raw) => raw.toUpperCase(),
71
+ /**
72
+ * Parse a lowercase string (e.g., for provider keys).
73
+ */
74
+ lowercase: (raw) => raw.toLowerCase()
75
+ };
76
+ var urlSerializers = {
77
+ /**
78
+ * Serialize ChainId to chain name.
79
+ */
80
+ chainId: (chainId) => {
81
+ const chain = chains.find((c) => c.id === chainId);
82
+ return chain?.name.toLowerCase() ?? String(chainId);
83
+ },
84
+ /**
85
+ * Serialize bigint to string.
86
+ */
87
+ bigint: (value) => value.toString(),
88
+ /**
89
+ * Serialize boolean to string.
90
+ */
91
+ boolean: (value) => value ? "true" : "false"
92
+ };
93
+ function parseParam(searchParams, config) {
94
+ const raw = searchParams.get(config.key);
95
+ if (raw === null) {
96
+ return config.defaultValue;
97
+ }
98
+ if (config.parse) {
99
+ const parsed = config.parse(raw);
100
+ return parsed !== void 0 ? parsed : config.defaultValue;
101
+ }
102
+ return raw;
103
+ }
104
+ function serializeParam(value, config) {
105
+ if (config.serialize) {
106
+ return config.serialize(value);
107
+ }
108
+ return String(value);
109
+ }
110
+ function useUrlState(config) {
111
+ const [searchParams, setSearchParams] = useSearchParams();
112
+ const location = useLocation();
113
+ const configRef = useRef(config);
114
+ configRef.current = config;
115
+ const hash = useMemo(() => {
116
+ const rawHash = location.hash;
117
+ return rawHash && rawHash.length > 1 ? rawHash.slice(1) : null;
118
+ }, [location.hash]);
119
+ const state = useMemo(() => {
120
+ const result = {};
121
+ for (const [key, paramConfig] of Object.entries(configRef.current)) {
122
+ result[key] = parseParam(
123
+ searchParams,
124
+ paramConfig
125
+ );
126
+ }
127
+ return result;
128
+ }, [searchParams]);
129
+ const setParam = useCallback(
130
+ (key, value) => {
131
+ const paramConfig = configRef.current[key];
132
+ const serialized = serializeParam(value, paramConfig);
133
+ setSearchParams(
134
+ (prev) => {
135
+ const next = new URLSearchParams(prev);
136
+ next.set(paramConfig.key, serialized);
137
+ return next;
138
+ },
139
+ { replace: true }
140
+ );
141
+ },
142
+ [setSearchParams]
143
+ );
144
+ return { state, setParam, hash, searchParams };
145
+ }
146
+
147
+ export { urlParsers, urlSerializers, useUrlState };
148
+ //# sourceMappingURL=useUrlState.js.map
149
+ //# sourceMappingURL=useUrlState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useUrlState.ts"],"names":[],"mappings":"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","file":"useUrlState.js","sourcesContent":["/**\n * @fileoverview URL state management hook for React Router.\n *\n * Provides a type-safe way to manage URL query parameters as application state.\n * The URL is the single source of truth - no useState needed.\n *\n * @remarks\n * **Key Design Principles:**\n * - URL is the source of truth (no bi-directional sync)\n * - Type-safe parsing and serialization\n * - Built-in parsers for common types (bigint, hex, chainId, boolean)\n * - Preserves other URL params when updating\n *\n * **When to Use:**\n * - Filter state that should persist in URL (currencies, payment methods)\n * - Page state that should be shareable (deposit ID, chain)\n * - Replacing useState + useEffect URL sync anti-patterns\n *\n * @see MultiSelectFilter for a component that uses this hook pattern\n */\n\nimport { useCallback, useMemo, useRef } from 'react'\nimport { useSearchParams, useLocation } from 'react-router-dom'\nimport type { Hex } from 'viem'\nimport { type ChainId, chains } from '@provex/utils/chain'\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Configuration for a single URL parameter managed by useUrlState.\n *\n * @template T - The TypeScript type of the parsed value\n *\n * @example\n * ```ts\n * // Simple string param\n * const providerConfig: UrlParamConfig<string> = {\n * key: 'provider',\n * defaultValue: 'venmo',\n * }\n *\n * // Param with custom parse/serialize\n * const pageConfig: UrlParamConfig<number> = {\n * key: 'page',\n * defaultValue: 1,\n * parse: (raw) => parseInt(raw, 10),\n * serialize: (val) => String(val),\n * }\n * ```\n */\nexport interface UrlParamConfig<T> {\n /** The URL query parameter key (e.g., \"provider\", \"currency\", \"page\") */\n key: string\n /** Default value when the URL param is missing or invalid */\n defaultValue: T\n /**\n * Custom parser to transform URL string to typed value.\n * If not provided, the raw string is returned (for string types).\n * Return undefined to fall back to defaultValue.\n */\n parse?: (raw: string) => T | undefined\n /**\n * Custom serializer to transform typed value to URL string.\n * If not provided, String(value) is used.\n */\n serialize?: (value: T) => string\n}\n\n/**\n * Type helper to extract the value type from a UrlParamConfig.\n */\ntype ConfigValue<C> = C extends UrlParamConfig<infer T> ? T : never\n\n/**\n * Type helper to create the state object type from a config record.\n * Maps each config key to its parsed value type.\n */\ntype UrlStateValues<C extends Record<string, UrlParamConfig<unknown>>> = {\n [K in keyof C]: ConfigValue<C[K]>\n}\n\n/**\n * Return type of useUrlState hook.\n * Includes all state values plus a setParam function for updates.\n */\nexport interface UseUrlStateResult<C extends Record<string, UrlParamConfig<unknown>>> {\n /** Current state values derived from URL params */\n state: UrlStateValues<C>\n /**\n * Update a single URL parameter.\n * This updates the URL which triggers a re-render with new state.\n *\n * @param key - The config key (not the URL param key)\n * @param value - The new value to set\n *\n * @example\n * ```ts\n * const { state, setParam } = useUrlState(config)\n * setParam('provider', 'revolut') // Updates ?provider=revolut\n * ```\n */\n setParam: <K extends keyof C>(key: K, value: ConfigValue<C[K]>) => void\n /** The raw URL hash (without the # prefix), or null if none */\n hash: string | null\n /** The raw search params object for advanced use cases */\n searchParams: URLSearchParams\n}\n\n// =============================================================================\n// Built-in Parsers\n// =============================================================================\n\n/**\n * Built-in parsers for common URL parameter types.\n * Use these with the `parse` option in UrlParamConfig.\n *\n * @example\n * ```ts\n * const config = {\n * chainId: {\n * key: 'chain',\n * defaultValue: 8453 as ChainId,\n * parse: urlParsers.chainId,\n * serialize: urlSerializers.chainId,\n * },\n * }\n * ```\n */\nexport const urlParsers = {\n /**\n * Parse a chain name or ID to ChainId.\n * Accepts: \"base\", \"pulsechain\", \"8453\", \"369\"\n */\n chainId: (raw: string): ChainId | undefined => {\n // Try by name first\n const byName = chains.find(c => c.name.toLowerCase() === raw.toLowerCase())\n if (byName) return byName.id as ChainId\n\n // Try by ID\n const parsed = parseInt(raw, 10)\n if (!isNaN(parsed)) {\n const byId = chains.find(c => c.id === parsed)\n if (byId) return byId.id as ChainId\n }\n\n return undefined\n },\n\n /**\n * Parse a string to bigint.\n * Returns undefined if not a valid integer string.\n */\n bigint: (raw: string): bigint | undefined => {\n try {\n return BigInt(raw)\n } catch {\n return undefined\n }\n },\n\n /**\n * Parse a string to number.\n * Returns undefined if not a valid number.\n */\n number: (raw: string): number | undefined => {\n const parsed = Number(raw)\n return isNaN(parsed) ? undefined : parsed\n },\n\n /**\n * Parse a string to integer.\n * Returns undefined if not a valid integer.\n */\n integer: (raw: string): number | undefined => {\n const parsed = parseInt(raw, 10)\n return isNaN(parsed) ? undefined : parsed\n },\n\n /**\n * Parse a hex string (validates 0x prefix).\n * Returns undefined if not a valid hex string.\n */\n hex: (raw: string): Hex | undefined => {\n if (raw.startsWith('0x') && /^0x[0-9a-fA-F]*$/.test(raw)) {\n return raw as Hex\n }\n return undefined\n },\n\n /**\n * Parse a boolean string.\n * Accepts: \"true\", \"false\", \"1\", \"0\"\n */\n boolean: (raw: string): boolean | undefined => {\n if (raw === 'true' || raw === '1') return true\n if (raw === 'false' || raw === '0') return false\n return undefined\n },\n\n /**\n * Parse an uppercase string (e.g., for currency tickers).\n */\n uppercase: (raw: string): string => raw.toUpperCase(),\n\n /**\n * Parse a lowercase string (e.g., for provider keys).\n */\n lowercase: (raw: string): string => raw.toLowerCase(),\n}\n\n/**\n * Built-in serializers for common URL parameter types.\n * Use these with the `serialize` option in UrlParamConfig.\n */\nexport const urlSerializers = {\n /**\n * Serialize ChainId to chain name.\n */\n chainId: (chainId: ChainId): string => {\n const chain = chains.find(c => c.id === chainId)\n return chain?.name.toLowerCase() ?? String(chainId)\n },\n\n /**\n * Serialize bigint to string.\n */\n bigint: (value: bigint): string => value.toString(),\n\n /**\n * Serialize boolean to string.\n */\n boolean: (value: boolean): string => value ? 'true' : 'false',\n}\n\n// =============================================================================\n// Hook Implementation\n// =============================================================================\n\n/**\n * Parse a URL parameter value using the provided config.\n * Returns the parsed value or defaultValue if parsing fails or param is missing.\n */\nfunction parseParam<T>(\n searchParams: URLSearchParams,\n config: UrlParamConfig<T>\n): T {\n const raw = searchParams.get(config.key)\n\n if (raw === null) {\n return config.defaultValue\n }\n\n if (config.parse) {\n const parsed = config.parse(raw)\n return parsed !== undefined ? parsed : config.defaultValue\n }\n\n // For string types, return raw value directly\n // TypeScript doesn't know T is string here, but this is safe\n // because if no parse is provided, T should be string\n return raw as T\n}\n\n/**\n * Serialize a value to URL string using the provided config.\n */\nfunction serializeParam<T>(value: T, config: UrlParamConfig<T>): string {\n if (config.serialize) {\n return config.serialize(value)\n }\n return String(value)\n}\n\n/**\n * Hook for managing multiple URL query parameters as typed state.\n * Uses URL as the single source of truth - no internal useState.\n *\n * **Key benefits:**\n * - No bi-directional state sync (URL is the only source of truth)\n * - Type-safe parsing and serialization\n * - Preserves other URL params when updating\n * - Shareable URLs with full state\n * - Access to URL hash fragment\n * - Built-in parsers for common types\n *\n * **When to use:**\n * - UI state that should persist in the URL (filters, selections, pagination)\n * - State that should be shareable via URL\n * - Replacing useState + useEffect URL sync anti-patterns\n *\n * @template C - Record of config objects keyed by state property names\n * @param config - Configuration object mapping state keys to URL param configs\n * @returns Object with current state values, setParam function, hash, and raw searchParams\n *\n * @example\n * ```tsx\n * // Define config outside component (stable reference)\n * const depositDetailsConfig = {\n * depositId: {\n * key: 'id',\n * defaultValue: null as bigint | null,\n * parse: urlParsers.bigint,\n * serialize: urlSerializers.bigint,\n * },\n * chainId: {\n * key: 'chain',\n * defaultValue: 8453 as ChainId,\n * parse: urlParsers.chainId,\n * serialize: urlSerializers.chainId,\n * },\n * escrow: {\n * key: 'escrow',\n * defaultValue: null as Hex | null,\n * parse: urlParsers.hex,\n * },\n * } as const\n *\n * function DepositDetails() {\n * const { state, setParam, hash } = useUrlState(depositDetailsConfig)\n *\n * // state.depositId, state.chainId, state.escrow derived from URL\n * // hash contains any #fragment if present\n *\n * return (\n * <div>\n * <p>Deposit: {state.depositId?.toString()}</p>\n * <p>Chain: {state.chainId}</p>\n * {hash && <p>Section: {hash}</p>}\n * </div>\n * )\n * }\n * ```\n */\nexport function useUrlState<C extends Record<string, UrlParamConfig<unknown>>>(\n config: C\n): UseUrlStateResult<C> {\n const [searchParams, setSearchParams] = useSearchParams()\n const location = useLocation()\n\n // Use ref for stable config reference to avoid re-creating memoized values\n // when config object identity changes (but contents are the same)\n const configRef = useRef(config)\n configRef.current = config\n\n // Extract hash without the # prefix\n const hash = useMemo(() => {\n const rawHash = location.hash\n return rawHash && rawHash.length > 1 ? rawHash.slice(1) : null\n }, [location.hash])\n\n // Derive all state values from URL params via useMemo\n // This is the key insight: URL is source of truth, state is derived\n const state = useMemo(() => {\n const result = {} as UrlStateValues<C>\n for (const [key, paramConfig] of Object.entries(configRef.current)) {\n ; (result as Record<string, unknown>)[key] = parseParam(\n searchParams,\n paramConfig as UrlParamConfig<unknown>\n )\n }\n return result\n }, [searchParams])\n\n // Create setter that updates URL params\n // Other params are preserved, only the specified param is updated\n const setParam = useCallback(\n <K extends keyof C>(key: K, value: ConfigValue<C[K]>) => {\n const paramConfig = configRef.current[key] as UrlParamConfig<ConfigValue<C[K]>>\n const serialized = serializeParam(value, paramConfig)\n\n setSearchParams(\n (prev) => {\n const next = new URLSearchParams(prev)\n next.set(paramConfig.key, serialized)\n return next\n },\n { replace: true }\n )\n },\n [setSearchParams]\n )\n\n return { state, setParam, hash, searchParams }\n}\n"]}