@agg-build/hooks 4.26.0 → 4.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/deposit.d.mts +142 -138
  2. package/dist/deposit.d.mts.map +1 -0
  3. package/dist/deposit.d.ts +142 -138
  4. package/dist/deposit.d.ts.map +1 -0
  5. package/dist/deposit.js +751 -2414
  6. package/dist/deposit.mjs +744 -727
  7. package/dist/deposit.mjs.map +1 -0
  8. package/dist/index-CbNR3EIA.d.mts +341 -0
  9. package/dist/index-CbNR3EIA.d.mts.map +1 -0
  10. package/dist/index-CsP1jRMD.d.ts +341 -0
  11. package/dist/index-CsP1jRMD.d.ts.map +1 -0
  12. package/dist/index-D0fXU85a.d.mts +743 -0
  13. package/dist/index-D0fXU85a.d.mts.map +1 -0
  14. package/dist/index-D0fXU85a.d.ts +743 -0
  15. package/dist/index-D0fXU85a.d.ts.map +1 -0
  16. package/dist/index.d.mts +6458 -7514
  17. package/dist/index.d.mts.map +1 -0
  18. package/dist/index.d.ts +6458 -7514
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +7451 -11416
  21. package/dist/index.mjs +7154 -5220
  22. package/dist/index.mjs.map +1 -0
  23. package/dist/use-ramp-session-7RAG9tvY.js +30 -0
  24. package/dist/use-ramp-session-BFWHCtuc.mjs +21 -0
  25. package/dist/use-ramp-session-BFWHCtuc.mjs.map +1 -0
  26. package/dist/use-sync-balances-BkcqInJh.mjs +1779 -0
  27. package/dist/use-sync-balances-BkcqInJh.mjs.map +1 -0
  28. package/dist/{use-sync-balances-CeD8qZWP.d.ts → use-sync-balances-CVEg__2V.d.mts} +7 -8
  29. package/dist/use-sync-balances-CVEg__2V.d.mts.map +1 -0
  30. package/dist/{use-sync-balances-CeD8qZWP.d.mts → use-sync-balances-CVEg__2V.d.ts} +7 -8
  31. package/dist/use-sync-balances-CVEg__2V.d.ts.map +1 -0
  32. package/dist/use-sync-balances-HryB8Q7F.js +1974 -0
  33. package/dist/withdraw--f5kYKJA.mjs +1474 -0
  34. package/dist/withdraw--f5kYKJA.mjs.map +1 -0
  35. package/dist/withdraw-BXWZYmJa.js +1591 -0
  36. package/dist/withdraw.d.mts +3 -351
  37. package/dist/withdraw.d.ts +3 -351
  38. package/dist/withdraw.js +10 -2229
  39. package/dist/withdraw.mjs +3 -20
  40. package/package.json +7 -6
  41. package/dist/chunk-JFJNW2PD.mjs +0 -5649
  42. package/dist/chunk-LNIAMJTZ.mjs +0 -505
  43. package/dist/chunk-OZQ3K47U.mjs +0 -31
  44. package/dist/use-deposit-addresses-B9ICS-3U.d.mts +0 -236
  45. package/dist/use-deposit-addresses-B9ICS-3U.d.ts +0 -236
