@ensofinance/checkout-widget 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/checkout-widget.es.js +25523 -24215
- package/dist/checkout-widget.es.js.map +1 -1
- package/dist/checkout-widget.umd.js +64 -59
- package/dist/checkout-widget.umd.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/package.json +1 -1
- package/src/assets/providers/alchemypay.svg +21 -0
- package/src/assets/providers/banxa.svg +21 -0
- package/src/assets/providers/binanceconnect.svg +14 -0
- package/src/assets/providers/kryptonim.svg +6 -0
- package/src/assets/providers/mercuryo.svg +21 -0
- package/src/assets/providers/moonpay.svg +14 -0
- package/src/assets/providers/stripe.svg +16 -0
- package/src/assets/providers/swapped.svg +1 -0
- package/src/assets/providers/topper.svg +14 -0
- package/src/assets/providers/transak.svg +21 -0
- package/src/assets/providers/unlimit.svg +21 -0
- package/src/components/AmountInput.tsx +41 -25
- package/src/components/ChakraProvider.tsx +36 -13
- package/src/components/Checkout.tsx +7 -1
- package/src/components/CurrencySwapDisplay.tsx +59 -22
- package/src/components/DepositProcessing.tsx +1 -1
- package/src/components/ExchangeConfirmSecurity.tsx +1 -1
- package/src/components/QuoteParameters.tsx +1 -1
- package/src/components/TransactionDetailRow.tsx +2 -2
- package/src/components/cards/ExchangeCard.tsx +1 -1
- package/src/components/cards/OptionCard.tsx +2 -1
- package/src/components/cards/WalletCard.tsx +1 -1
- package/src/components/modal.tsx +3 -3
- package/src/components/steps/CardBuyFlow/CardBuyFlow.tsx +412 -0
- package/src/components/steps/CardBuyFlow/ChooseAmountStep.tsx +352 -0
- package/src/components/steps/CardBuyFlow/OpenWidgetStep.tsx +193 -0
- package/src/components/steps/ExchangeFlow.tsx +254 -1416
- package/src/components/steps/FlowSelector.tsx +117 -60
- package/src/components/steps/SmartAccountFlow.tsx +372 -0
- package/src/components/steps/WalletFlow/WalletAmountStep.tsx +2 -2
- package/src/components/steps/WalletFlow/WalletConfirmStep.tsx +92 -51
- package/src/components/steps/WalletFlow/WalletFlow.tsx +17 -16
- package/src/components/steps/WalletFlow/WalletQuoteStep.tsx +2 -2
- package/src/components/steps/WalletFlow/WalletTokenStep.tsx +6 -4
- package/src/components/steps/shared/ChooseAmountStep.tsx +325 -0
- package/src/components/steps/shared/SignUserOpStep.tsx +117 -0
- package/src/components/steps/shared/TrackUserOpStep.tsx +625 -0
- package/src/components/steps/shared/exchangeIntegration.ts +19 -0
- package/src/components/steps/shared/types.ts +22 -0
- package/src/components/ui/index.tsx +23 -6
- package/src/components/ui/toaster.tsx +2 -1
- package/src/components/ui/transitions.tsx +16 -0
- package/src/types/index.ts +99 -0
- package/src/util/constants.tsx +27 -0
- package/src/util/enso-hooks.tsx +75 -61
- package/src/util/meld-hooks.tsx +533 -0
- package/src/assets/usdc.webp +0 -0
- package/src/assets/usdt.webp +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Box, Icon, Skeleton, Text } from "@chakra-ui/react";
|
|
2
2
|
import { X, AlertCircle } from "lucide-react";
|
|
3
|
-
import { useContext, useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
4
4
|
import { useAccount } from "wagmi";
|
|
5
5
|
import { IconButton } from "../ui";
|
|
6
6
|
import {
|
|
@@ -17,29 +17,58 @@ import { CheckoutContext } from "../Checkout";
|
|
|
17
17
|
import { WalletCard, OptionCard } from "../cards";
|
|
18
18
|
import { WalletStatus } from "../cards/WalletCard";
|
|
19
19
|
import { useWalletIcon, useSmartAccountBalances } from "@/util/enso-hooks";
|
|
20
|
-
import ExchangeFlow
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
ExchangeToIntegrationType,
|
|
24
|
-
} from "@/components/steps/ExchangeFlow";
|
|
20
|
+
import ExchangeFlow from "@/components/steps/ExchangeFlow";
|
|
21
|
+
import CardBuyFlow, { CardBuyStep } from "@/components/steps/CardBuyFlow/CardBuyFlow";
|
|
22
|
+
import SmartAccountFlow from "@/components/steps/SmartAccountFlow";
|
|
25
23
|
import WalletFlow, {
|
|
26
24
|
WalletFlowStep,
|
|
27
25
|
} from "@/components/steps/WalletFlow/WalletFlow";
|
|
26
|
+
import {
|
|
27
|
+
isMeldCardBuySupportedChain,
|
|
28
|
+
useCountryCode,
|
|
29
|
+
useMeldSupportedCrypto,
|
|
30
|
+
} from "@/util/meld-hooks";
|
|
31
|
+
import { useAppStore } from "@/store";
|
|
32
|
+
import mastercardIcon from "@/assets/mastercard.png";
|
|
33
|
+
import visaIcon from "@/assets/visa.png";
|
|
34
|
+
import {
|
|
35
|
+
ExchangeToIntegrationType,
|
|
36
|
+
EXCHANGE_ICON_BY_TYPE,
|
|
37
|
+
} from "./shared/exchangeIntegration";
|
|
38
|
+
// Card brand icons
|
|
39
|
+
const CARD_ICONS = [mastercardIcon, visaIcon];
|
|
40
|
+
|
|
41
|
+
type FlowLaunchState = {
|
|
42
|
+
flow: string;
|
|
43
|
+
presetAmount?: boolean;
|
|
44
|
+
} | null;
|
|
28
45
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
type FlowOption = {
|
|
47
|
+
id: string;
|
|
48
|
+
title: string;
|
|
49
|
+
limit: string;
|
|
50
|
+
delay: string;
|
|
51
|
+
icons: string[];
|
|
52
|
+
disabled?: boolean;
|
|
53
|
+
} & {
|
|
54
|
+
flow: string;
|
|
55
|
+
firstStep?: number;
|
|
32
56
|
};
|
|
33
57
|
|
|
34
58
|
const FlowSelector = () => {
|
|
35
|
-
const { handleClose, enableExchange, enforceFlow } =
|
|
59
|
+
const { handleClose, enableExchange, enableCardBuy, enforceFlow } =
|
|
36
60
|
useContext(CheckoutContext);
|
|
37
|
-
const [
|
|
38
|
-
const [initialStep, setInitialStep] = useState<string | number>("");
|
|
61
|
+
const [flowLaunch, setFlowLaunch] = useState<FlowLaunchState>(null);
|
|
39
62
|
const [enforceError, setEnforceError] = useState<string | null>(null);
|
|
40
63
|
|
|
41
64
|
const { total, isLoading } = useWalletBalance();
|
|
42
65
|
const { address } = useAccount();
|
|
66
|
+
const chainIdOut = useAppStore((s) => s.chainIdOut);
|
|
67
|
+
const { countryCode } = useCountryCode();
|
|
68
|
+
const { data: supportedCryptos = [] } = useMeldSupportedCrypto({
|
|
69
|
+
countryCode,
|
|
70
|
+
enabled: !!enableCardBuy && !!chainIdOut,
|
|
71
|
+
});
|
|
43
72
|
|
|
44
73
|
const { walletIcon, walletDisplayName } = useWalletIcon();
|
|
45
74
|
|
|
@@ -47,6 +76,25 @@ const FlowSelector = () => {
|
|
|
47
76
|
const { total: smartAccountTotal, isLoading: isLoadingSmartAccount } =
|
|
48
77
|
useSmartAccountBalances(1);
|
|
49
78
|
|
|
79
|
+
// Check if card buy is available for this chain
|
|
80
|
+
const isCardBuyAvailable =
|
|
81
|
+
enableCardBuy &&
|
|
82
|
+
chainIdOut &&
|
|
83
|
+
isMeldCardBuySupportedChain(chainIdOut, supportedCryptos);
|
|
84
|
+
|
|
85
|
+
const handleSetFlow = useCallback((nextFlow: string) => {
|
|
86
|
+
if (!nextFlow) {
|
|
87
|
+
setFlowLaunch(null);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
setFlowLaunch({ flow: nextFlow });
|
|
92
|
+
}, []);
|
|
93
|
+
|
|
94
|
+
const handleCardBuyDepositDetected = useCallback(() => {
|
|
95
|
+
setFlowLaunch({ flow: "smart-account", presetAmount: true });
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
50
98
|
// Handle enforceFlow on mount
|
|
51
99
|
useEffect(() => {
|
|
52
100
|
if (!enforceFlow) return;
|
|
@@ -58,14 +106,15 @@ const FlowSelector = () => {
|
|
|
58
106
|
);
|
|
59
107
|
return;
|
|
60
108
|
}
|
|
61
|
-
|
|
62
|
-
|
|
109
|
+
setFlowLaunch({
|
|
110
|
+
flow: "exchange",
|
|
111
|
+
});
|
|
63
112
|
} else if (enforceFlow === "wallet") {
|
|
64
113
|
if (address) {
|
|
65
|
-
|
|
66
|
-
|
|
114
|
+
setFlowLaunch({
|
|
115
|
+
flow: "wallet",
|
|
116
|
+
});
|
|
67
117
|
}
|
|
68
|
-
// If no address, we'll show the wallet connection prompt in the UI
|
|
69
118
|
}
|
|
70
119
|
}, [enforceFlow, enableExchange, address]);
|
|
71
120
|
|
|
@@ -86,37 +135,7 @@ const FlowSelector = () => {
|
|
|
86
135
|
}, [address]);
|
|
87
136
|
|
|
88
137
|
const OPTIONS = useMemo(() => {
|
|
89
|
-
const options:
|
|
90
|
-
id: string;
|
|
91
|
-
title: string;
|
|
92
|
-
limit: string;
|
|
93
|
-
delay: string;
|
|
94
|
-
icons: string[];
|
|
95
|
-
flow: string;
|
|
96
|
-
firstStep: string | number;
|
|
97
|
-
disabled?: boolean;
|
|
98
|
-
}[] = [
|
|
99
|
-
// {
|
|
100
|
-
// id: "1",
|
|
101
|
-
// title: "Crypto Transfer",
|
|
102
|
-
// limit: address ? "No Limit" : "Connect wallet to proceed",
|
|
103
|
-
// delay: address ? "Instant - 2 min" : "",
|
|
104
|
-
// icons: [RabbyIcon, MetamaskIcon],
|
|
105
|
-
// flow: "mainFlow",
|
|
106
|
-
// firstStep: "selectToken",
|
|
107
|
-
// disabled: !address,
|
|
108
|
-
// },
|
|
109
|
-
// {
|
|
110
|
-
// id: "3",
|
|
111
|
-
// title: "Deposit with card",
|
|
112
|
-
// limit: "$10,000 limit",
|
|
113
|
-
// delay: "5 min",
|
|
114
|
-
// icons: [MastercardIcon, VisaIcon],
|
|
115
|
-
// flow: "",
|
|
116
|
-
// firstStep: "",
|
|
117
|
-
// disabled: true,
|
|
118
|
-
// },
|
|
119
|
-
];
|
|
138
|
+
const options: FlowOption[] = [];
|
|
120
139
|
|
|
121
140
|
// Add Smart Balance option if balance > $20
|
|
122
141
|
if (!isLoadingSmartAccount && smartAccountTotal > 20) {
|
|
@@ -126,12 +145,12 @@ const FlowSelector = () => {
|
|
|
126
145
|
limit: formatUSD(smartAccountTotal),
|
|
127
146
|
delay: "2 min",
|
|
128
147
|
icons: [],
|
|
129
|
-
flow: "
|
|
130
|
-
firstStep: WithdrawalStep.ChooseBalanceAsset,
|
|
148
|
+
flow: "smart-account",
|
|
131
149
|
});
|
|
132
150
|
}
|
|
133
151
|
|
|
134
|
-
|
|
152
|
+
// Add CEX exchange option
|
|
153
|
+
if (Array.isArray(enableExchange) && enableExchange.length > 0) {
|
|
135
154
|
options.unshift({
|
|
136
155
|
id: "exchange",
|
|
137
156
|
title: "Connect Exchange",
|
|
@@ -146,16 +165,51 @@ const FlowSelector = () => {
|
|
|
146
165
|
)
|
|
147
166
|
.filter(Boolean),
|
|
148
167
|
flow: "exchange",
|
|
149
|
-
// Start at ChooseExchange to allow picking among multiple integrations
|
|
150
|
-
firstStep: WithdrawalStep.ChooseExchange,
|
|
151
168
|
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Add card buy option (MELD)
|
|
172
|
+
if (isCardBuyAvailable) {
|
|
173
|
+
options.push({
|
|
174
|
+
id: "card-buy",
|
|
175
|
+
title: "Buy with Card",
|
|
176
|
+
limit: "$10,000 limit",
|
|
177
|
+
delay: "1-5 min",
|
|
178
|
+
icons: CARD_ICONS,
|
|
179
|
+
flow: "card-buy",
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
152
183
|
return options;
|
|
153
|
-
}, [
|
|
184
|
+
}, [
|
|
185
|
+
enableExchange,
|
|
186
|
+
isCardBuyAvailable,
|
|
187
|
+
isLoadingSmartAccount,
|
|
188
|
+
smartAccountTotal,
|
|
189
|
+
]);
|
|
154
190
|
|
|
155
|
-
|
|
191
|
+
switch (flowLaunch?.flow) {
|
|
192
|
+
case "exchange":
|
|
193
|
+
return <ExchangeFlow setFlow={handleSetFlow} />;
|
|
194
|
+
|
|
195
|
+
case "smart-account":
|
|
196
|
+
return (
|
|
197
|
+
<SmartAccountFlow
|
|
198
|
+
setFlow={handleSetFlow}
|
|
199
|
+
presetAmount={flowLaunch.presetAmount}
|
|
200
|
+
/>
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
case "card-buy":
|
|
204
|
+
return (
|
|
205
|
+
<CardBuyFlow
|
|
206
|
+
setFlow={handleSetFlow}
|
|
207
|
+
onCardBuyDepositDetected={handleCardBuyDepositDetected}
|
|
208
|
+
/>
|
|
209
|
+
);
|
|
156
210
|
|
|
157
|
-
|
|
158
|
-
|
|
211
|
+
case "wallet":
|
|
212
|
+
return <WalletFlow setFlow={handleSetFlow} />;
|
|
159
213
|
}
|
|
160
214
|
|
|
161
215
|
// Error state for enforced exchange mode with no exchanges configured
|
|
@@ -275,7 +329,9 @@ const FlowSelector = () => {
|
|
|
275
329
|
status={WalletStatus.CONNECTED}
|
|
276
330
|
badge={walletDisplayName}
|
|
277
331
|
onClick={() => {
|
|
278
|
-
|
|
332
|
+
setFlowLaunch({
|
|
333
|
+
flow: "wallet",
|
|
334
|
+
});
|
|
279
335
|
}}
|
|
280
336
|
/>
|
|
281
337
|
}
|
|
@@ -294,8 +350,9 @@ const FlowSelector = () => {
|
|
|
294
350
|
icons={option.icons}
|
|
295
351
|
onClick={() => {
|
|
296
352
|
if (option.disabled) return;
|
|
297
|
-
|
|
298
|
-
|
|
353
|
+
setFlowLaunch({
|
|
354
|
+
flow: option.flow,
|
|
355
|
+
});
|
|
299
356
|
}}
|
|
300
357
|
/>
|
|
301
358
|
))}
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { Center, Spinner, Box, Icon, Text, Flex } from "@chakra-ui/react";
|
|
2
|
+
import { ChevronLeft, X } from "lucide-react";
|
|
3
|
+
import { useContext, useEffect, useState } from "react";
|
|
4
|
+
import { useReadContract } from "wagmi";
|
|
5
|
+
import { erc20Abi } from "viem";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
BodyWrapper,
|
|
9
|
+
HeaderDescription,
|
|
10
|
+
HeaderTitle,
|
|
11
|
+
HeaderWrapper,
|
|
12
|
+
ListWrapper,
|
|
13
|
+
} from "../ui/styled";
|
|
14
|
+
import { IconButton, Button } from "../ui";
|
|
15
|
+
import { CheckoutContext } from "../Checkout";
|
|
16
|
+
import Modal from "../modal";
|
|
17
|
+
import { useAppStore } from "@/store";
|
|
18
|
+
import { AssetCard } from "../cards";
|
|
19
|
+
import { formatNumber, formatUSD, normalizeValue } from "@/util";
|
|
20
|
+
import {
|
|
21
|
+
useAppDetails,
|
|
22
|
+
useSmartAccountAddress,
|
|
23
|
+
useSmartAccountBalances,
|
|
24
|
+
} from "@/util/enso-hooks";
|
|
25
|
+
import ChooseAmountStep from "./shared/ChooseAmountStep";
|
|
26
|
+
import SignUserOpStep from "./shared/SignUserOpStep";
|
|
27
|
+
import TrackUserOpStep from "./shared/TrackUserOpStep";
|
|
28
|
+
import type { MatchedToken } from "./shared/types";
|
|
29
|
+
import { AnimatedStep } from "../ui/transitions";
|
|
30
|
+
|
|
31
|
+
export enum SmartAccountStep {
|
|
32
|
+
ChooseAsset = 0,
|
|
33
|
+
ChooseAmount = 1,
|
|
34
|
+
SignUserOp = 2,
|
|
35
|
+
TrackUserOp = 3,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const smartAccountPreviousStep: Partial<
|
|
39
|
+
Record<SmartAccountStep, SmartAccountStep>
|
|
40
|
+
> = {
|
|
41
|
+
[SmartAccountStep.ChooseAmount]: SmartAccountStep.ChooseAsset,
|
|
42
|
+
[SmartAccountStep.SignUserOp]: SmartAccountStep.ChooseAmount,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const ChooseDelayedBalance = ({
|
|
46
|
+
setStep,
|
|
47
|
+
onTokenSelect,
|
|
48
|
+
notice,
|
|
49
|
+
}: {
|
|
50
|
+
setStep: (step: SmartAccountStep) => void;
|
|
51
|
+
onTokenSelect: (token: MatchedToken) => void;
|
|
52
|
+
notice?: string | null;
|
|
53
|
+
}) => {
|
|
54
|
+
const { chainIdIn, setTokenIn, setChainIdIn } = useAppStore();
|
|
55
|
+
const [selectedToken, setSelectedToken] = useState<string | null>(null);
|
|
56
|
+
|
|
57
|
+
const { holdingsList, total, isLoading } = useSmartAccountBalances(1);
|
|
58
|
+
|
|
59
|
+
if (isLoading) {
|
|
60
|
+
return (
|
|
61
|
+
<Center>
|
|
62
|
+
<Spinner m={5} />
|
|
63
|
+
</Center>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<BodyWrapper>
|
|
69
|
+
{notice ? (
|
|
70
|
+
<Box mb={3} width="100%" textAlign="left">
|
|
71
|
+
<Text fontSize="sm" color="fg.muted">
|
|
72
|
+
{notice}
|
|
73
|
+
</Text>
|
|
74
|
+
</Box>
|
|
75
|
+
) : null}
|
|
76
|
+
<Box mb={4} width="100%" textAlign="left">
|
|
77
|
+
<HeaderDescription>
|
|
78
|
+
Smart Account Balance: {formatUSD(total)}
|
|
79
|
+
</HeaderDescription>
|
|
80
|
+
</Box>
|
|
81
|
+
<Box overflowY={"scroll"} maxH={"400px"}>
|
|
82
|
+
<ListWrapper>
|
|
83
|
+
{holdingsList?.map((asset) => (
|
|
84
|
+
<AssetCard
|
|
85
|
+
key={`${asset.token}-${asset.chainId}`}
|
|
86
|
+
chainId={asset.chainId}
|
|
87
|
+
icon={asset.logoUri}
|
|
88
|
+
title={asset.name}
|
|
89
|
+
balance={`${formatNumber(
|
|
90
|
+
normalizeValue(asset.amount, asset.decimals),
|
|
91
|
+
)} ${asset.symbol}`}
|
|
92
|
+
usdBalance={formatUSD(asset.total)}
|
|
93
|
+
tag=""
|
|
94
|
+
loading={false}
|
|
95
|
+
selected={
|
|
96
|
+
selectedToken === asset.token &&
|
|
97
|
+
chainIdIn === asset.chainId
|
|
98
|
+
}
|
|
99
|
+
onClick={() => {
|
|
100
|
+
setSelectedToken(asset.token);
|
|
101
|
+
setTokenIn(asset.token);
|
|
102
|
+
setChainIdIn(asset.chainId);
|
|
103
|
+
const mockMatchedToken: MatchedToken = {
|
|
104
|
+
symbol: asset.symbol,
|
|
105
|
+
name: asset.name,
|
|
106
|
+
networkId: asset.chainId.toString(),
|
|
107
|
+
chainId: asset.chainId,
|
|
108
|
+
integrationNetworks: [],
|
|
109
|
+
tokenAddress: asset.token,
|
|
110
|
+
balance: Number(
|
|
111
|
+
normalizeValue(
|
|
112
|
+
asset.amount,
|
|
113
|
+
asset.decimals,
|
|
114
|
+
),
|
|
115
|
+
),
|
|
116
|
+
marketValue: asset.total,
|
|
117
|
+
};
|
|
118
|
+
onTokenSelect(mockMatchedToken);
|
|
119
|
+
}}
|
|
120
|
+
/>
|
|
121
|
+
))}
|
|
122
|
+
</ListWrapper>
|
|
123
|
+
</Box>
|
|
124
|
+
{holdingsList?.length === 0 ? (
|
|
125
|
+
<Box textAlign="center" color="fg.subtle" py={8}>
|
|
126
|
+
No tokens found in smart account
|
|
127
|
+
</Box>
|
|
128
|
+
) : null}
|
|
129
|
+
<Button
|
|
130
|
+
disabled={!selectedToken}
|
|
131
|
+
onClick={() => {
|
|
132
|
+
setStep(SmartAccountStep.ChooseAmount);
|
|
133
|
+
}}
|
|
134
|
+
>
|
|
135
|
+
Continue
|
|
136
|
+
</Button>
|
|
137
|
+
</BodyWrapper>
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const SmartAccountFlow = ({
|
|
142
|
+
setFlow,
|
|
143
|
+
presetAmount,
|
|
144
|
+
}: {
|
|
145
|
+
setFlow: (flow: string) => void;
|
|
146
|
+
presetAmount?: boolean;
|
|
147
|
+
}) => {
|
|
148
|
+
const { handleClose, enforceFlow } = useContext(CheckoutContext);
|
|
149
|
+
const [currentStep, setCurrentStep] = useState<SmartAccountStep>(
|
|
150
|
+
SmartAccountStep.ChooseAsset,
|
|
151
|
+
);
|
|
152
|
+
const [selectedToken, setSelectedToken] = useState<MatchedToken | null>(
|
|
153
|
+
null,
|
|
154
|
+
);
|
|
155
|
+
const [userOp, setUserOp] = useState<any | null>(null);
|
|
156
|
+
const [launchNotice, setLaunchNotice] = useState<string | null>(null);
|
|
157
|
+
const [isBootstrapping, setIsBootstrapping] = useState(!!presetAmount);
|
|
158
|
+
|
|
159
|
+
const setSelectedIntegration = useAppStore(
|
|
160
|
+
(state) => state.setSelectedIntegration,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const { smartAccountAddress } = useSmartAccountAddress();
|
|
164
|
+
|
|
165
|
+
const { tokenInData, tokenInPrice, chainIdIn, tokenIn } = useAppDetails();
|
|
166
|
+
|
|
167
|
+
const { data: launchRpcBalance } = useReadContract({
|
|
168
|
+
chainId: chainIdIn,
|
|
169
|
+
address: tokenIn as `0x${string}`,
|
|
170
|
+
abi: erc20Abi,
|
|
171
|
+
functionName: "balanceOf",
|
|
172
|
+
args: [smartAccountAddress],
|
|
173
|
+
query: {
|
|
174
|
+
enabled: !!presetAmount && !!smartAccountAddress,
|
|
175
|
+
refetchInterval: isBootstrapping ? 3000 : false,
|
|
176
|
+
staleTime: 0,
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Set delayed integration while mounted
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
setSelectedIntegration({
|
|
183
|
+
type: "delayed",
|
|
184
|
+
name: "Smart account",
|
|
185
|
+
id: "",
|
|
186
|
+
});
|
|
187
|
+
return () => setSelectedIntegration(null);
|
|
188
|
+
}, [setSelectedIntegration]);
|
|
189
|
+
|
|
190
|
+
// Card-buy bootstrap: resolve token and jump to sign step
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
if (!presetAmount || !isBootstrapping) return;
|
|
193
|
+
if (!chainIdIn || !tokenIn || !smartAccountAddress) return;
|
|
194
|
+
if (typeof launchRpcBalance !== "bigint") return;
|
|
195
|
+
if (!tokenInData) return;
|
|
196
|
+
|
|
197
|
+
const decimals = tokenInData.decimals;
|
|
198
|
+
const symbol = tokenInData.symbol;
|
|
199
|
+
const name = tokenInData.name;
|
|
200
|
+
|
|
201
|
+
const normalizedBalance = Number(
|
|
202
|
+
normalizeValue(launchRpcBalance, decimals),
|
|
203
|
+
);
|
|
204
|
+
if (!Number.isFinite(normalizedBalance)) return;
|
|
205
|
+
|
|
206
|
+
const resolvedPrice = Number(tokenInPrice ?? 0);
|
|
207
|
+
const resolvedMarketValue =
|
|
208
|
+
resolvedPrice > 0 ? normalizedBalance * resolvedPrice : 0;
|
|
209
|
+
|
|
210
|
+
const synthesizedToken: MatchedToken = {
|
|
211
|
+
symbol,
|
|
212
|
+
name,
|
|
213
|
+
networkId: chainIdIn.toString(),
|
|
214
|
+
chainId: chainIdIn,
|
|
215
|
+
integrationNetworks: [],
|
|
216
|
+
tokenAddress: tokenIn,
|
|
217
|
+
balance: normalizedBalance,
|
|
218
|
+
marketValue: resolvedMarketValue,
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
setSelectedToken(synthesizedToken);
|
|
222
|
+
setCurrentStep(SmartAccountStep.SignUserOp);
|
|
223
|
+
setIsBootstrapping(false);
|
|
224
|
+
}, [
|
|
225
|
+
isBootstrapping,
|
|
226
|
+
presetAmount,
|
|
227
|
+
chainIdIn,
|
|
228
|
+
tokenIn,
|
|
229
|
+
launchRpcBalance,
|
|
230
|
+
tokenInData,
|
|
231
|
+
tokenInPrice,
|
|
232
|
+
smartAccountAddress,
|
|
233
|
+
]);
|
|
234
|
+
|
|
235
|
+
// Timeout fallback for bootstrap
|
|
236
|
+
useEffect(() => {
|
|
237
|
+
if (!presetAmount || !isBootstrapping) return;
|
|
238
|
+
|
|
239
|
+
const timer = window.setTimeout(() => {
|
|
240
|
+
setIsBootstrapping(false);
|
|
241
|
+
setLaunchNotice(
|
|
242
|
+
"We could not auto-open the quote yet. Select your Smart Account USDC balance to continue.",
|
|
243
|
+
);
|
|
244
|
+
setCurrentStep(SmartAccountStep.ChooseAsset);
|
|
245
|
+
}, 12000);
|
|
246
|
+
|
|
247
|
+
return () => window.clearTimeout(timer);
|
|
248
|
+
}, [isBootstrapping, presetAmount]);
|
|
249
|
+
|
|
250
|
+
const currentStepComponent = (() => {
|
|
251
|
+
if (presetAmount && isBootstrapping) {
|
|
252
|
+
return (
|
|
253
|
+
<BodyWrapper>
|
|
254
|
+
<Flex direction="column" gap={3} align="center" py={8}>
|
|
255
|
+
<Spinner />
|
|
256
|
+
<Text color="fg.muted" fontSize="sm" textAlign="center">
|
|
257
|
+
Preparing your Smart Account quote...
|
|
258
|
+
</Text>
|
|
259
|
+
</Flex>
|
|
260
|
+
</BodyWrapper>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
switch (currentStep) {
|
|
265
|
+
case SmartAccountStep.ChooseAsset:
|
|
266
|
+
return (
|
|
267
|
+
<ChooseDelayedBalance
|
|
268
|
+
setStep={setCurrentStep}
|
|
269
|
+
onTokenSelect={setSelectedToken}
|
|
270
|
+
notice={launchNotice}
|
|
271
|
+
/>
|
|
272
|
+
);
|
|
273
|
+
case SmartAccountStep.ChooseAmount:
|
|
274
|
+
return (
|
|
275
|
+
<ChooseAmountStep
|
|
276
|
+
setStep={setCurrentStep}
|
|
277
|
+
nextStep={SmartAccountStep.SignUserOp}
|
|
278
|
+
selectedToken={selectedToken}
|
|
279
|
+
mode="smart-account"
|
|
280
|
+
/>
|
|
281
|
+
);
|
|
282
|
+
case SmartAccountStep.SignUserOp:
|
|
283
|
+
return (
|
|
284
|
+
<SignUserOpStep
|
|
285
|
+
nextStep={SmartAccountStep.TrackUserOp}
|
|
286
|
+
setStep={setCurrentStep}
|
|
287
|
+
setUserOp={setUserOp}
|
|
288
|
+
/>
|
|
289
|
+
);
|
|
290
|
+
case SmartAccountStep.TrackUserOp:
|
|
291
|
+
return (
|
|
292
|
+
<TrackUserOpStep
|
|
293
|
+
userOp={userOp}
|
|
294
|
+
onReset={() =>
|
|
295
|
+
setCurrentStep(SmartAccountStep.ChooseAsset)
|
|
296
|
+
}
|
|
297
|
+
/>
|
|
298
|
+
);
|
|
299
|
+
default:
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
})();
|
|
303
|
+
|
|
304
|
+
return (
|
|
305
|
+
<>
|
|
306
|
+
<Modal.Header>
|
|
307
|
+
<HeaderWrapper>
|
|
308
|
+
{!(
|
|
309
|
+
enforceFlow &&
|
|
310
|
+
currentStep === SmartAccountStep.ChooseAsset
|
|
311
|
+
) && (
|
|
312
|
+
<IconButton
|
|
313
|
+
minWidth={"16px"}
|
|
314
|
+
minHeight={"16px"}
|
|
315
|
+
maxWidth={"16px"}
|
|
316
|
+
onClick={() => {
|
|
317
|
+
const previousStep =
|
|
318
|
+
smartAccountPreviousStep[currentStep];
|
|
319
|
+
if (previousStep !== undefined) {
|
|
320
|
+
setCurrentStep(previousStep);
|
|
321
|
+
} else {
|
|
322
|
+
setFlow("");
|
|
323
|
+
}
|
|
324
|
+
}}
|
|
325
|
+
>
|
|
326
|
+
<Icon
|
|
327
|
+
as={ChevronLeft}
|
|
328
|
+
color="fg.muted"
|
|
329
|
+
width={"16px"}
|
|
330
|
+
height={"16px"}
|
|
331
|
+
/>
|
|
332
|
+
</IconButton>
|
|
333
|
+
)}
|
|
334
|
+
|
|
335
|
+
<Box
|
|
336
|
+
display="flex"
|
|
337
|
+
flexDirection="column"
|
|
338
|
+
gap={"4px"}
|
|
339
|
+
alignItems={"center"}
|
|
340
|
+
width="100%"
|
|
341
|
+
>
|
|
342
|
+
<HeaderTitle>Deposit from Smart account</HeaderTitle>
|
|
343
|
+
</Box>
|
|
344
|
+
|
|
345
|
+
{handleClose && (
|
|
346
|
+
<IconButton
|
|
347
|
+
onClick={handleClose}
|
|
348
|
+
minWidth={"16px"}
|
|
349
|
+
minHeight={"16px"}
|
|
350
|
+
maxWidth={"16px"}
|
|
351
|
+
marginLeft={"auto"}
|
|
352
|
+
>
|
|
353
|
+
<Icon
|
|
354
|
+
as={X}
|
|
355
|
+
color="fg.muted"
|
|
356
|
+
width={"16px"}
|
|
357
|
+
height={"16px"}
|
|
358
|
+
/>
|
|
359
|
+
</IconButton>
|
|
360
|
+
)}
|
|
361
|
+
</HeaderWrapper>
|
|
362
|
+
</Modal.Header>
|
|
363
|
+
<Modal.Body>
|
|
364
|
+
<AnimatedStep key={isBootstrapping ? "bootstrap" : currentStep}>
|
|
365
|
+
{currentStepComponent}
|
|
366
|
+
</AnimatedStep>
|
|
367
|
+
</Modal.Body>
|
|
368
|
+
</>
|
|
369
|
+
);
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
export default SmartAccountFlow;
|
|
@@ -10,7 +10,7 @@ import { normalizeValue, denormalizeValue } from "@/util";
|
|
|
10
10
|
import { precisionizeNumber, getPositiveDecimalValue } from "@/util/common";
|
|
11
11
|
import { useTokenBalance } from "@/util/wallet";
|
|
12
12
|
import { useAppDetails } from "@/util/enso-hooks";
|
|
13
|
-
const WalletAmountStep = ({ setStep }: { setStep: (step:
|
|
13
|
+
const WalletAmountStep = ({ setStep }: { setStep: (step: number) => void }) => {
|
|
14
14
|
const [amountInput, setAmountInput] = useState<AmountInputValue>({
|
|
15
15
|
tokenAmount: "",
|
|
16
16
|
usdAmount: "10.10",
|
|
@@ -124,7 +124,7 @@ const WalletAmountStep = ({ setStep }: { setStep: (step: string) => void }) => {
|
|
|
124
124
|
/>
|
|
125
125
|
|
|
126
126
|
<Button
|
|
127
|
-
onClick={() => setStep(
|
|
127
|
+
onClick={() => setStep(2)}
|
|
128
128
|
disabled={isAmountInvalid}
|
|
129
129
|
>
|
|
130
130
|
Continue
|