@agg-build/hooks 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{chunk-CDPQERUC.mjs → chunk-BWXNOWAS.mjs} +99 -36
- package/dist/chunk-PALQVBAQ.mjs +31 -0
- package/dist/chunk-QXBQRWFF.mjs +389 -0
- package/dist/deposit.d.mts +2 -1
- package/dist/deposit.d.ts +2 -1
- package/dist/deposit.js +57 -2
- package/dist/deposit.mjs +5 -3
- package/dist/index.d.mts +71 -193
- package/dist/index.d.ts +71 -193
- package/dist/index.js +663 -167
- package/dist/index.mjs +78 -37
- package/dist/{use-sync-balances-D1Jdkck9.d.mts → use-deposit-addresses-B9ICS-3U.d.mts} +1 -11
- package/dist/{use-sync-balances-D1Jdkck9.d.ts → use-deposit-addresses-B9ICS-3U.d.ts} +1 -11
- package/dist/use-sync-balances-B1_8tBKw.d.mts +14 -0
- package/dist/use-sync-balances-B1_8tBKw.d.ts +14 -0
- package/dist/withdraw.d.mts +293 -0
- package/dist/withdraw.d.ts +293 -0
- package/dist/withdraw.js +1402 -0
- package/dist/withdraw.mjs +16 -0
- package/package.json +15 -2
package/README.md
CHANGED
|
@@ -393,7 +393,7 @@ Optional (only needed for wallet-based sign-in / deposits):
|
|
|
393
393
|
## Links
|
|
394
394
|
|
|
395
395
|
- Documentation — <https://docs.agg.market/>
|
|
396
|
-
- Demo app — <https://
|
|
396
|
+
- Demo app — <https://agg.market/>
|
|
397
397
|
- Vanilla SDK — [`@agg-build/sdk`](https://www.npmjs.com/package/@agg-build/sdk)
|
|
398
398
|
- Trading UI — [`@agg-build/ui`](https://www.npmjs.com/package/@agg-build/ui)
|
|
399
399
|
- Connect / sign-in UX — [`@agg-build/auth`](https://www.npmjs.com/package/@agg-build/auth)
|
|
@@ -320,6 +320,8 @@ var enUsLabels = {
|
|
|
320
320
|
successDescription: "Your USDC has been successfully added to your balance.",
|
|
321
321
|
pendingTitle: (provider) => `Complete your payment on ${provider}`,
|
|
322
322
|
pendingDescription: "Once your transaction is finished, your balance may take a few minutes to update. The deposit will appear in your activity once it's successful.",
|
|
323
|
+
pendingWalletAddressHelp: "Some providers may ask for a wallet address during checkout, use this one to receive your deposit:",
|
|
324
|
+
pendingCopyAddress: "Copy deposit address",
|
|
323
325
|
viewActivity: "View Activity",
|
|
324
326
|
chooseAnotherProvider: "Choose another provider",
|
|
325
327
|
summary: {
|
|
@@ -356,9 +358,55 @@ var enUsLabels = {
|
|
|
356
358
|
done: "Done",
|
|
357
359
|
balancePrefix: "Balance:",
|
|
358
360
|
methods: {
|
|
361
|
+
walletTitle: "Withdraw to wallet",
|
|
362
|
+
walletDescription: "Withdraw funds instantly to your crypto wallet",
|
|
359
363
|
cardTitle: "Withdraw with card",
|
|
360
364
|
cardDescription: "Withdraw funds to your card"
|
|
361
365
|
},
|
|
366
|
+
walletFlow: {
|
|
367
|
+
title: "Withdraw to wallet",
|
|
368
|
+
recipientAddressLabel: "Recipient address",
|
|
369
|
+
amountLabel: "Amount",
|
|
370
|
+
max: "Max",
|
|
371
|
+
tokenLabel: "Receive token",
|
|
372
|
+
networkLabel: "Receive network",
|
|
373
|
+
confirm: "Confirm withdrawal",
|
|
374
|
+
successTitle: "Withdrawal submitted",
|
|
375
|
+
successDescription: (tokenSymbol) => `Your ${tokenSymbol} withdrawal is being processed and will arrive shortly.`,
|
|
376
|
+
// Terminal-state copy. The success step swaps `successTitle` /
|
|
377
|
+
// `successDescription` for these once the lifecycle has reached a
|
|
378
|
+
// terminal status — otherwise a finished withdrawal would keep showing
|
|
379
|
+
// "submitted / processing" forever and force the user to hard-refresh.
|
|
380
|
+
successTitleCompleted: "Withdrawal complete",
|
|
381
|
+
successDescriptionCompleted: (tokenSymbol) => `Your ${tokenSymbol} withdrawal has been delivered.`,
|
|
382
|
+
successTitlePartial: "Withdrawal partially completed",
|
|
383
|
+
successDescriptionPartial: (tokenSymbol) => `Some legs of your ${tokenSymbol} withdrawal completed; see details below.`,
|
|
384
|
+
successTitleFailed: "Withdrawal failed",
|
|
385
|
+
successDescriptionFailed: (tokenSymbol) => `Your ${tokenSymbol} withdrawal could not be completed.`,
|
|
386
|
+
summary: {
|
|
387
|
+
// The response is `pricingStatus: "unquoted"` — we don't know net
|
|
388
|
+
// output until on-chain settlement. Calling this "Amount received"
|
|
389
|
+
// would imply receipt before the lifecycle has confirmed. Keep it
|
|
390
|
+
// honest as the submitted amount until the multi-stable quote
|
|
391
|
+
// layer (PR-E) populates `expected.outputRaw`.
|
|
392
|
+
amountReceived: "Amount",
|
|
393
|
+
network: "Network",
|
|
394
|
+
toWallet: "To wallet",
|
|
395
|
+
fees: "Fees"
|
|
396
|
+
},
|
|
397
|
+
lifecycle: {
|
|
398
|
+
pending: "Submitted \u2014 preparing your withdrawal\u2026",
|
|
399
|
+
bridging: "Bridging funds across chains\u2026",
|
|
400
|
+
transferring: "Transferring to your wallet\u2026",
|
|
401
|
+
completed: "Withdrawal complete.",
|
|
402
|
+
partial: "Withdrawal partially completed \u2014 see details below.",
|
|
403
|
+
failed: "Withdrawal failed.",
|
|
404
|
+
steps: {
|
|
405
|
+
bridge: (sourceChainName, destChainName) => `Bridging from ${sourceChainName} to ${destChainName}`,
|
|
406
|
+
transfer: (destChainName) => `Transferring on ${destChainName}`
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
},
|
|
362
410
|
cardFlow: {
|
|
363
411
|
title: "Sell crypto",
|
|
364
412
|
amountLabel: "Amount",
|
|
@@ -382,7 +430,10 @@ var enUsLabels = {
|
|
|
382
430
|
}
|
|
383
431
|
},
|
|
384
432
|
summary: {
|
|
385
|
-
|
|
433
|
+
// Lifecycle-honest: until quoting is live (PR-E) the response is
|
|
434
|
+
// `pricingStatus: "unquoted"` so this is the submitted amount, not
|
|
435
|
+
// a guaranteed net output.
|
|
436
|
+
amountReceived: "Amount",
|
|
386
437
|
network: "Network"
|
|
387
438
|
}
|
|
388
439
|
},
|
|
@@ -399,12 +450,16 @@ var enUsLabels = {
|
|
|
399
450
|
userProfile: {
|
|
400
451
|
activity: {
|
|
401
452
|
depositType: "Deposit",
|
|
402
|
-
withdrawalType: "
|
|
453
|
+
withdrawalType: "Withdrawal",
|
|
403
454
|
depositTitles: {
|
|
404
455
|
connectedWallet: "Deposit from connected wallet",
|
|
405
456
|
externalWallet: "Deposit from external wallet",
|
|
406
457
|
card: "Deposit with card"
|
|
407
458
|
},
|
|
459
|
+
// Activity-row title for any withdrawal regardless of lifecycle
|
|
460
|
+
// state (pending / completed / failed) — render the asset rather
|
|
461
|
+
// than implying success. The ActivityRow renders a separate status
|
|
462
|
+
// chip when the row is failed.
|
|
408
463
|
withdrawalTitle: (tokenSymbol) => `Withdraw ${tokenSymbol}`
|
|
409
464
|
},
|
|
410
465
|
positions: {
|
|
@@ -1690,6 +1745,20 @@ function useOnRedeemEvent(callback) {
|
|
|
1690
1745
|
return listeners.addRedeemEventListener(handler);
|
|
1691
1746
|
}, [listeners, hasCallback]);
|
|
1692
1747
|
}
|
|
1748
|
+
function useOnWithdrawalLifecycle(callback) {
|
|
1749
|
+
const listeners = useContext4(AggWsUserEventContext);
|
|
1750
|
+
const callbackRef = useRef2(callback);
|
|
1751
|
+
callbackRef.current = callback;
|
|
1752
|
+
const hasCallback = callback !== null;
|
|
1753
|
+
useEffect(() => {
|
|
1754
|
+
if (!listeners || !callbackRef.current) return;
|
|
1755
|
+
const handler = (msg) => {
|
|
1756
|
+
var _a;
|
|
1757
|
+
(_a = callbackRef.current) == null ? void 0 : _a.call(callbackRef, msg);
|
|
1758
|
+
};
|
|
1759
|
+
return listeners.addWithdrawalLifecycleListener(handler);
|
|
1760
|
+
}, [listeners, hasCallback]);
|
|
1761
|
+
}
|
|
1693
1762
|
function AggWebSocketProvider({ children }) {
|
|
1694
1763
|
const client = useAggClient();
|
|
1695
1764
|
const { enableWebsocketsLogs } = useAggUiConfig();
|
|
@@ -1710,6 +1779,9 @@ function AggWebSocketProvider({ children }) {
|
|
|
1710
1779
|
const balanceUpdateListenersRef = useRef2(/* @__PURE__ */ new Set());
|
|
1711
1780
|
const orderEventListenersRef = useRef2(/* @__PURE__ */ new Set());
|
|
1712
1781
|
const redeemEventListenersRef = useRef2(/* @__PURE__ */ new Set());
|
|
1782
|
+
const withdrawalLifecycleListenersRef = useRef2(
|
|
1783
|
+
/* @__PURE__ */ new Set()
|
|
1784
|
+
);
|
|
1713
1785
|
const userEventListeners = useMemo2(
|
|
1714
1786
|
() => ({
|
|
1715
1787
|
addOrderSubmittedListener: (cb) => {
|
|
@@ -1735,6 +1807,12 @@ function AggWebSocketProvider({ children }) {
|
|
|
1735
1807
|
return () => {
|
|
1736
1808
|
redeemEventListenersRef.current.delete(cb);
|
|
1737
1809
|
};
|
|
1810
|
+
},
|
|
1811
|
+
addWithdrawalLifecycleListener: (cb) => {
|
|
1812
|
+
withdrawalLifecycleListenersRef.current.add(cb);
|
|
1813
|
+
return () => {
|
|
1814
|
+
withdrawalLifecycleListenersRef.current.delete(cb);
|
|
1815
|
+
};
|
|
1738
1816
|
}
|
|
1739
1817
|
}),
|
|
1740
1818
|
[]
|
|
@@ -1909,6 +1987,14 @@ function AggWebSocketProvider({ children }) {
|
|
|
1909
1987
|
listener(msg);
|
|
1910
1988
|
}
|
|
1911
1989
|
},
|
|
1990
|
+
onWithdrawalLifecycle: (msg) => {
|
|
1991
|
+
if (isClientAuthenticated) {
|
|
1992
|
+
invalidateBalanceCaches();
|
|
1993
|
+
}
|
|
1994
|
+
for (const listener of withdrawalLifecycleListenersRef.current) {
|
|
1995
|
+
listener(msg);
|
|
1996
|
+
}
|
|
1997
|
+
},
|
|
1912
1998
|
onError: (msg) => {
|
|
1913
1999
|
const outcomeId = resolveSnapshotUnavailableOutcomeId(msg.message);
|
|
1914
2000
|
if (!outcomeId) return;
|
|
@@ -2989,33 +3075,11 @@ var AggProvider = ({ client, config, children }) => {
|
|
|
2989
3075
|
return /* @__PURE__ */ jsx8(AggClientProvider, { client, children: /* @__PURE__ */ jsx8(AggUiProvider, { config, children: /* @__PURE__ */ jsx8(EventListStateProvider, { children: /* @__PURE__ */ jsx8(AggWebSocketProvider, { children: /* @__PURE__ */ jsx8(AggAuthProvider, { children: /* @__PURE__ */ jsx8(AggBalanceProvider, { children }) }) }) }) }) });
|
|
2990
3076
|
};
|
|
2991
3077
|
|
|
2992
|
-
// src/use-ramp-quotes.ts
|
|
2993
|
-
import { useMutation } from "@tanstack/react-query";
|
|
2994
|
-
function useRampQuotes() {
|
|
2995
|
-
const client = useAggClient();
|
|
2996
|
-
return useMutation({
|
|
2997
|
-
mutationFn: (params) => __async(null, null, function* () {
|
|
2998
|
-
return client.getRampQuotes(params);
|
|
2999
|
-
})
|
|
3000
|
-
});
|
|
3001
|
-
}
|
|
3002
|
-
|
|
3003
|
-
// src/use-ramp-session.ts
|
|
3004
|
-
import { useMutation as useMutation2 } from "@tanstack/react-query";
|
|
3005
|
-
function useRampSession() {
|
|
3006
|
-
const client = useAggClient();
|
|
3007
|
-
return useMutation2({
|
|
3008
|
-
mutationFn: (params) => __async(null, null, function* () {
|
|
3009
|
-
return client.createRampSession(params);
|
|
3010
|
-
})
|
|
3011
|
-
});
|
|
3012
|
-
}
|
|
3013
|
-
|
|
3014
3078
|
// src/execution/use-quote-managed.ts
|
|
3015
|
-
import { useMutation
|
|
3079
|
+
import { useMutation } from "@tanstack/react-query";
|
|
3016
3080
|
function useQuoteManaged(options) {
|
|
3017
3081
|
const client = useAggClient();
|
|
3018
|
-
return
|
|
3082
|
+
return useMutation({
|
|
3019
3083
|
mutationFn: (params) => client.quoteManaged(params),
|
|
3020
3084
|
onSuccess: options == null ? void 0 : options.onSuccess,
|
|
3021
3085
|
onError: options == null ? void 0 : options.onError
|
|
@@ -3023,11 +3087,11 @@ function useQuoteManaged(options) {
|
|
|
3023
3087
|
}
|
|
3024
3088
|
|
|
3025
3089
|
// src/execution/use-execute-managed.ts
|
|
3026
|
-
import { useMutation as
|
|
3090
|
+
import { useMutation as useMutation2, useQueryClient as useQueryClient3 } from "@tanstack/react-query";
|
|
3027
3091
|
function useExecuteManaged(options) {
|
|
3028
3092
|
const client = useAggClient();
|
|
3029
3093
|
const queryClient = useQueryClient3();
|
|
3030
|
-
return
|
|
3094
|
+
return useMutation2({
|
|
3031
3095
|
mutationFn: (params) => client.executeManaged(params),
|
|
3032
3096
|
onSuccess: (data) => {
|
|
3033
3097
|
var _a;
|
|
@@ -3041,11 +3105,11 @@ function useExecuteManaged(options) {
|
|
|
3041
3105
|
}
|
|
3042
3106
|
|
|
3043
3107
|
// src/execution/use-withdraw-managed.ts
|
|
3044
|
-
import { useMutation as
|
|
3108
|
+
import { useMutation as useMutation3, useQueryClient as useQueryClient4 } from "@tanstack/react-query";
|
|
3045
3109
|
function useWithdrawManaged(options) {
|
|
3046
3110
|
const client = useAggClient();
|
|
3047
3111
|
const queryClient = useQueryClient4();
|
|
3048
|
-
return
|
|
3112
|
+
return useMutation3({
|
|
3049
3113
|
mutationFn: (params) => client.withdrawManaged(params),
|
|
3050
3114
|
onSuccess: (data) => {
|
|
3051
3115
|
var _a;
|
|
@@ -3153,11 +3217,11 @@ function useDepositAddresses(options) {
|
|
|
3153
3217
|
}
|
|
3154
3218
|
|
|
3155
3219
|
// src/execution/use-sync-balances.ts
|
|
3156
|
-
import { useMutation as
|
|
3220
|
+
import { useMutation as useMutation4, useQueryClient as useQueryClient5 } from "@tanstack/react-query";
|
|
3157
3221
|
function useSyncBalances(options) {
|
|
3158
3222
|
const client = useAggClient();
|
|
3159
3223
|
const queryClient = useQueryClient5();
|
|
3160
|
-
return
|
|
3224
|
+
return useMutation4({
|
|
3161
3225
|
mutationFn: () => client.syncManagedBalances(),
|
|
3162
3226
|
onSuccess: () => {
|
|
3163
3227
|
var _a;
|
|
@@ -3465,11 +3529,11 @@ var computeClosedPositionTotals = (group) => {
|
|
|
3465
3529
|
};
|
|
3466
3530
|
|
|
3467
3531
|
// src/execution/use-redeem.ts
|
|
3468
|
-
import { useMutation as
|
|
3532
|
+
import { useMutation as useMutation5, useQueryClient as useQueryClient6 } from "@tanstack/react-query";
|
|
3469
3533
|
var useRedeem = () => {
|
|
3470
3534
|
const client = useAggClient();
|
|
3471
3535
|
const queryClient = useQueryClient6();
|
|
3472
|
-
return
|
|
3536
|
+
return useMutation5({
|
|
3473
3537
|
mutationFn: (body) => client.redeem(body),
|
|
3474
3538
|
onSuccess: () => {
|
|
3475
3539
|
queryClient.invalidateQueries({ queryKey: executionKeys.positionsPrefix() });
|
|
@@ -3571,6 +3635,7 @@ export {
|
|
|
3571
3635
|
useAggWebSocketConnectionState,
|
|
3572
3636
|
useOnOrderSubmitted,
|
|
3573
3637
|
useOnBalanceUpdate,
|
|
3638
|
+
useOnWithdrawalLifecycle,
|
|
3574
3639
|
DEFAULT_AGG_ROOT_CLASS_NAME,
|
|
3575
3640
|
CHART_TIME_RANGES,
|
|
3576
3641
|
resolveTradingStateKind,
|
|
@@ -3583,8 +3648,6 @@ export {
|
|
|
3583
3648
|
useEventTradingContext,
|
|
3584
3649
|
AggUiProvider,
|
|
3585
3650
|
AggProvider,
|
|
3586
|
-
useRampQuotes,
|
|
3587
|
-
useRampSession,
|
|
3588
3651
|
useQuoteManaged,
|
|
3589
3652
|
useExecuteManaged,
|
|
3590
3653
|
useWithdrawManaged,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__async,
|
|
3
|
+
useAggClient
|
|
4
|
+
} from "./chunk-BWXNOWAS.mjs";
|
|
5
|
+
|
|
6
|
+
// src/use-ramp-quotes.ts
|
|
7
|
+
import { useMutation } from "@tanstack/react-query";
|
|
8
|
+
function useRampQuotes() {
|
|
9
|
+
const client = useAggClient();
|
|
10
|
+
return useMutation({
|
|
11
|
+
mutationFn: (params) => __async(null, null, function* () {
|
|
12
|
+
return client.getRampQuotes(params);
|
|
13
|
+
})
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/use-ramp-session.ts
|
|
18
|
+
import { useMutation as useMutation2 } from "@tanstack/react-query";
|
|
19
|
+
function useRampSession() {
|
|
20
|
+
const client = useAggClient();
|
|
21
|
+
return useMutation2({
|
|
22
|
+
mutationFn: (params) => __async(null, null, function* () {
|
|
23
|
+
return client.createRampSession(params);
|
|
24
|
+
})
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
useRampQuotes,
|
|
30
|
+
useRampSession
|
|
31
|
+
};
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__async,
|
|
3
|
+
invalidateBalanceQueries,
|
|
4
|
+
useAggBalanceState,
|
|
5
|
+
useAggClient,
|
|
6
|
+
useAggWebSocket,
|
|
7
|
+
useAggWebSocketConnectionState,
|
|
8
|
+
useDepositAddresses,
|
|
9
|
+
useManagedBalances,
|
|
10
|
+
useOnWithdrawalLifecycle,
|
|
11
|
+
useSyncBalances,
|
|
12
|
+
useWithdrawManaged
|
|
13
|
+
} from "./chunk-BWXNOWAS.mjs";
|
|
14
|
+
|
|
15
|
+
// src/withdraw/use-withdraw-flow.ts
|
|
16
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
17
|
+
var EVM_ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/;
|
|
18
|
+
var SOLANA_ADDRESS_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
19
|
+
var SOLANA_CHAIN_ID = 792703809;
|
|
20
|
+
var WITHDRAWAL_SUPPORTED_CHAIN_IDS = /* @__PURE__ */ new Set([
|
|
21
|
+
1,
|
|
22
|
+
// Ethereum
|
|
23
|
+
137,
|
|
24
|
+
// Polygon
|
|
25
|
+
42161,
|
|
26
|
+
// Arbitrum
|
|
27
|
+
8453,
|
|
28
|
+
// Base
|
|
29
|
+
56,
|
|
30
|
+
// BNB
|
|
31
|
+
SOLANA_CHAIN_ID
|
|
32
|
+
]);
|
|
33
|
+
var isValidDestinationAddress = (address, chainId) => {
|
|
34
|
+
if (chainId === SOLANA_CHAIN_ID) return SOLANA_ADDRESS_REGEX.test(address);
|
|
35
|
+
return EVM_ADDRESS_REGEX.test(address);
|
|
36
|
+
};
|
|
37
|
+
var DEFAULT_WITHDRAW_SUMMARY = {
|
|
38
|
+
amountReceived: "",
|
|
39
|
+
network: "",
|
|
40
|
+
toWallet: "",
|
|
41
|
+
fees: "\u2014"
|
|
42
|
+
};
|
|
43
|
+
var formatRawTokenAmount = (rawAmount, decimals) => {
|
|
44
|
+
if (!rawAmount) return "0";
|
|
45
|
+
const raw = BigInt(rawAmount);
|
|
46
|
+
const divisor = BigInt(`1${"0".repeat(decimals)}`);
|
|
47
|
+
const whole = raw / divisor;
|
|
48
|
+
const remainder = raw % divisor;
|
|
49
|
+
if (remainder === BigInt(0)) return whole.toString();
|
|
50
|
+
return `${whole.toString()}.${remainder.toString().padStart(decimals, "0").replace(/0+$/, "")}`;
|
|
51
|
+
};
|
|
52
|
+
var formatRawTokenAmountForDisplay = (rawAmount, decimals, fractionDigits = 2) => {
|
|
53
|
+
const [wholePart, fractionalPart = ""] = formatRawTokenAmount(rawAmount, decimals).split(".");
|
|
54
|
+
const normalizedFraction = fractionalPart.padEnd(fractionDigits, "0").slice(0, fractionDigits);
|
|
55
|
+
return `${wholePart}.${normalizedFraction}`;
|
|
56
|
+
};
|
|
57
|
+
var parseTokenAmountToRaw = (amount, decimals) => {
|
|
58
|
+
const normalizedAmount = amount.trim();
|
|
59
|
+
if (!normalizedAmount || !/^\d*(?:\.\d*)?$/.test(normalizedAmount)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const [wholePart = "0", fractionalPart = ""] = normalizedAmount.split(".");
|
|
63
|
+
if (fractionalPart.length > decimals) return null;
|
|
64
|
+
const normalizedWhole = wholePart === "" ? "0" : wholePart;
|
|
65
|
+
const normalizedFraction = fractionalPart.padEnd(decimals, "0");
|
|
66
|
+
try {
|
|
67
|
+
return `${BigInt(normalizedWhole)}${normalizedFraction}`.replace(/^0+(?=\d)/, "");
|
|
68
|
+
} catch (e) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var shortenAddress = (address) => address.length > 12 ? `${address.slice(0, 6)}....${address.slice(-4)}` : address;
|
|
73
|
+
function useWithdrawFlow(options) {
|
|
74
|
+
var _a, _b;
|
|
75
|
+
const { open, onOpenChange } = options;
|
|
76
|
+
const { totalBalance } = useAggBalanceState();
|
|
77
|
+
const { balances } = useManagedBalances({ enabled: open });
|
|
78
|
+
const { supportedChains } = useDepositAddresses({ enabled: open });
|
|
79
|
+
const withdrawMutation = useWithdrawManaged();
|
|
80
|
+
const syncBalances = useSyncBalances();
|
|
81
|
+
const ws = useAggWebSocket();
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (!open) return;
|
|
84
|
+
syncBalances.mutate(void 0, {
|
|
85
|
+
onError: () => {
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}, [open]);
|
|
89
|
+
const [withdrawDestination, setWithdrawDestination] = useState("");
|
|
90
|
+
const [withdrawAmount, setWithdrawAmount] = useState("");
|
|
91
|
+
const [withdrawToken, setWithdrawToken] = useState("USDC");
|
|
92
|
+
const [withdrawNetwork, setWithdrawNetwork] = useState("");
|
|
93
|
+
const [withdrawSummary, setWithdrawSummary] = useState(DEFAULT_WITHDRAW_SUMMARY);
|
|
94
|
+
const [withdrawalId, setWithdrawalId] = useState(null);
|
|
95
|
+
const networkOptions = useMemo(
|
|
96
|
+
() => (supportedChains != null ? supportedChains : []).filter((chain) => WITHDRAWAL_SUPPORTED_CHAIN_IDS.has(chain.chainId)).map((chain) => ({
|
|
97
|
+
value: String(chain.chainId),
|
|
98
|
+
label: chain.name
|
|
99
|
+
})),
|
|
100
|
+
[supportedChains]
|
|
101
|
+
);
|
|
102
|
+
const resolvedWithdrawNetwork = withdrawNetwork || ((_a = networkOptions[0]) == null ? void 0 : _a.value) || "";
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (!networkOptions.length) return;
|
|
105
|
+
if (!withdrawNetwork || !networkOptions.some((option) => option.value === withdrawNetwork)) {
|
|
106
|
+
setWithdrawNetwork(networkOptions[0].value);
|
|
107
|
+
}
|
|
108
|
+
}, [networkOptions, withdrawNetwork]);
|
|
109
|
+
const tokenOptions = useMemo(() => {
|
|
110
|
+
var _a2, _b2, _c;
|
|
111
|
+
const selectedChainId = Number(resolvedWithdrawNetwork);
|
|
112
|
+
const supportedTokensForNetwork = (_b2 = (_a2 = supportedChains == null ? void 0 : supportedChains.find((chain) => chain.chainId === selectedChainId)) == null ? void 0 : _a2.tokens) != null ? _b2 : [];
|
|
113
|
+
const withdrawableSymbols = new Set(supportedTokensForNetwork.map((token) => token.symbol));
|
|
114
|
+
return ((_c = balances == null ? void 0 : balances.cash) != null ? _c : []).filter((cashEntry) => withdrawableSymbols.has(cashEntry.tokenSymbol)).map((cashEntry) => ({
|
|
115
|
+
value: cashEntry.tokenSymbol,
|
|
116
|
+
label: cashEntry.tokenSymbol
|
|
117
|
+
}));
|
|
118
|
+
}, [balances, resolvedWithdrawNetwork, supportedChains]);
|
|
119
|
+
const resolvedWithdrawToken = tokenOptions.some((option) => option.value === withdrawToken) ? withdrawToken : ((_b = tokenOptions[0]) == null ? void 0 : _b.value) || "";
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (!tokenOptions.length) return;
|
|
122
|
+
if (!tokenOptions.some((option) => option.value === withdrawToken)) {
|
|
123
|
+
setWithdrawToken(tokenOptions[0].value);
|
|
124
|
+
}
|
|
125
|
+
}, [tokenOptions, withdrawToken]);
|
|
126
|
+
const selectedCashEntry = useMemo(
|
|
127
|
+
() => balances == null ? void 0 : balances.cash.find((cashEntry) => cashEntry.tokenSymbol === resolvedWithdrawToken),
|
|
128
|
+
[balances, resolvedWithdrawToken]
|
|
129
|
+
);
|
|
130
|
+
const selectedTokenDecimals = useMemo(() => {
|
|
131
|
+
var _a2, _b2, _c, _d;
|
|
132
|
+
const selectedChainId = Number(resolvedWithdrawNetwork);
|
|
133
|
+
return (_d = (_c = (_b2 = (_a2 = supportedChains == null ? void 0 : supportedChains.find((chain) => chain.chainId === selectedChainId)) == null ? void 0 : _a2.tokens.find((token) => token.symbol === resolvedWithdrawToken)) == null ? void 0 : _b2.decimals) != null ? _c : selectedCashEntry == null ? void 0 : selectedCashEntry.decimals) != null ? _d : 6;
|
|
134
|
+
}, [
|
|
135
|
+
resolvedWithdrawNetwork,
|
|
136
|
+
resolvedWithdrawToken,
|
|
137
|
+
selectedCashEntry == null ? void 0 : selectedCashEntry.decimals,
|
|
138
|
+
supportedChains
|
|
139
|
+
]);
|
|
140
|
+
const exactBalance = useMemo(() => {
|
|
141
|
+
if (!selectedCashEntry) return "0";
|
|
142
|
+
return formatRawTokenAmount(selectedCashEntry.totalRaw, selectedCashEntry.decimals);
|
|
143
|
+
}, [selectedCashEntry]);
|
|
144
|
+
const balanceDisplay = useMemo(() => {
|
|
145
|
+
if (!selectedCashEntry) return `0.00 ${resolvedWithdrawToken || withdrawToken}`;
|
|
146
|
+
return `${formatRawTokenAmountForDisplay(
|
|
147
|
+
selectedCashEntry.totalRaw,
|
|
148
|
+
selectedCashEntry.decimals
|
|
149
|
+
)} ${resolvedWithdrawToken}`;
|
|
150
|
+
}, [resolvedWithdrawToken, selectedCashEntry, withdrawToken]);
|
|
151
|
+
const resetFlowState = useCallback(() => {
|
|
152
|
+
setWithdrawDestination("");
|
|
153
|
+
setWithdrawAmount("");
|
|
154
|
+
setWithdrawToken("USDC");
|
|
155
|
+
setWithdrawNetwork("");
|
|
156
|
+
setWithdrawSummary(DEFAULT_WITHDRAW_SUMMARY);
|
|
157
|
+
setWithdrawalId(null);
|
|
158
|
+
}, []);
|
|
159
|
+
const handleWithdrawOpenChange = useCallback(
|
|
160
|
+
(isOpen) => {
|
|
161
|
+
if (!isOpen) resetFlowState();
|
|
162
|
+
onOpenChange(isOpen);
|
|
163
|
+
},
|
|
164
|
+
[onOpenChange, resetFlowState]
|
|
165
|
+
);
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
if (!open) resetFlowState();
|
|
168
|
+
}, [open, resetFlowState]);
|
|
169
|
+
const handleWithdrawProvider = useCallback(() => __async(null, null, function* () {
|
|
170
|
+
var _a2, _b2;
|
|
171
|
+
const destinationChainId = Number(resolvedWithdrawNetwork);
|
|
172
|
+
const trimmedDestination = withdrawDestination.trim();
|
|
173
|
+
const amountRaw = parseTokenAmountToRaw(withdrawAmount, selectedTokenDecimals);
|
|
174
|
+
if (!amountRaw || BigInt(amountRaw) <= BigInt(0)) {
|
|
175
|
+
throw new Error("Enter a withdrawal amount greater than zero.");
|
|
176
|
+
}
|
|
177
|
+
if (!Number.isFinite(destinationChainId) || destinationChainId <= 0) {
|
|
178
|
+
throw new Error("Select a destination network.");
|
|
179
|
+
}
|
|
180
|
+
if (!isValidDestinationAddress(trimmedDestination, destinationChainId)) {
|
|
181
|
+
const expected = destinationChainId === SOLANA_CHAIN_ID ? "a Solana wallet address (base58, 32\u201344 chars)" : "an EVM destination address (0x\u2026 40 hex chars)";
|
|
182
|
+
throw new Error(`Enter ${expected}.`);
|
|
183
|
+
}
|
|
184
|
+
if (selectedCashEntry) {
|
|
185
|
+
const scaleBy = (n, exp) => {
|
|
186
|
+
if (exp <= 0) return n;
|
|
187
|
+
return n * BigInt(`1${"0".repeat(exp)}`);
|
|
188
|
+
};
|
|
189
|
+
const balanceInDestFrame = (() => {
|
|
190
|
+
const native = BigInt(selectedCashEntry.totalRaw);
|
|
191
|
+
if (selectedCashEntry.decimals === selectedTokenDecimals) return native;
|
|
192
|
+
if (selectedCashEntry.decimals > selectedTokenDecimals) {
|
|
193
|
+
return native / scaleBy(BigInt(1), selectedCashEntry.decimals - selectedTokenDecimals);
|
|
194
|
+
}
|
|
195
|
+
return scaleBy(native, selectedTokenDecimals - selectedCashEntry.decimals);
|
|
196
|
+
})();
|
|
197
|
+
if (BigInt(amountRaw) > balanceInDestFrame) {
|
|
198
|
+
throw new Error("Withdrawal amount exceeds your available balance.");
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
ws == null ? void 0 : ws.connect();
|
|
202
|
+
yield new Promise((resolve, reject) => {
|
|
203
|
+
withdrawMutation.mutate(
|
|
204
|
+
{
|
|
205
|
+
amountRaw,
|
|
206
|
+
tokenSymbol: resolvedWithdrawToken,
|
|
207
|
+
destinationAddress: trimmedDestination,
|
|
208
|
+
destinationChainId
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
onSuccess: (data) => {
|
|
212
|
+
setWithdrawalId(data.withdrawalId);
|
|
213
|
+
resolve();
|
|
214
|
+
},
|
|
215
|
+
onError: reject
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
setWithdrawSummary({
|
|
220
|
+
amountReceived: `${formatRawTokenAmountForDisplay(amountRaw, selectedTokenDecimals)} ${resolvedWithdrawToken}`,
|
|
221
|
+
network: (_b2 = (_a2 = supportedChains == null ? void 0 : supportedChains.find((chain) => chain.chainId === destinationChainId)) == null ? void 0 : _a2.name) != null ? _b2 : resolvedWithdrawNetwork,
|
|
222
|
+
toWallet: shortenAddress(trimmedDestination),
|
|
223
|
+
fees: "\u2014"
|
|
224
|
+
});
|
|
225
|
+
}), [
|
|
226
|
+
resolvedWithdrawNetwork,
|
|
227
|
+
resolvedWithdrawToken,
|
|
228
|
+
selectedCashEntry,
|
|
229
|
+
selectedTokenDecimals,
|
|
230
|
+
supportedChains,
|
|
231
|
+
withdrawAmount,
|
|
232
|
+
withdrawDestination,
|
|
233
|
+
withdrawMutation,
|
|
234
|
+
ws
|
|
235
|
+
]);
|
|
236
|
+
return {
|
|
237
|
+
open,
|
|
238
|
+
onOpenChange: handleWithdrawOpenChange,
|
|
239
|
+
withdrawFlow: {
|
|
240
|
+
balance: totalBalance,
|
|
241
|
+
balanceDisplay,
|
|
242
|
+
amount: withdrawAmount,
|
|
243
|
+
destinationWallet: withdrawDestination,
|
|
244
|
+
tokenOptions,
|
|
245
|
+
networkOptions,
|
|
246
|
+
selectedToken: resolvedWithdrawToken,
|
|
247
|
+
selectedNetwork: resolvedWithdrawNetwork,
|
|
248
|
+
purchaseSummary: withdrawSummary,
|
|
249
|
+
withdrawalId
|
|
250
|
+
},
|
|
251
|
+
onWithdrawDestinationChange: setWithdrawDestination,
|
|
252
|
+
onWithdrawAmountChange: setWithdrawAmount,
|
|
253
|
+
onWithdrawTokenChange: setWithdrawToken,
|
|
254
|
+
onWithdrawNetworkChange: setWithdrawNetwork,
|
|
255
|
+
onMaxClick: useCallback(() => {
|
|
256
|
+
setWithdrawAmount(exactBalance === "0" ? "0" : exactBalance);
|
|
257
|
+
}, [exactBalance]),
|
|
258
|
+
onSelectWithdrawProvider: useCallback(
|
|
259
|
+
(_providerId) => __async(null, null, function* () {
|
|
260
|
+
return handleWithdrawProvider();
|
|
261
|
+
}),
|
|
262
|
+
[handleWithdrawProvider]
|
|
263
|
+
),
|
|
264
|
+
onDoneWithdraw: useCallback(() => handleWithdrawOpenChange(false), [handleWithdrawOpenChange])
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/withdraw/use-withdrawal-lifecycle.ts
|
|
269
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef, useState as useState2 } from "react";
|
|
270
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
271
|
+
var INITIAL_STATE = {
|
|
272
|
+
pending: true,
|
|
273
|
+
status: null,
|
|
274
|
+
terminal: false,
|
|
275
|
+
lastLeg: null,
|
|
276
|
+
legs: [],
|
|
277
|
+
errorMessage: null,
|
|
278
|
+
timestamp: null
|
|
279
|
+
};
|
|
280
|
+
var restLegToWsLeg = (leg) => ({
|
|
281
|
+
sourceChainId: leg.sourceChainId,
|
|
282
|
+
destChainId: leg.destChainId,
|
|
283
|
+
type: leg.type,
|
|
284
|
+
// The wire-level WS leg status enum and the REST leg status enum are the
|
|
285
|
+
// same union (planned/submitted/confirmed/failed). Keep the cast narrow to
|
|
286
|
+
// avoid pulling the SDK enum apart for a one-line bridge.
|
|
287
|
+
status: leg.status,
|
|
288
|
+
amountRaw: leg.amountRaw,
|
|
289
|
+
txHash: leg.txHash,
|
|
290
|
+
bridgeOperationId: leg.bridgeOperationId
|
|
291
|
+
});
|
|
292
|
+
var mergeLegs = (prev, snapshot, delta) => {
|
|
293
|
+
if (snapshot) return snapshot;
|
|
294
|
+
if (!delta) return prev;
|
|
295
|
+
const idx = prev.findIndex(
|
|
296
|
+
(l) => l.sourceChainId === delta.sourceChainId && l.destChainId === delta.destChainId && l.type === delta.type
|
|
297
|
+
);
|
|
298
|
+
if (idx === -1) return [...prev, delta];
|
|
299
|
+
const next = prev.slice();
|
|
300
|
+
next[idx] = delta;
|
|
301
|
+
return next;
|
|
302
|
+
};
|
|
303
|
+
var restToLifecycleState = (response) => {
|
|
304
|
+
var _a;
|
|
305
|
+
return {
|
|
306
|
+
pending: false,
|
|
307
|
+
status: response.status,
|
|
308
|
+
terminal: response.status === "completed" || response.status === "partial" || response.status === "failed",
|
|
309
|
+
lastLeg: null,
|
|
310
|
+
legs: response.legs.map(restLegToWsLeg),
|
|
311
|
+
errorMessage: (_a = response.errorMessage) != null ? _a : null,
|
|
312
|
+
// No server timestamp on the REST response — we use 0 as "older than any
|
|
313
|
+
// WS timestamp" so a subsequent WS event always wins. Callers that read
|
|
314
|
+
// `timestamp` should treat 0/null interchangeably as "unset".
|
|
315
|
+
timestamp: 0
|
|
316
|
+
};
|
|
317
|
+
};
|
|
318
|
+
function useWithdrawalLifecycle(withdrawalId) {
|
|
319
|
+
const client = useAggClient();
|
|
320
|
+
const wsConnected = useAggWebSocketConnectionState();
|
|
321
|
+
const queryClient = useQueryClient();
|
|
322
|
+
const [state, setState] = useState2(INITIAL_STATE);
|
|
323
|
+
const stateRef = useRef(state);
|
|
324
|
+
stateRef.current = state;
|
|
325
|
+
const balanceRefetchedForRef = useRef(null);
|
|
326
|
+
useEffect2(() => {
|
|
327
|
+
setState(INITIAL_STATE);
|
|
328
|
+
}, [withdrawalId]);
|
|
329
|
+
useEffect2(() => {
|
|
330
|
+
if (!withdrawalId) return;
|
|
331
|
+
let cancelled = false;
|
|
332
|
+
(() => __async(null, null, function* () {
|
|
333
|
+
try {
|
|
334
|
+
const response = yield client.getWithdrawalStatus(withdrawalId);
|
|
335
|
+
if (cancelled) return;
|
|
336
|
+
if (response.withdrawalId !== withdrawalId) return;
|
|
337
|
+
const current = stateRef.current;
|
|
338
|
+
const wsAlreadyWon = current.timestamp != null && current.timestamp > 0;
|
|
339
|
+
if (wsAlreadyWon) return;
|
|
340
|
+
setState(restToLifecycleState(response));
|
|
341
|
+
} catch (e) {
|
|
342
|
+
}
|
|
343
|
+
}))();
|
|
344
|
+
return () => {
|
|
345
|
+
cancelled = true;
|
|
346
|
+
};
|
|
347
|
+
}, [client, withdrawalId, wsConnected]);
|
|
348
|
+
const handler = useMemo2(() => {
|
|
349
|
+
if (!withdrawalId) return null;
|
|
350
|
+
return (msg) => {
|
|
351
|
+
if (msg.withdrawalId !== withdrawalId) return;
|
|
352
|
+
setState((prev) => {
|
|
353
|
+
var _a, _b;
|
|
354
|
+
return {
|
|
355
|
+
pending: false,
|
|
356
|
+
status: msg.status,
|
|
357
|
+
terminal: msg.terminal,
|
|
358
|
+
lastLeg: (_a = msg.leg) != null ? _a : null,
|
|
359
|
+
// `legs[]` is the cumulative server-known truth. Snapshots
|
|
360
|
+
// (`pending` / terminal rollup) carry a full `legs[]` and replace
|
|
361
|
+
// it. Intermediate per-leg deltas carry only `leg` (no `legs[]`)
|
|
362
|
+
// — merge the delta into the existing array by
|
|
363
|
+
// (sourceChainId, destChainId, type) so the timeline UI doesn't
|
|
364
|
+
// collapse to empty between snapshots.
|
|
365
|
+
legs: mergeLegs(prev.legs, msg.legs, msg.leg),
|
|
366
|
+
errorMessage: (_b = msg.errorMessage) != null ? _b : null,
|
|
367
|
+
timestamp: msg.timestamp
|
|
368
|
+
};
|
|
369
|
+
});
|
|
370
|
+
};
|
|
371
|
+
}, [withdrawalId]);
|
|
372
|
+
useOnWithdrawalLifecycle(handler);
|
|
373
|
+
useEffect2(() => {
|
|
374
|
+
if (!withdrawalId) return;
|
|
375
|
+
if (!state.terminal) return;
|
|
376
|
+
if (balanceRefetchedForRef.current === withdrawalId) return;
|
|
377
|
+
balanceRefetchedForRef.current = withdrawalId;
|
|
378
|
+
invalidateBalanceQueries(queryClient);
|
|
379
|
+
client.syncManagedBalances().catch(() => {
|
|
380
|
+
});
|
|
381
|
+
}, [client, queryClient, state.terminal, withdrawalId]);
|
|
382
|
+
const reset = useCallback2(() => setState(INITIAL_STATE), []);
|
|
383
|
+
return { state, reset };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export {
|
|
387
|
+
useWithdrawFlow,
|
|
388
|
+
useWithdrawalLifecycle
|
|
389
|
+
};
|