@@ -0,0 +1,1474 @@
1
+ import { S as shouldInvalidateBalancesForOrderEvent, _ as invalidateBalanceQueries, c as useAggBalanceState, d as useAggUiConfig, l as useAggClient, r as useDepositAddresses, t as useSyncBalances, v as invalidatePositionQueries, w as executionKeys, x as invalidateUserMoneyState, y as invalidateUserActivityQueries } from "./use-sync-balances-BkcqInJh.mjs";
2
+ import { CandleBuilder } from "@agg-build/sdk";
3
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
5
+ import { jsx } from "react/jsx-runtime";
6
+ //#region src/candle-store.ts
7
+ const MAX_BUILDERS = 60;
8
+ const builders = /* @__PURE__ */ new Map();
9
+ /**
10
+ * LRU-bounded store for per-market CandleBuilders.
11
+ * Fed from WS provider (onDelta/onSnapshot/onTrade).
12
+ * Read from useLiveCandles and SDK-only partners.
13
+ */
14
+ function getOrCreateBuilder(marketId) {
15
+ const existing = builders.get(marketId);
16
+ if (existing) {
17
+ builders.delete(marketId);
18
+ builders.set(marketId, existing);
19
+ return existing;
20
+ }
21
+ evict();
22
+ const builder = new CandleBuilder();
23
+ builders.set(marketId, builder);
24
+ return builder;
25
+ }
26
+ function getBuilder(marketId) {
27
+ return builders.get(marketId);
28
+ }
29
+ function resetAllBuilders() {
30
+ for (const builder of builders.values()) builder.reset();
31
+ }
32
+ function evict() {
33
+ if (builders.size < MAX_BUILDERS) return;
34
+ const oldest = builders.keys().next().value;
35
+ if (oldest) {
36
+ builders.get(oldest)?.dispose();
37
+ builders.delete(oldest);
38
+ }
39
+ }
40
+ /** On-demand counts only; no candle arrays escape through diagnostics. */
41
+ function getCandleStoreDiagnostics() {
42
+ return {
43
+ builders: builders.size,
44
+ retainedCandles: [...builders.values()].reduce((total, builder) => total + [
45
+ "1m",
46
+ "5m",
47
+ "1h",
48
+ "1d"
49
+ ].reduce((count, interval) => count + builder.getClosed(interval).length + (builder.getForming(interval) ? 1 : 0), 0), 0),
50
+ maxBuilders: MAX_BUILDERS
51
+ };
52
+ }
53
+ //#endregion
54
+ //#region src/market-data/live-cache.ts
55
+ const MAX_TRADES = 50;
56
+ const createMarketLiveState = (marketId, overrides) => ({
57
+ marketId,
58
+ orderbook: null,
59
+ orderbookSnapshotVersion: 0,
60
+ orderbookError: null,
61
+ trades: [],
62
+ isConnected: false,
63
+ integrity: "ok",
64
+ ...overrides
65
+ });
66
+ const withLiveOrderbook = (data, orderbook, source) => {
67
+ const current = data ?? createMarketLiveState(orderbook.marketId);
68
+ return {
69
+ ...current,
70
+ orderbook,
71
+ orderbookSnapshotVersion: source === "snapshot" ? current.orderbookSnapshotVersion + 1 : current.orderbookSnapshotVersion,
72
+ orderbookError: null,
73
+ integrity: "ok"
74
+ };
75
+ };
76
+ const withLiveOrderbookError = (data, marketId, orderbookError) => {
77
+ const current = data ?? createMarketLiveState(marketId);
78
+ if (current.orderbookError === orderbookError && current.marketId === marketId) return current;
79
+ return {
80
+ ...current,
81
+ marketId,
82
+ orderbookError
83
+ };
84
+ };
85
+ const withLiveIntegrity = (data, integrity) => {
86
+ if (!data) return data;
87
+ if (data.integrity === integrity) return data;
88
+ return {
89
+ ...data,
90
+ integrity
91
+ };
92
+ };
93
+ const withLiveTrade = (data, trade) => {
94
+ const current = data ?? createMarketLiveState(trade.outcomeId);
95
+ const trades = [trade, ...current.trades].slice(0, MAX_TRADES);
96
+ return {
97
+ ...current,
98
+ trades
99
+ };
100
+ };
101
+ //#endregion
102
+ //#region src/market-data/query-keys.ts
103
+ const marketDataKeys = {
104
+ all: () => ["market-data"],
105
+ live: (marketId) => [
106
+ "market-data",
107
+ "live",
108
+ marketId
109
+ ],
110
+ livePrefix: () => ["market-data", "live"],
111
+ orderbook: (marketId, selectionKey, depth) => [
112
+ "market-data",
113
+ "orderbook",
114
+ marketId,
115
+ selectionKey ?? null,
116
+ depth ?? null
117
+ ],
118
+ orderbookPrefix: (marketId) => marketId == null ? ["market-data", "orderbook"] : [
119
+ "market-data",
120
+ "orderbook",
121
+ marketId
122
+ ],
123
+ orderbookQuote: (marketId, side, size) => [
124
+ "market-data",
125
+ "orderbook-quote",
126
+ marketId,
127
+ side,
128
+ size
129
+ ],
130
+ /**
131
+ * Stable per `{marketId, interval, rangeKey}`. Deliberately excludes the
132
+ * concrete `startTs`/`endTs` of the rolling window — those advance every
133
+ * candle bucket, and including them would mint a fresh key on every
134
+ * rollover, defeating React Query's cache on tab/page revisits. The
135
+ * current window is passed to `queryFn` via closure instead, and
136
+ * `refetchInterval` advances the data under the stable key.
137
+ */
138
+ chart: (marketId, interval, rangeKey, countBack) => [
139
+ "market-data",
140
+ "chart",
141
+ marketId,
142
+ interval,
143
+ rangeKey,
144
+ countBack ?? null
145
+ ],
146
+ chartPrefix: (marketId, interval) => {
147
+ if (!marketId) return ["market-data", "chart"];
148
+ if (!interval) return [
149
+ "market-data",
150
+ "chart",
151
+ marketId
152
+ ];
153
+ return [
154
+ "market-data",
155
+ "chart",
156
+ marketId,
157
+ interval
158
+ ];
159
+ }
160
+ };
161
+ //#endregion
162
+ //#region src/market-data/live-store.ts
163
+ const LIVE_RETENTION = {
164
+ maxInactiveSnapshots: 32,
165
+ maxInactiveLevels: 16384,
166
+ inactiveTtlMs: 5e3,
167
+ maxEntries: 300
168
+ };
169
+ const levelEntries = (state) => {
170
+ const book = state.orderbook;
171
+ return book ? (book.bids?.length ?? 0) + (book.asks?.length ?? 0) + Object.values(book.venueOrderbooks ?? {}).reduce((n, venue) => n + venue.bids.length + venue.asks.length, 0) : 0;
172
+ };
173
+ const hasSnapshot = (state) => !!state.orderbook || !!state.candles || state.trades.length > 0;
174
+ /** Stable per-outcome snapshots; inactive depth is bounded separately from active subscribers. */
175
+ var MarketLiveStore = class {
176
+ constructor(seed) {
177
+ this.seed = seed;
178
+ this.connectionState = false;
179
+ this.connectionGeneration = 0;
180
+ this.states = /* @__PURE__ */ new Map();
181
+ this.listeners = /* @__PURE__ */ new Map();
182
+ this.touched = /* @__PURE__ */ new Map();
183
+ this.needsPrune = false;
184
+ }
185
+ ensure(id) {
186
+ let state = this.states.get(id);
187
+ if (state && !this.listeners.has(id) && Date.now() - (this.touched.get(id) ?? 0) >= LIVE_RETENTION.inactiveTtlMs) {
188
+ this.drop(id);
189
+ state = void 0;
190
+ }
191
+ if (!state) {
192
+ const seed = this.seed?.(id);
193
+ state = {
194
+ ...createMarketLiveState(id, {
195
+ isConnected: this.connectionState,
196
+ ...seed
197
+ }),
198
+ connectionGeneration: this.connectionGeneration,
199
+ orderbookGeneration: seed?.orderbook ? -1 : this.connectionGeneration
200
+ };
201
+ this.states.set(id, state);
202
+ this.touched.set(id, Date.now());
203
+ this.needsPrune = true;
204
+ }
205
+ return state;
206
+ }
207
+ get(id) {
208
+ const state = this.ensure(id);
209
+ this.touched.set(id, Date.now());
210
+ if (this.needsPrune) this.prune(/* @__PURE__ */ new Set([id]));
211
+ return state;
212
+ }
213
+ /** Read retained data without creating a state, keeping it alive, or observing updates. */
214
+ peek(id) {
215
+ if (!this.listeners.has(id) && Date.now() - (this.touched.get(id) ?? 0) >= LIVE_RETENTION.inactiveTtlMs) return void 0;
216
+ return this.states.get(id);
217
+ }
218
+ /** Protect a render's whole selection before useSyncExternalStore subscribes. */
219
+ getMany(ids) {
220
+ const result = ids.map((id) => {
221
+ const state = this.ensure(id);
222
+ this.touched.set(id, Date.now());
223
+ return state;
224
+ });
225
+ if (this.needsPrune) this.prune(new Set(ids));
226
+ return result;
227
+ }
228
+ update(id, updater) {
229
+ const previous = this.ensure(id);
230
+ const next = updater(previous);
231
+ if (!next || next === previous) return;
232
+ const bookChanged = next.orderbook && (!previous.orderbook || next.orderbook.seq !== previous.orderbook.seq || next.orderbook.checksum !== previous.orderbook.checksum || next.orderbook.timestamp !== previous.orderbook.timestamp || next.orderbookSnapshotVersion !== previous.orderbookSnapshotVersion);
233
+ this.states.set(id, bookChanged ? {
234
+ ...next,
235
+ orderbookGeneration: this.connectionGeneration
236
+ } : next);
237
+ if (bookChanged || next.candles !== previous.candles || next.trades !== previous.trades) this.touched.set(id, Date.now());
238
+ for (const listener of this.listeners.get(id) ?? []) listener();
239
+ if (!this.listeners.has(id) || this.needsPrune) this.prune(/* @__PURE__ */ new Set([id]));
240
+ }
241
+ connected(connected) {
242
+ if (connected && !this.connectionState) this.connectionGeneration++;
243
+ this.connectionState = connected;
244
+ for (const id of [...this.states.keys()]) this.update(id, (state) => state.isConnected === connected && state.connectionGeneration === this.connectionGeneration ? state : {
245
+ ...state,
246
+ isConnected: connected,
247
+ connectionGeneration: this.connectionGeneration,
248
+ ...connected ? {} : { candles: void 0 }
249
+ });
250
+ }
251
+ subscribe(ids, listener) {
252
+ for (const id of ids) {
253
+ const set = this.listeners.get(id) ?? /* @__PURE__ */ new Set();
254
+ set.add(listener);
255
+ this.listeners.set(id, set);
256
+ this.touched.set(id, Date.now());
257
+ }
258
+ return () => {
259
+ for (const id of ids) {
260
+ const set = this.listeners.get(id);
261
+ set?.delete(listener);
262
+ if (!set?.size) {
263
+ this.listeners.delete(id);
264
+ this.touched.set(id, Date.now());
265
+ }
266
+ }
267
+ this.prune();
268
+ };
269
+ }
270
+ getDiagnostics() {
271
+ const inactive = [...this.states].filter(([id]) => !this.listeners.has(id));
272
+ return {
273
+ entries: this.states.size,
274
+ activeEntries: this.listeners.size,
275
+ cachedBooks: [...this.states.values()].filter((state) => state.orderbook).length,
276
+ cachedLevelEntries: [...this.states.values()].reduce((n, state) => n + levelEntries(state), 0),
277
+ inactiveSnapshots: inactive.filter(([, state]) => hasSnapshot(state)).length,
278
+ inactiveLevelEntries: inactive.reduce((n, [, state]) => n + levelEntries(state), 0),
279
+ limits: LIVE_RETENTION
280
+ };
281
+ }
282
+ drop(id) {
283
+ this.states.delete(id);
284
+ this.touched.delete(id);
285
+ }
286
+ prune(protectedIds = /* @__PURE__ */ new Set()) {
287
+ this.needsPrune = false;
288
+ if (this.timer) clearTimeout(this.timer);
289
+ this.timer = void 0;
290
+ const now = Date.now();
291
+ const inactive = [...this.states].filter(([id]) => !this.listeners.has(id) && !protectedIds.has(id)).sort(([a], [b]) => (this.touched.get(a) ?? 0) - (this.touched.get(b) ?? 0));
292
+ let count = inactive.filter(([, state]) => hasSnapshot(state)).length;
293
+ let levels = inactive.reduce((n, [, state]) => n + levelEntries(state), 0);
294
+ for (const [id, state] of inactive) if (now - (this.touched.get(id) ?? 0) >= LIVE_RETENTION.inactiveTtlMs || this.states.size > LIVE_RETENTION.maxEntries || count > LIVE_RETENTION.maxInactiveSnapshots || levels > LIVE_RETENTION.maxInactiveLevels) {
295
+ if (hasSnapshot(state)) count--;
296
+ levels -= levelEntries(state);
297
+ this.drop(id);
298
+ }
299
+ let nextExpiry = Infinity;
300
+ for (const id of this.states.keys()) if (!this.listeners.has(id)) nextExpiry = Math.min(nextExpiry, (this.touched.get(id) ?? now) + LIVE_RETENTION.inactiveTtlMs);
301
+ if (Number.isFinite(nextExpiry)) this.timer = setTimeout(() => {
302
+ this.timer = void 0;
303
+ this.prune();
304
+ }, Math.max(1, nextExpiry - now));
305
+ }
306
+ };
307
+ const stores = /* @__PURE__ */ new WeakMap();
308
+ function getMarketLiveStore(client) {
309
+ let store = stores.get(client);
310
+ if (!store) {
311
+ store = new MarketLiveStore((id) => client.getQueryData?.(marketDataKeys.live(id)));
312
+ stores.set(client, store);
313
+ }
314
+ return store;
315
+ }
316
+ function useLiveStates(ids) {
317
+ const store = getMarketLiveStore(useQueryClient());
318
+ const key = JSON.stringify(ids);
319
+ const selector = useMemo(() => {
320
+ const selectedIds = JSON.parse(key);
321
+ let previous = [];
322
+ return {
323
+ subscribe: (listener) => store.subscribe(selectedIds, listener),
324
+ get: () => {
325
+ const next = store.getMany(selectedIds).map((data) => ({ data }));
326
+ if (next.length === previous.length && next.every((entry, index) => entry.data === previous[index]?.data)) return previous;
327
+ previous = next;
328
+ return next;
329
+ }
330
+ };
331
+ }, [key, store]);
332
+ return useSyncExternalStore(selector.subscribe, selector.get, selector.get);
333
+ }
334
+ /** Keep acknowledged closed tails until REST history catches up; empty batches never erase them. */
335
+ function mergeCandleTails(previous, next) {
336
+ return Object.fromEntries([
337
+ "1m",
338
+ "5m",
339
+ "1h",
340
+ "1d"
341
+ ].map((interval) => {
342
+ const oldClosed = previous?.[interval].closed ?? [];
343
+ const incoming = next[interval].closed;
344
+ const closed = incoming.length ? [...new Map([...oldClosed, ...incoming].map((candle) => [candle.openTime, candle])).values()].sort((a, b) => a.openTime - b.openTime).slice(-500) : oldClosed;
345
+ return [interval, {
346
+ forming: next[interval].forming,
347
+ closed
348
+ }];
349
+ }));
350
+ }
351
+ //#endregion
352
+ //#region src/market-data/market-resolution-cache.ts
353
+ const isRecord = (value) => {
354
+ return typeof value === "object" && value !== null;
355
+ };
356
+ const NESTED_COLLECTION_FIELDS = [
357
+ "pages",
358
+ "data",
359
+ "markets",
360
+ "events",
361
+ "venueMarkets"
362
+ ];
363
+ const marketContainsOutcomeId = (market, outcomeId) => {
364
+ if (!isRecord(market)) return false;
365
+ const outcomes = market.venueMarketOutcomes;
366
+ if (!Array.isArray(outcomes)) return false;
367
+ return outcomes.some((outcome) => isRecord(outcome) && outcome.id === outcomeId);
368
+ };
369
+ /**
370
+ * Deep-search arbitrary query data for a venue market that holds `outcomeId`.
371
+ * Used both to decide which cached queries to optimistically patch and as the
372
+ * `invalidateQueries` predicate.
373
+ */
374
+ const cachedDataContainsOutcomeId = (data, outcomeId) => {
375
+ if (Array.isArray(data)) return data.some((entry) => cachedDataContainsOutcomeId(entry, outcomeId));
376
+ if (!isRecord(data)) return false;
377
+ if (marketContainsOutcomeId(data, outcomeId)) return true;
378
+ return NESTED_COLLECTION_FIELDS.some((field) => {
379
+ const value = data[field];
380
+ return Array.isArray(value) && value.some((entry) => cachedDataContainsOutcomeId(entry, outcomeId));
381
+ });
382
+ };
383
+ const normalizeLabel = (label) => {
384
+ return typeof label === "string" ? label.trim().toLowerCase() : "";
385
+ };
386
+ /**
387
+ * Match a local outcome to the resolution payload by external identifier first
388
+ * (authoritative on-chain token id) then by label — the WS event carries
389
+ * `externalIdentifier`/`label`, never the internal `id` per outcome.
390
+ */
391
+ const findResolvedOutcome = (resolvedOutcomes, outcome) => {
392
+ const externalIdentifier = outcome.externalIdentifier;
393
+ if (typeof externalIdentifier === "string" && externalIdentifier.length > 0) {
394
+ const matchedByExternalId = resolvedOutcomes.find((resolved) => resolved.externalIdentifier === externalIdentifier);
395
+ if (matchedByExternalId) return matchedByExternalId;
396
+ }
397
+ const label = normalizeLabel(outcome.label);
398
+ if (label) return resolvedOutcomes.find((resolved) => normalizeLabel(resolved.label) === label) ?? null;
399
+ return null;
400
+ };
401
+ const applyWinnersToOutcomes = (outcomes, resolution) => {
402
+ let changed = false;
403
+ const next = outcomes.map((outcome) => {
404
+ if (!isRecord(outcome)) return outcome;
405
+ const resolved = findResolvedOutcome(resolution.outcomes, outcome);
406
+ if (!resolved) return outcome;
407
+ const nextWinner = resolved.winner === true;
408
+ if (outcome.winner === nextWinner) return outcome;
409
+ changed = true;
410
+ return {
411
+ ...outcome,
412
+ winner: nextWinner
413
+ };
414
+ });
415
+ return changed ? next : outcomes;
416
+ };
417
+ const applyResolutionToMarket = (market, resolution) => {
418
+ const outcomes = market.venueMarketOutcomes;
419
+ if (!Array.isArray(outcomes)) return market;
420
+ if (!outcomes.some((outcome) => isRecord(outcome) && outcome.id === resolution.outcomeId)) return market;
421
+ const nextOutcomes = applyWinnersToOutcomes(outcomes, resolution);
422
+ if (!(market.status !== resolution.status) && nextOutcomes === outcomes) return market;
423
+ return {
424
+ ...market,
425
+ status: resolution.status,
426
+ venueMarketOutcomes: nextOutcomes
427
+ };
428
+ };
429
+ /**
430
+ * Return a structurally-shared copy of `data` with the resolved market marked
431
+ * `status: resolved|closed` and its outcomes' `winner` flags stamped from the
432
+ * WS payload. Untouched branches keep their original reference (no spurious
433
+ * re-renders). Returns the original `data` when nothing matched.
434
+ */
435
+ const applyResolutionToCachedData = (data, resolution) => {
436
+ if (Array.isArray(data)) {
437
+ let changed = false;
438
+ const next = data.map((entry) => {
439
+ const updated = applyResolutionToCachedData(entry, resolution);
440
+ if (updated !== entry) changed = true;
441
+ return updated;
442
+ });
443
+ return changed ? next : data;
444
+ }
445
+ if (!isRecord(data)) return data;
446
+ if (Array.isArray(data.venueMarketOutcomes)) return applyResolutionToMarket(data, resolution);
447
+ let changed = false;
448
+ const next = { ...data };
449
+ for (const field of NESTED_COLLECTION_FIELDS) {
450
+ const value = data[field];
451
+ if (!Array.isArray(value)) continue;
452
+ const updated = applyResolutionToCachedData(value, resolution);
453
+ if (updated !== value) {
454
+ next[field] = updated;
455
+ changed = true;
456
+ }
457
+ }
458
+ return changed ? next : data;
459
+ };
460
+ //#endregion
461
+ //#region src/market-data/history-retention.ts
462
+ const DEFAULT_HISTORY_RETENTION = {
463
+ maxInactiveQueries: 16,
464
+ maxInactivePoints: 2e4
465
+ };
466
+ const owners = /* @__PURE__ */ new WeakMap();
467
+ const isHistory = (key) => key[0] === "market-data" && key[1] === "chart";
468
+ const points = (data) => {
469
+ const chart = data;
470
+ return chart?.venues ? Object.values(chart.venues).reduce((total, venue) => total + venue.candles.length + (venue.liveCandle ? 1 : 0), 0) : 0;
471
+ };
472
+ /** Read-only diagnostics for chart policy only; ordinary REST and user caches are excluded. */
473
+ function getChartHistoryRetentionStats(client) {
474
+ const queries = client.getQueryCache().findAll({ queryKey: marketDataKeys.chartPrefix() });
475
+ const active = queries.filter((query) => query.getObserversCount() > 0);
476
+ const inactive = queries.filter((query) => query.getObserversCount() === 0);
477
+ const owner = owners.get(client);
478
+ return {
479
+ activeQueries: active.length,
480
+ inactiveQueries: inactive.length,
481
+ activePoints: active.reduce((sum, query) => sum + points(query.state.data), 0),
482
+ inactivePoints: inactive.reduce((sum, query) => sum + points(query.state.data), 0),
483
+ inFlightQueries: queries.filter((query) => query.state.fetchStatus === "fetching").length,
484
+ trackedQueries: owner?.touched.size ?? 0,
485
+ evictedQueries: owner?.evicted ?? 0,
486
+ limits: owner?.limits ?? DEFAULT_HISTORY_RETENTION
487
+ };
488
+ }
489
+ /** One reusable policy owner per QueryClient. Called by the hooks provider, never the demo app. */
490
+ function installChartHistoryRetention(client, limits = DEFAULT_HISTORY_RETENTION) {
491
+ let owner = owners.get(client);
492
+ if (!owner) {
493
+ owner = {
494
+ refs: 0,
495
+ clock: Date.now(),
496
+ touched: /* @__PURE__ */ new Map(),
497
+ evicted: 0,
498
+ queued: false,
499
+ pruning: false,
500
+ limits,
501
+ unsubscribe: () => {},
502
+ prune: () => {}
503
+ };
504
+ owners.set(client, owner);
505
+ const current = owner;
506
+ const prune = () => {
507
+ if (current.pruning) return;
508
+ current.pruning = true;
509
+ try {
510
+ const inactive = client.getQueryCache().findAll({ queryKey: marketDataKeys.chartPrefix() }).filter((query) => query.getObserversCount() === 0).sort((a, b) => (current.touched.get(a.queryHash) ?? a.state.dataUpdatedAt) - (current.touched.get(b.queryHash) ?? b.state.dataUpdatedAt));
511
+ let count = inactive.length;
512
+ let totalPoints = inactive.reduce((sum, query) => sum + points(query.state.data), 0);
513
+ for (const query of inactive) {
514
+ if (count <= current.limits.maxInactiveQueries && totalPoints <= current.limits.maxInactivePoints) break;
515
+ if (query.getObserversCount() > 0) continue;
516
+ count--;
517
+ totalPoints -= points(query.state.data);
518
+ current.evicted++;
519
+ current.touched.delete(query.queryHash);
520
+ client.removeQueries({
521
+ queryKey: query.queryKey,
522
+ exact: true
523
+ });
524
+ }
525
+ } finally {
526
+ current.pruning = false;
527
+ }
528
+ };
529
+ current.prune = prune;
530
+ const schedule = () => {
531
+ if (current.queued) return;
532
+ current.queued = true;
533
+ queueMicrotask(() => {
534
+ current.queued = false;
535
+ prune();
536
+ });
537
+ };
538
+ current.unsubscribe = client.getQueryCache().subscribe((event) => {
539
+ if (!isHistory(event.query.queryKey)) return;
540
+ if (event.type === "removed") current.touched.delete(event.query.queryHash);
541
+ else if (event.type === "observerAdded" || event.type === "updated" || event.type === "added") {
542
+ current.clock = Math.max(current.clock + 1, Date.now());
543
+ current.touched.set(event.query.queryHash, current.clock);
544
+ }
545
+ if (!current.pruning) schedule();
546
+ });
547
+ schedule();
548
+ }
549
+ owner.refs++;
550
+ const current = owner;
551
+ let released = false;
552
+ return () => {
553
+ if (released) return;
554
+ released = true;
555
+ if (--current.refs > 0) return;
556
+ queueMicrotask(() => {
557
+ if (current.refs > 0) return;
558
+ current.prune();
559
+ current.unsubscribe();
560
+ owners.delete(client);
561
+ });
562
+ };
563
+ }
564
+ //#endregion
565
+ //#region src/core/providers/ws-provider.tsx
566
+ const AggWebSocketContext = createContext(null);
567
+ const AggWebSocketConnectionContext = createContext(false);
568
+ const AggWsUserEventContext = createContext(null);
569
+ const snapshotUnavailableOutcomeIdPattern = /snapshot unavailable for\s+([^\s]+)/i;
570
+ const resolveSnapshotUnavailableOutcomeId = (errorMessage) => {
571
+ const matchedOutcomeId = snapshotUnavailableOutcomeIdPattern.exec(errorMessage)?.[1];
572
+ if (!matchedOutcomeId) return null;
573
+ return matchedOutcomeId.replace(/[.,;:!?]+$/, "");
574
+ };
575
+ function useAggWebSocket() {
576
+ return useContext(AggWebSocketContext);
577
+ }
578
+ function useAggWebSocketConnectionState() {
579
+ return useContext(AggWebSocketConnectionContext);
580
+ }
581
+ function useOnOrderSubmitted(callback) {
582
+ const listeners = useContext(AggWsUserEventContext);
583
+ const callbackRef = useRef(callback);
584
+ callbackRef.current = callback;
585
+ useEffect(() => {
586
+ if (!listeners || !callbackRef.current) return;
587
+ const handler = (msg) => {
588
+ callbackRef.current?.(msg);
589
+ };
590
+ return listeners.addOrderSubmittedListener(handler);
591
+ }, [listeners, callback !== null]);
592
+ }
593
+ function useOnBalanceUpdate(callback) {
594
+ const listeners = useContext(AggWsUserEventContext);
595
+ const callbackRef = useRef(callback);
596
+ callbackRef.current = callback;
597
+ useEffect(() => {
598
+ if (!listeners || !callbackRef.current) return;
599
+ const handler = (msg) => {
600
+ callbackRef.current?.(msg);
601
+ };
602
+ return listeners.addBalanceUpdateListener(handler);
603
+ }, [listeners, callback !== null]);
604
+ }
605
+ function useOnOrderEvent(callback) {
606
+ const listeners = useContext(AggWsUserEventContext);
607
+ const callbackRef = useRef(callback);
608
+ callbackRef.current = callback;
609
+ useEffect(() => {
610
+ if (!listeners || !callbackRef.current) return;
611
+ const handler = (msg) => {
612
+ callbackRef.current?.(msg);
613
+ };
614
+ return listeners.addOrderEventListener(handler);
615
+ }, [listeners, callback !== null]);
616
+ }
617
+ function useOnRedeemEvent(callback) {
618
+ const listeners = useContext(AggWsUserEventContext);
619
+ const callbackRef = useRef(callback);
620
+ callbackRef.current = callback;
621
+ useEffect(() => {
622
+ if (!listeners || !callbackRef.current) return;
623
+ const handler = (msg) => {
624
+ callbackRef.current?.(msg);
625
+ };
626
+ return listeners.addRedeemEventListener(handler);
627
+ }, [listeners, callback !== null]);
628
+ }
629
+ function useOnWithdrawalLifecycle(callback) {
630
+ const listeners = useContext(AggWsUserEventContext);
631
+ const callbackRef = useRef(callback);
632
+ callbackRef.current = callback;
633
+ useEffect(() => {
634
+ if (!listeners || !callbackRef.current) return;
635
+ const handler = (msg) => {
636
+ callbackRef.current?.(msg);
637
+ };
638
+ return listeners.addWithdrawalLifecycleListener(handler);
639
+ }, [listeners, callback !== null]);
640
+ }
641
+ function AggWebSocketProvider({ children }) {
642
+ const client = useAggClient();
643
+ const { enableWebsocketsLogs } = useAggUiConfig();
644
+ const queryClient = useQueryClient();
645
+ const liveStore = getMarketLiveStore(queryClient);
646
+ useEffect(() => installChartHistoryRetention(queryClient), [queryClient]);
647
+ const [ws, setWs] = useState(null);
648
+ const [isConnected, setIsConnected] = useState(false);
649
+ const [isClientAuthenticated, setIsClientAuthenticated] = useState(client.isAuthenticated);
650
+ const wsRef = useRef(null);
651
+ const resyncDeadlineByMarketRef = useRef(/* @__PURE__ */ new Map());
652
+ const lastBalanceInvalidationAtRef = useRef(0);
653
+ useEffect(() => {
654
+ setIsClientAuthenticated(client.isAuthenticated);
655
+ return client.onAuthStateChange((authenticated) => {
656
+ setIsClientAuthenticated(authenticated);
657
+ });
658
+ }, [client]);
659
+ const orderSubmittedListenersRef = useRef(/* @__PURE__ */ new Set());
660
+ const balanceUpdateListenersRef = useRef(/* @__PURE__ */ new Set());
661
+ const orderEventListenersRef = useRef(/* @__PURE__ */ new Set());
662
+ const redeemEventListenersRef = useRef(/* @__PURE__ */ new Set());
663
+ const withdrawalLifecycleListenersRef = useRef(/* @__PURE__ */ new Set());
664
+ const userEventListeners = useMemo(() => ({
665
+ addOrderSubmittedListener: (cb) => {
666
+ orderSubmittedListenersRef.current.add(cb);
667
+ return () => {
668
+ orderSubmittedListenersRef.current.delete(cb);
669
+ };
670
+ },
671
+ addBalanceUpdateListener: (cb) => {
672
+ balanceUpdateListenersRef.current.add(cb);
673
+ return () => {
674
+ balanceUpdateListenersRef.current.delete(cb);
675
+ };
676
+ },
677
+ addOrderEventListener: (cb) => {
678
+ orderEventListenersRef.current.add(cb);
679
+ return () => {
680
+ orderEventListenersRef.current.delete(cb);
681
+ };
682
+ },
683
+ addRedeemEventListener: (cb) => {
684
+ redeemEventListenersRef.current.add(cb);
685
+ return () => {
686
+ redeemEventListenersRef.current.delete(cb);
687
+ };
688
+ },
689
+ addWithdrawalLifecycleListener: (cb) => {
690
+ withdrawalLifecycleListenersRef.current.add(cb);
691
+ return () => {
692
+ withdrawalLifecycleListenersRef.current.delete(cb);
693
+ };
694
+ }
695
+ }), []);
696
+ const syncLiveQuery = useCallback((marketId, updater) => {
697
+ liveStore.update(marketId, (previous) => updater(previous));
698
+ }, [liveStore]);
699
+ const invalidateBalanceCaches = useCallback(() => {
700
+ const now = Date.now();
701
+ if (now - lastBalanceInvalidationAtRef.current < 750) return;
702
+ lastBalanceInvalidationAtRef.current = now;
703
+ invalidateBalanceQueries(queryClient);
704
+ }, [queryClient]);
705
+ const invalidatePositionCaches = useCallback(() => {
706
+ invalidatePositionQueries(queryClient);
707
+ }, [queryClient]);
708
+ const invalidateActivityCaches = useCallback(() => {
709
+ invalidateUserActivityQueries(queryClient);
710
+ }, [queryClient]);
711
+ const invalidateResolutionCaches = useCallback((resolution) => {
712
+ const { outcomeId } = resolution;
713
+ syncLiveQuery(outcomeId, (previous) => {
714
+ if (!previous) return previous;
715
+ return {
716
+ ...previous,
717
+ orderbook: null,
718
+ orderbookSnapshotVersion: previous.orderbookSnapshotVersion + 1,
719
+ orderbookError: null,
720
+ integrity: "ok"
721
+ };
722
+ });
723
+ const affectedQueries = queryClient.getQueryCache().findAll({ predicate: (query) => cachedDataContainsOutcomeId(query.state.data, outcomeId) });
724
+ for (const query of affectedQueries) queryClient.setQueryData(query.queryKey, (previous) => applyResolutionToCachedData(previous, resolution));
725
+ queryClient.invalidateQueries({
726
+ queryKey: ["midpoints"],
727
+ refetchType: "all"
728
+ });
729
+ queryClient.invalidateQueries({
730
+ queryKey: ["venue-market-midpoints"],
731
+ refetchType: "all"
732
+ });
733
+ queryClient.invalidateQueries({
734
+ queryKey: ["venue-event"],
735
+ refetchType: "all"
736
+ });
737
+ queryClient.invalidateQueries({
738
+ queryKey: ["venue-markets"],
739
+ refetchType: "all"
740
+ });
741
+ queryClient.invalidateQueries({
742
+ queryKey: ["events"],
743
+ refetchType: "all"
744
+ });
745
+ queryClient.invalidateQueries({
746
+ queryKey: ["event-list"],
747
+ refetchType: "all"
748
+ });
749
+ queryClient.invalidateQueries({
750
+ queryKey: ["search"],
751
+ refetchType: "all"
752
+ });
753
+ queryClient.invalidateQueries({
754
+ predicate: (query) => cachedDataContainsOutcomeId(query.state.data, outcomeId),
755
+ refetchType: "all"
756
+ });
757
+ }, [queryClient, syncLiveQuery]);
758
+ const publishLocalCandles = useCallback((id) => {
759
+ const builder = getOrCreateBuilder(id);
760
+ const candles = Object.fromEntries([
761
+ "1m",
762
+ "5m",
763
+ "1h",
764
+ "1d"
765
+ ].map((interval) => [interval, {
766
+ forming: builder.getForming(interval) ? { ...builder.getForming(interval) } : null,
767
+ closed: builder.getClosed(interval).slice(-2)
768
+ }]));
769
+ liveStore.update(id, (previous) => ({
770
+ ...previous,
771
+ candles
772
+ }));
773
+ }, [liveStore]);
774
+ const callbacks = useMemo(() => ({
775
+ onMarketView: (view) => {
776
+ if (view.book && view.integrity === "ok") resyncDeadlineByMarketRef.current.delete(view.outcomeId);
777
+ liveStore.update(view.outcomeId, (previous) => ({
778
+ ...view.book ? withLiveOrderbook(previous, view.book, view.snapshot ? "snapshot" : "delta") : previous,
779
+ integrity: view.integrity,
780
+ candles: mergeCandleTails(previous.candles, view.candles),
781
+ receivedAtMs: view.receivedAtMs,
782
+ trades: [...view.trades, ...previous.trades].slice(0, 50)
783
+ }));
784
+ getOrCreateBuilder(view.outcomeId).replaceTail(view.candles);
785
+ },
786
+ onSnapshot: (marketId, book) => {
787
+ if (wsRef.current?.processesCandles) return;
788
+ resyncDeadlineByMarketRef.current.delete(marketId);
789
+ syncLiveQuery(marketId, (previous) => withLiveOrderbook(previous, book, "snapshot"));
790
+ if (!wsRef.current?.processesCandles && book.midpoint != null) {
791
+ getOrCreateBuilder(marketId).addMidpoint(book.midpoint, book.timestamp);
792
+ publishLocalCandles(marketId);
793
+ }
794
+ },
795
+ onDelta: (marketId, book) => {
796
+ if (wsRef.current?.processesCandles) return;
797
+ resyncDeadlineByMarketRef.current.delete(marketId);
798
+ syncLiveQuery(marketId, (previous) => withLiveOrderbook(previous, book, "delta"));
799
+ if (!wsRef.current?.processesCandles && book.midpoint != null) {
800
+ getOrCreateBuilder(marketId).addMidpoint(book.midpoint, book.timestamp);
801
+ publishLocalCandles(marketId);
802
+ }
803
+ },
804
+ onTrade: (trade) => {
805
+ if (wsRef.current?.processesCandles) return;
806
+ syncLiveQuery(trade.outcomeId, (previous) => withLiveTrade(previous, trade));
807
+ if (!wsRef.current?.processesCandles) {
808
+ getOrCreateBuilder(trade.outcomeId).addTrade(trade.price, trade.size, trade.timestamp);
809
+ publishLocalCandles(trade.outcomeId);
810
+ }
811
+ },
812
+ onHeartbeat: () => {
813
+ const now = Date.now();
814
+ resyncDeadlineByMarketRef.current.forEach((deadline, marketId) => {
815
+ if (deadline > now) return;
816
+ wsRef.current?.resnapshot(marketId);
817
+ resyncDeadlineByMarketRef.current.set(marketId, now + 35e3);
818
+ });
819
+ },
820
+ onDiagnostics: (event) => {
821
+ if (event.type !== "seq_gap" && event.type !== "checksum_mismatch") return;
822
+ const id = event.outcomeId ?? event.marketId;
823
+ resyncDeadlineByMarketRef.current.set(id, Date.now() + 35e3);
824
+ syncLiveQuery(id, (previous) => withLiveIntegrity(previous, "resyncing"));
825
+ },
826
+ onOrderSubmitted: (msg) => {
827
+ for (const listener of orderSubmittedListenersRef.current) listener(msg);
828
+ },
829
+ onBalanceUpdate: (msg) => {
830
+ if (isClientAuthenticated) {
831
+ invalidateBalanceCaches();
832
+ invalidateActivityCaches();
833
+ }
834
+ for (const listener of balanceUpdateListenersRef.current) listener(msg);
835
+ },
836
+ onOrderEvent: (msg) => {
837
+ if (isClientAuthenticated) {
838
+ const currentUserId = client.user?.id ?? null;
839
+ if (shouldInvalidateBalancesForOrderEvent(msg, currentUserId)) {
840
+ invalidateBalanceCaches();
841
+ invalidatePositionCaches();
842
+ }
843
+ }
844
+ for (const listener of orderEventListenersRef.current) listener(msg);
845
+ },
846
+ onRedeemEvent: (msg) => {
847
+ for (const listener of redeemEventListenersRef.current) listener(msg);
848
+ },
849
+ onWithdrawalLifecycle: (msg) => {
850
+ if (isClientAuthenticated) invalidateBalanceCaches();
851
+ for (const listener of withdrawalLifecycleListenersRef.current) listener(msg);
852
+ },
853
+ onMarketResolved: (msg) => {
854
+ invalidateResolutionCaches(msg);
855
+ if (isClientAuthenticated) {
856
+ invalidateBalanceCaches();
857
+ invalidatePositionCaches();
858
+ invalidateActivityCaches();
859
+ }
860
+ },
861
+ onError: (msg) => {
862
+ const outcomeId = resolveSnapshotUnavailableOutcomeId(msg.message);
863
+ if (!outcomeId) return;
864
+ syncLiveQuery(outcomeId, (previous) => withLiveOrderbookError(previous, outcomeId, msg.message));
865
+ },
866
+ onConnectionStateChange: (connected) => {
867
+ setIsConnected(connected);
868
+ if (connected) queryClient.invalidateQueries({
869
+ queryKey: marketDataKeys.chartPrefix(),
870
+ refetchType: "active"
871
+ });
872
+ else resetAllBuilders();
873
+ liveStore.connected(connected);
874
+ }
875
+ }), [
876
+ client,
877
+ invalidateActivityCaches,
878
+ invalidateBalanceCaches,
879
+ invalidatePositionCaches,
880
+ invalidateResolutionCaches,
881
+ isClientAuthenticated,
882
+ queryClient,
883
+ syncLiveQuery,
884
+ liveStore,
885
+ publishLocalCandles
886
+ ]);
887
+ const callbacksRef = useRef(callbacks);
888
+ callbacksRef.current = callbacks;
889
+ useEffect(() => {
890
+ wsRef.current?.setCallbacks(callbacks);
891
+ }, [callbacks]);
892
+ useEffect(() => {
893
+ let instance = null;
894
+ const resyncDeadlineByMarket = resyncDeadlineByMarketRef.current;
895
+ try {
896
+ instance = client.createWebSocket(callbacksRef.current, { enableLogs: enableWebsocketsLogs });
897
+ } catch {
898
+ console.warn("[AggUI] WebSocket connection could not be established. Ensure wsUrl is configured in createAggClient(). Real-time features (live prices, order updates) are disabled.");
899
+ setWs(null);
900
+ wsRef.current = null;
901
+ setIsConnected(false);
902
+ return;
903
+ }
904
+ instance.setHostDiagnostics?.(() => ({
905
+ live: liveStore.getDiagnostics(),
906
+ history: getChartHistoryRetentionStats(queryClient),
907
+ candles: getCandleStoreDiagnostics()
908
+ }));
909
+ setWs(instance);
910
+ wsRef.current = instance;
911
+ return () => {
912
+ instance?.destroy();
913
+ if (wsRef.current === instance) wsRef.current = null;
914
+ resyncDeadlineByMarket.clear();
915
+ setWs(null);
916
+ setIsConnected(false);
917
+ };
918
+ }, [
919
+ client,
920
+ enableWebsocketsLogs,
921
+ isClientAuthenticated,
922
+ liveStore,
923
+ queryClient
924
+ ]);
925
+ return /* @__PURE__ */ jsx(AggWebSocketConnectionContext.Provider, {
926
+ value: isConnected,
927
+ children: /* @__PURE__ */ jsx(AggWsUserEventContext.Provider, {
928
+ value: userEventListeners,
929
+ children: /* @__PURE__ */ jsx(AggWebSocketContext.Provider, {
930
+ value: ws,
931
+ children
932
+ })
933
+ })
934
+ });
935
+ }
936
+ //#endregion
937
+ //#region src/execution/use-withdraw-managed.ts
938
+ /**
939
+ * Mutation hook for withdrawing funds from managed wallets. On submission
940
+ * success the backend has already debited the managed-side balance and
941
+ * recorded the activity row, so we invalidate the full money-state cache
942
+ * surface (balances, positions, activity). Terminal lifecycle invalidation
943
+ * is handled separately by `useWithdrawalLifecycle`.
944
+ */
945
+ function useWithdrawManaged(options) {
946
+ const client = useAggClient();
947
+ const queryClient = useQueryClient();
948
+ return useMutation({
949
+ mutationFn: (params) => client.withdrawManaged(params),
950
+ onSuccess: (data) => {
951
+ invalidateUserMoneyState(queryClient);
952
+ options?.onSuccess?.(data);
953
+ },
954
+ onError: options?.onError
955
+ });
956
+ }
957
+ //#endregion
958
+ //#region src/execution/use-managed-balances.ts
959
+ /**
960
+ * Query hook for fetching unified managed wallet balances across all chains.
961
+ */
962
+ function useManagedBalances(options) {
963
+ const client = useAggClient();
964
+ const { enabled = true, mode, custody } = options ?? {};
965
+ const params = {
966
+ ...mode ? { mode } : {},
967
+ ...custody ? { custody } : {}
968
+ };
969
+ const query = useQuery({
970
+ queryKey: executionKeys.balances(mode, custody),
971
+ queryFn: () => client.getManagedBalances(Object.keys(params).length ? params : void 0),
972
+ enabled,
973
+ staleTime: 1e4,
974
+ refetchInterval: mode === "paper" ? false : 5e3,
975
+ refetchIntervalInBackground: false,
976
+ gcTime: 3e5,
977
+ retry: 1
978
+ });
979
+ return {
980
+ ...query,
981
+ balances: query.data
982
+ };
983
+ }
984
+ //#endregion
985
+ //#region src/withdraw/use-withdraw-flow.ts
986
+ const EVM_ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/;
987
+ const SOLANA_ADDRESS_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
988
+ /** Solana mainnet chain id (mirrors `CHAIN_IDS.SOLANA` in `@app/balance`). */
989
+ const SOLANA_CHAIN_ID = 792703809;
990
+ /**
991
+ * Chain IDs the withdrawal pipeline can settle on. Mirrors
992
+ * `WITHDRAWAL_SUPPORTED_CHAIN_IDS` in `@app/balance` (api). Solana is
993
+ * supported as a destination — the API resolves the SPL USDC mint and
994
+ * Relay's cross-chain quotes accept base58 recipients (no on-chain
995
+ * forwarder for Solana, so the lifecycle path skips that step). Keep this
996
+ * set in lock-step with the api-side constant.
997
+ */
998
+ const WITHDRAWAL_SUPPORTED_CHAIN_IDS = /* @__PURE__ */ new Set([
999
+ 1,
1000
+ 137,
1001
+ 42161,
1002
+ 8453,
1003
+ 56,
1004
+ SOLANA_CHAIN_ID,
1005
+ 1337
1006
+ ]);
1007
+ const isValidDestinationAddress = (address, chainId) => {
1008
+ if (chainId === SOLANA_CHAIN_ID) return SOLANA_ADDRESS_REGEX.test(address);
1009
+ return EVM_ADDRESS_REGEX.test(address);
1010
+ };
1011
+ /**
1012
+ * Classify a recipient address by chain family so the network picker can
1013
+ * auto-select the matching chain and disable the others. EVM addresses are
1014
+ * unambiguous (`0x…` — `0` isn't in the base58 alphabet, so they can never
1015
+ * match the Solana regex), so we test EVM first. Returns null until the input
1016
+ * is a structurally complete address of either family — partial/garbage input
1017
+ * leaves every network enabled.
1018
+ */
1019
+ const detectDestinationAddressKind = (address) => {
1020
+ const trimmed = address.trim();
1021
+ if (!trimmed) return null;
1022
+ if (EVM_ADDRESS_REGEX.test(trimmed)) return "evm";
1023
+ if (SOLANA_ADDRESS_REGEX.test(trimmed)) return "solana";
1024
+ return null;
1025
+ };
1026
+ const DEFAULT_WITHDRAW_SUMMARY = {
1027
+ amountReceived: "",
1028
+ network: "",
1029
+ toWallet: "",
1030
+ fees: "—"
1031
+ };
1032
+ const formatRawTokenAmount = (rawAmount, decimals) => {
1033
+ if (!rawAmount) return "0";
1034
+ const raw = BigInt(rawAmount);
1035
+ const divisor = BigInt(`1${"0".repeat(decimals)}`);
1036
+ const whole = raw / divisor;
1037
+ const remainder = raw % divisor;
1038
+ if (remainder === BigInt(0)) return whole.toString();
1039
+ return `${whole.toString()}.${remainder.toString().padStart(decimals, "0").replace(/0+$/, "")}`;
1040
+ };
1041
+ const formatRawTokenAmountForDisplay = (rawAmount, decimals, fractionDigits = 2) => {
1042
+ const [wholePart, fractionalPart = ""] = formatRawTokenAmount(rawAmount, decimals).split(".");
1043
+ return `${wholePart}.${fractionalPart.padEnd(fractionDigits, "0").slice(0, fractionDigits)}`;
1044
+ };
1045
+ const parseTokenAmountToRaw = (amount, decimals) => {
1046
+ const normalizedAmount = amount.trim();
1047
+ if (!normalizedAmount || !/^\d*(?:\.\d*)?$/.test(normalizedAmount)) return null;
1048
+ const [wholePart = "0", fractionalPart = ""] = normalizedAmount.split(".");
1049
+ if (fractionalPart.length > decimals) return null;
1050
+ const normalizedWhole = wholePart === "" ? "0" : wholePart;
1051
+ const normalizedFraction = fractionalPart.padEnd(decimals, "0");
1052
+ try {
1053
+ return `${BigInt(normalizedWhole)}${normalizedFraction}`.replace(/^0+(?=\d)/, "");
1054
+ } catch {
1055
+ return null;
1056
+ }
1057
+ };
1058
+ const shortenAddress = (address) => address.length > 12 ? `${address.slice(0, 6)}...${address.slice(-4)}` : address;
1059
+ function useWithdrawFlow(options) {
1060
+ const { open, onOpenChange } = options;
1061
+ const { totalBalance } = useAggBalanceState();
1062
+ const { balances } = useManagedBalances({ enabled: open });
1063
+ const { supportedChains } = useDepositAddresses({ enabled: open });
1064
+ const withdrawMutation = useWithdrawManaged();
1065
+ const syncBalances = useSyncBalances();
1066
+ const ws = useAggWebSocket();
1067
+ useEffect(() => {
1068
+ if (!open) return;
1069
+ syncBalances.mutate(void 0, { onError: () => {} });
1070
+ }, [open]);
1071
+ const [withdrawDestination, setWithdrawDestination] = useState("");
1072
+ const [withdrawAmount, setWithdrawAmount] = useState("");
1073
+ const [withdrawToken, setWithdrawToken] = useState("USDC");
1074
+ const [withdrawNetwork, setWithdrawNetwork] = useState("");
1075
+ const [withdrawSummary, setWithdrawSummary] = useState(DEFAULT_WITHDRAW_SUMMARY);
1076
+ const [withdrawalId, setWithdrawalId] = useState(null);
1077
+ const [isMax, setIsMax] = useState(false);
1078
+ const detectedAddressKind = useMemo(() => detectDestinationAddressKind(withdrawDestination), [withdrawDestination]);
1079
+ const networkOptions = useMemo(() => (supportedChains ?? []).filter((chain) => WITHDRAWAL_SUPPORTED_CHAIN_IDS.has(chain.chainId)).map((chain) => {
1080
+ const value = String(chain.chainId);
1081
+ const isSolana = chain.chainId === SOLANA_CHAIN_ID;
1082
+ const disabled = detectedAddressKind === "solana" ? !isSolana : detectedAddressKind === "evm" ? isSolana : false;
1083
+ return {
1084
+ value,
1085
+ label: chain.name,
1086
+ disabled
1087
+ };
1088
+ }), [supportedChains, detectedAddressKind]);
1089
+ const firstEnabledNetwork = networkOptions.find((option) => !option.disabled)?.value ?? networkOptions[0]?.value ?? "";
1090
+ const isCurrentNetworkSelectable = networkOptions.some((option) => option.value === withdrawNetwork && !option.disabled);
1091
+ const resolvedWithdrawNetwork = isCurrentNetworkSelectable ? withdrawNetwork : firstEnabledNetwork;
1092
+ useEffect(() => {
1093
+ if (!networkOptions.length) return;
1094
+ if (!isCurrentNetworkSelectable && firstEnabledNetwork) setWithdrawNetwork(firstEnabledNetwork);
1095
+ }, [
1096
+ networkOptions,
1097
+ isCurrentNetworkSelectable,
1098
+ firstEnabledNetwork
1099
+ ]);
1100
+ const tokenOptions = useMemo(() => {
1101
+ const selectedChainId = Number(resolvedWithdrawNetwork);
1102
+ const supportedTokensForNetwork = supportedChains?.find((chain) => chain.chainId === selectedChainId)?.tokens ?? [];
1103
+ const withdrawableSymbols = new Set(supportedTokensForNetwork.map((token) => token.symbol));
1104
+ return (balances?.cash ?? []).filter((cashEntry) => withdrawableSymbols.has(cashEntry.tokenSymbol)).map((cashEntry) => ({
1105
+ value: cashEntry.tokenSymbol,
1106
+ label: cashEntry.tokenSymbol
1107
+ }));
1108
+ }, [
1109
+ balances,
1110
+ resolvedWithdrawNetwork,
1111
+ supportedChains
1112
+ ]);
1113
+ const resolvedWithdrawToken = tokenOptions.some((option) => option.value === withdrawToken) ? withdrawToken : tokenOptions[0]?.value || "";
1114
+ useEffect(() => {
1115
+ if (!tokenOptions.length) return;
1116
+ if (!tokenOptions.some((option) => option.value === withdrawToken)) setWithdrawToken(tokenOptions[0].value);
1117
+ }, [tokenOptions, withdrawToken]);
1118
+ const selectedCashEntry = useMemo(() => balances?.cash.find((cashEntry) => cashEntry.tokenSymbol === resolvedWithdrawToken), [balances, resolvedWithdrawToken]);
1119
+ const selectedTokenDecimals = useMemo(() => {
1120
+ const selectedChainId = Number(resolvedWithdrawNetwork);
1121
+ return supportedChains?.find((chain) => chain.chainId === selectedChainId)?.tokens.find((token) => token.symbol === resolvedWithdrawToken)?.decimals ?? selectedCashEntry?.decimals ?? 6;
1122
+ }, [
1123
+ resolvedWithdrawNetwork,
1124
+ resolvedWithdrawToken,
1125
+ selectedCashEntry?.decimals,
1126
+ supportedChains
1127
+ ]);
1128
+ const exactBalance = useMemo(() => {
1129
+ if (!selectedCashEntry) return "0";
1130
+ return formatRawTokenAmount(selectedCashEntry.totalRaw, selectedCashEntry.decimals);
1131
+ }, [selectedCashEntry]);
1132
+ const balanceDisplay = useMemo(() => {
1133
+ if (!selectedCashEntry) return `0.00 ${resolvedWithdrawToken || withdrawToken}`;
1134
+ return `${formatRawTokenAmountForDisplay(selectedCashEntry.totalRaw, selectedCashEntry.decimals)} ${resolvedWithdrawToken}`;
1135
+ }, [
1136
+ resolvedWithdrawToken,
1137
+ selectedCashEntry,
1138
+ withdrawToken
1139
+ ]);
1140
+ const resetFlowState = useCallback(() => {
1141
+ setWithdrawDestination("");
1142
+ setWithdrawAmount("");
1143
+ setWithdrawToken("USDC");
1144
+ setWithdrawNetwork("");
1145
+ setWithdrawSummary(DEFAULT_WITHDRAW_SUMMARY);
1146
+ setWithdrawalId(null);
1147
+ setIsMax(false);
1148
+ }, []);
1149
+ const handleWithdrawOpenChange = useCallback((isOpen) => {
1150
+ if (!isOpen) resetFlowState();
1151
+ onOpenChange(isOpen);
1152
+ }, [onOpenChange, resetFlowState]);
1153
+ useEffect(() => {
1154
+ if (!open) resetFlowState();
1155
+ }, [open, resetFlowState]);
1156
+ const handleWithdrawProvider = useCallback(async () => {
1157
+ const destinationChainId = Number(resolvedWithdrawNetwork);
1158
+ const trimmedDestination = withdrawDestination.trim();
1159
+ const amountRaw = parseTokenAmountToRaw(withdrawAmount, selectedTokenDecimals);
1160
+ if (!amountRaw || BigInt(amountRaw) <= BigInt(0)) throw new Error("Enter a withdrawal amount greater than zero.");
1161
+ if (!Number.isFinite(destinationChainId) || destinationChainId <= 0) throw new Error("Select a destination network.");
1162
+ if (!isValidDestinationAddress(trimmedDestination, destinationChainId)) throw new Error(`Enter ${destinationChainId === SOLANA_CHAIN_ID ? "a Solana wallet address (base58, 32–44 chars)" : "an EVM destination address (0x… 40 hex chars)"}.`);
1163
+ if (selectedCashEntry) {
1164
+ const scaleBy = (n, exp) => {
1165
+ if (exp <= 0) return n;
1166
+ return n * BigInt(`1${"0".repeat(exp)}`);
1167
+ };
1168
+ const balanceInDestFrame = (() => {
1169
+ const native = BigInt(selectedCashEntry.totalRaw);
1170
+ if (selectedCashEntry.decimals === selectedTokenDecimals) return native;
1171
+ if (selectedCashEntry.decimals > selectedTokenDecimals) return native / scaleBy(BigInt(1), selectedCashEntry.decimals - selectedTokenDecimals);
1172
+ return scaleBy(native, selectedTokenDecimals - selectedCashEntry.decimals);
1173
+ })();
1174
+ if (!isMax && BigInt(amountRaw) > balanceInDestFrame) throw new Error("Withdrawal amount exceeds your available balance.");
1175
+ }
1176
+ ws?.connect();
1177
+ await new Promise((resolve, reject) => {
1178
+ withdrawMutation.mutate({
1179
+ amountRaw,
1180
+ tokenSymbol: resolvedWithdrawToken,
1181
+ destinationAddress: trimmedDestination,
1182
+ destinationChainId,
1183
+ ...isMax ? { max: true } : {}
1184
+ }, {
1185
+ onSuccess: (data) => {
1186
+ setWithdrawalId(data.withdrawalId);
1187
+ resolve();
1188
+ },
1189
+ onError: reject
1190
+ });
1191
+ });
1192
+ setWithdrawSummary({
1193
+ amountReceived: `${formatRawTokenAmountForDisplay(amountRaw, selectedTokenDecimals)} ${resolvedWithdrawToken}`,
1194
+ network: supportedChains?.find((chain) => chain.chainId === destinationChainId)?.name ?? resolvedWithdrawNetwork,
1195
+ toWallet: shortenAddress(trimmedDestination),
1196
+ fees: "—"
1197
+ });
1198
+ }, [
1199
+ isMax,
1200
+ resolvedWithdrawNetwork,
1201
+ resolvedWithdrawToken,
1202
+ selectedCashEntry,
1203
+ selectedTokenDecimals,
1204
+ supportedChains,
1205
+ withdrawAmount,
1206
+ withdrawDestination,
1207
+ withdrawMutation,
1208
+ ws
1209
+ ]);
1210
+ return {
1211
+ open,
1212
+ onOpenChange: handleWithdrawOpenChange,
1213
+ withdrawFlow: {
1214
+ balance: totalBalance,
1215
+ balanceDisplay,
1216
+ amount: withdrawAmount,
1217
+ destinationWallet: withdrawDestination,
1218
+ tokenOptions,
1219
+ networkOptions,
1220
+ selectedToken: resolvedWithdrawToken,
1221
+ selectedNetwork: resolvedWithdrawNetwork,
1222
+ purchaseSummary: withdrawSummary,
1223
+ withdrawalId,
1224
+ isMax
1225
+ },
1226
+ onWithdrawDestinationChange: setWithdrawDestination,
1227
+ onWithdrawAmountChange: useCallback((v) => {
1228
+ setIsMax(false);
1229
+ setWithdrawAmount(v);
1230
+ }, []),
1231
+ onWithdrawTokenChange: setWithdrawToken,
1232
+ onWithdrawNetworkChange: setWithdrawNetwork,
1233
+ onMaxClick: useCallback(() => {
1234
+ setIsMax(true);
1235
+ setWithdrawAmount(exactBalance === "0" ? "0" : exactBalance);
1236
+ }, [exactBalance]),
1237
+ onSelectWithdrawProvider: useCallback(async (_providerId) => handleWithdrawProvider(), [handleWithdrawProvider]),
1238
+ onDoneWithdraw: useCallback(() => handleWithdrawOpenChange(false), [handleWithdrawOpenChange])
1239
+ };
1240
+ }
1241
+ //#endregion
1242
+ //#region src/withdraw/use-withdraw-estimate.ts
1243
+ /**
1244
+ * Natively estimates withdrawal fees, network reserves, and the expected net
1245
+ * amount to be received based on the selected token, destination network,
1246
+ * and transfer amount.
1247
+ */
1248
+ function useWithdrawEstimate({ amount, selectedToken, selectedNetwork }) {
1249
+ return useMemo(() => {
1250
+ const numAmount = Number(amount);
1251
+ if (!amount || isNaN(numAmount) || numAmount <= 0 || !selectedToken || !selectedNetwork) return null;
1252
+ const network = selectedNetwork.toLowerCase();
1253
+ let feeVal = .1;
1254
+ let reserveVal = .2;
1255
+ if (network === "1" || network === "mainnet" || network === "ethereum") {
1256
+ feeVal = 1.22;
1257
+ reserveVal = .3;
1258
+ } else if (network === "137" || network === "polygon") {
1259
+ feeVal = .05;
1260
+ reserveVal = .15;
1261
+ } else if (network === "42161" || network === "arbitrum") {
1262
+ feeVal = .05;
1263
+ reserveVal = .15;
1264
+ } else if (network === "8453" || network === "base") {
1265
+ feeVal = .05;
1266
+ reserveVal = .15;
1267
+ } else if (network === "56" || network === "bnb" || network === "bsc") {
1268
+ feeVal = .1;
1269
+ reserveVal = .2;
1270
+ } else if (network === "792703809" || network === "solana") {
1271
+ feeVal = .01;
1272
+ reserveVal = .04;
1273
+ } else if (network === "1337" || network === "hyperliquid") {
1274
+ feeVal = .02;
1275
+ reserveVal = .08;
1276
+ }
1277
+ const youReceiveVal = Math.max(0, numAmount - feeVal);
1278
+ return {
1279
+ estimatedFees: `~$${feeVal.toFixed(2)}`,
1280
+ networkReserve: `~$${reserveVal.toFixed(2)}`,
1281
+ youReceive: `~${youReceiveVal.toFixed(2)} ${selectedToken}`
1282
+ };
1283
+ }, [
1284
+ amount,
1285
+ selectedToken,
1286
+ selectedNetwork
1287
+ ]);
1288
+ }
1289
+ //#endregion
1290
+ //#region src/withdraw/use-withdrawal-lifecycle.ts
1291
+ const INITIAL_STATE = {
1292
+ pending: true,
1293
+ status: null,
1294
+ requestedAmountRaw: null,
1295
+ completedAmountRaw: null,
1296
+ feeRaw: null,
1297
+ terminal: false,
1298
+ lastLeg: null,
1299
+ legs: [],
1300
+ errorMessage: null,
1301
+ timestamp: null
1302
+ };
1303
+ /**
1304
+ * Convert a leg row from the REST status response into the WS lifecycle leg
1305
+ * shape so a single state mapping handles both. The REST leg includes the
1306
+ * type/status fields the WS event omits in the legs[] array — they line up
1307
+ * cleanly because the WS leg type is a superset.
1308
+ */
1309
+ const restLegToWsLeg = (leg) => ({
1310
+ sourceChainId: leg.sourceChainId,
1311
+ destChainId: leg.destChainId,
1312
+ type: leg.type,
1313
+ status: leg.status,
1314
+ amountRaw: leg.amountRaw,
1315
+ txHash: leg.txHash,
1316
+ bridgeOperationId: leg.bridgeOperationId
1317
+ });
1318
+ /**
1319
+ * Merge the new event's leg-state into the existing `legs[]`.
1320
+ *
1321
+ * - If the event carries a full `legs[]` snapshot (pending / terminal
1322
+ * rollup), it replaces — the server's snapshot is authoritative.
1323
+ * - If it carries only a `leg` delta, find the matching row by
1324
+ * (sourceChainId, destChainId, type) and replace it; if no row matches
1325
+ * (the delta arrived before any snapshot listed the leg), append it.
1326
+ * This preserves the timeline UI between snapshots — without this,
1327
+ * intermediate `submitted`/`confirmed` deltas would wipe `legs[]` to
1328
+ * `[]` and the per-leg checklist would collapse mid-flow.
1329
+ * - If neither is present, leave the existing `legs[]` untouched.
1330
+ */
1331
+ const mergeLegs = (prev, snapshot, delta) => {
1332
+ if (snapshot) return snapshot;
1333
+ if (!delta) return prev;
1334
+ const idx = prev.findIndex((l) => l.sourceChainId === delta.sourceChainId && l.destChainId === delta.destChainId && l.type === delta.type);
1335
+ if (idx === -1) return [...prev, delta];
1336
+ const next = prev.slice();
1337
+ next[idx] = delta;
1338
+ return next;
1339
+ };
1340
+ const restToLifecycleState = (response) => {
1341
+ return {
1342
+ pending: false,
1343
+ status: response.status,
1344
+ requestedAmountRaw: response.requested.amountRaw,
1345
+ completedAmountRaw: response.completedAmountRaw ?? null,
1346
+ feeRaw: response.expected.feeRaw ?? null,
1347
+ terminal: response.status === "completed" || response.status === "partial" || response.status === "failed",
1348
+ lastLeg: null,
1349
+ legs: response.legs.map(restLegToWsLeg),
1350
+ errorMessage: response.errorMessage ?? null,
1351
+ timestamp: 0
1352
+ };
1353
+ };
1354
+ /**
1355
+ * Subscribes to `withdrawal_lifecycle` WS events for a single `withdrawalId`
1356
+ * and exposes the latest snapshot. Returns a stable `pending` initial state
1357
+ * until the first matching event arrives.
1358
+ *
1359
+ * Pass `null` for `withdrawalId` to disable the subscription (e.g. before
1360
+ * the user has submitted a withdrawal). Switching the `withdrawalId` resets
1361
+ * the state so a stale snapshot from a prior withdrawal can never bleed
1362
+ * across.
1363
+ *
1364
+ * **Backfill behavior**: on mount with a non-null `withdrawalId`, and again
1365
+ * whenever the WS connection re-opens, the hook calls
1366
+ * `GET /execution/withdrawals/:id` to recover any lifecycle events that fired
1367
+ * before the listener was attached or while the socket was disconnected. WS
1368
+ * events always win over REST snapshots (REST stamps as `timestamp: 0`), so a
1369
+ * race between the two never regresses to a stale state.
1370
+ */
1371
+ function useWithdrawalLifecycle(withdrawalId) {
1372
+ const client = useAggClient();
1373
+ const wsConnected = useAggWebSocketConnectionState();
1374
+ const queryClient = useQueryClient();
1375
+ const [state, setState] = useState(INITIAL_STATE);
1376
+ const stateRef = useRef(state);
1377
+ stateRef.current = state;
1378
+ const balanceRefetchedForRef = useRef(null);
1379
+ useEffect(() => {
1380
+ setState(INITIAL_STATE);
1381
+ }, [withdrawalId]);
1382
+ useEffect(() => {
1383
+ if (!withdrawalId) return;
1384
+ let cancelled = false;
1385
+ (async () => {
1386
+ try {
1387
+ const response = await client.getWithdrawalStatus(withdrawalId);
1388
+ if (cancelled) return;
1389
+ if (response.withdrawalId !== withdrawalId) return;
1390
+ const current = stateRef.current;
1391
+ if (current.timestamp != null && current.timestamp > 0) return;
1392
+ setState(restToLifecycleState(response));
1393
+ } catch {}
1394
+ })();
1395
+ return () => {
1396
+ cancelled = true;
1397
+ };
1398
+ }, [
1399
+ client,
1400
+ withdrawalId,
1401
+ wsConnected
1402
+ ]);
1403
+ useOnWithdrawalLifecycle(useMemo(() => {
1404
+ if (!withdrawalId) return null;
1405
+ return (msg) => {
1406
+ if (msg.withdrawalId !== withdrawalId) return;
1407
+ setState((prev) => ({
1408
+ pending: false,
1409
+ status: msg.status,
1410
+ requestedAmountRaw: msg.requestedAmountRaw ?? prev.requestedAmountRaw,
1411
+ completedAmountRaw: msg.completedAmountRaw ?? prev.completedAmountRaw,
1412
+ feeRaw: msg.feeRaw ?? null,
1413
+ terminal: msg.terminal,
1414
+ lastLeg: msg.leg ?? null,
1415
+ legs: mergeLegs(prev.legs, msg.legs, msg.leg),
1416
+ errorMessage: msg.errorMessage ?? null,
1417
+ timestamp: msg.timestamp
1418
+ }));
1419
+ };
1420
+ }, [withdrawalId]));
1421
+ useEffect(() => {
1422
+ if (!withdrawalId) return;
1423
+ if (!state.terminal) return;
1424
+ if (balanceRefetchedForRef.current === withdrawalId) return;
1425
+ balanceRefetchedForRef.current = withdrawalId;
1426
+ invalidateUserMoneyState(queryClient);
1427
+ client.syncManagedBalances().catch(() => {});
1428
+ }, [
1429
+ client,
1430
+ queryClient,
1431
+ state.terminal,
1432
+ withdrawalId
1433
+ ]);
1434
+ return {
1435
+ state,
1436
+ reset: useCallback(() => setState(INITIAL_STATE), [])
1437
+ };
1438
+ }
1439
+ //#endregion
1440
+ //#region src/withdraw/use-withdraw-preview.ts
1441
+ /**
1442
+ * Live withdrawal preview backed by the API (`POST /execution/withdraw/preview`).
1443
+ * Disabled until amount/token/chain/address are present and amount is non-zero.
1444
+ * The backend caches identical previews for 15s; FE debounce keeps Relay calls
1445
+ * bounded. Returns the real fee + receive amount, replacing the hardcoded
1446
+ * `useWithdrawEstimate`.
1447
+ */
1448
+ function useWithdrawPreview({ amountRaw, tokenSymbol, destinationChainId, destinationAddress, max }) {
1449
+ const client = useAggClient();
1450
+ const enabled = !!amountRaw && BigInt(amountRaw || "0") > BigInt(0) && !!tokenSymbol && !!destinationChainId && !!destinationAddress;
1451
+ return useQuery({
1452
+ queryKey: [
1453
+ "withdraw-preview",
1454
+ tokenSymbol,
1455
+ amountRaw,
1456
+ destinationChainId,
1457
+ destinationAddress,
1458
+ max ?? false
1459
+ ],
1460
+ enabled,
1461
+ staleTime: 15e3,
1462
+ queryFn: () => client.withdrawPreview({
1463
+ amountRaw,
1464
+ tokenSymbol,
1465
+ destinationChainId,
1466
+ destinationAddress,
1467
+ ...max ? { max: true } : {}
1468
+ })
1469
+ });
1470
+ }
1471
+ //#endregion
1472
+ export { marketDataKeys as _, useManagedBalances as a, getOrCreateBuilder as b, useAggWebSocket as c, useOnOrderEvent as d, useOnOrderSubmitted as f, useLiveStates as g, getMarketLiveStore as h, useWithdrawFlow as i, useAggWebSocketConnectionState as l, useOnWithdrawalLifecycle as m, useWithdrawalLifecycle as n, useWithdrawManaged as o, useOnRedeemEvent as p, useWithdrawEstimate as r, AggWebSocketProvider as s, useWithdrawPreview as t, useOnBalanceUpdate as u, createMarketLiveState as v, getBuilder as y };
1473
+
1474
+ //# sourceMappingURL=withdraw--f5kYKJA.mjs.map