@ensofinance/checkout-widget 0.0.19 → 0.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ import { ComponentType } from 'react';
4
4
  import { JSX as JSX_2 } from 'react/jsx-runtime';
5
5
  import { SystemConfig as WidgetTheme } from '@chakra-ui/react';
6
6
 
7
- export declare const Checkout: ({ config: { apiKey, tokenOut, chainIdOut, theme, enableExchange, cexBridgeChainMapping, recipient }, onClose, wrapper, }: {
7
+ export declare const Checkout: ({ config: { apiKey, tokenOut, chainIdOut, theme, enableExchange, cexBridgeChainMapping, recipient, enforceFlow, }, onClose, wrapper, }: {
8
8
  config: CheckoutConfig;
9
9
  wrapper?: ComponentType;
10
10
  onClose?: () => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ensofinance/checkout-widget",
3
- "version": "0.0.19",
3
+ "version": "0.0.20",
4
4
  "type": "module",
5
5
  "homepage": "https://www.enso.build/",
6
6
  "repository": {
@@ -4,18 +4,33 @@ import FlowSelector from "./steps/FlowSelector";
4
4
  import { useAppStore } from "@/store";
5
5
  import { TxTracker } from "@/util/tx-tracker";
6
6
  import ChakraProvider from "./ChakraProvider";
7
- import { type CheckoutConfig, type SupportedExchanges } from "@/types";
7
+ import {
8
+ type CheckoutConfig,
9
+ type SupportedExchanges,
10
+ type EnforceFlow,
11
+ } from "@/types";
12
+ import posthog from "posthog-js";
8
13
 
9
14
  type ICheckoutContext = {
10
15
  handleClose: () => void;
11
16
  enableExchange?: SupportedExchanges[];
12
17
  cexBridgeChainMapping?: Record<number, number>;
18
+ enforceFlow?: EnforceFlow;
13
19
  };
14
20
 
15
21
  const CheckoutContext = createContext<ICheckoutContext>({} as ICheckoutContext);
16
22
 
17
23
  const Checkout = ({
18
- config: { apiKey, tokenOut, chainIdOut, theme, enableExchange, cexBridgeChainMapping, recipient },
24
+ config: {
25
+ apiKey,
26
+ tokenOut,
27
+ chainIdOut,
28
+ theme,
29
+ enableExchange,
30
+ cexBridgeChainMapping,
31
+ recipient,
32
+ enforceFlow,
33
+ },
19
34
  onClose,
20
35
  wrapper,
21
36
  }: {
@@ -47,6 +62,10 @@ const Checkout = ({
47
62
  }, [tokenOut, chainIdOut, recipient]);
48
63
 
49
64
  useEffect(() => {
65
+ posthog.init("phc_capPDVae4W7y6QIqVTugTtx5geVthX4YVswtXa6DrjM", {
66
+ api_host: "https://eu.i.posthog.com",
67
+ person_profiles: "always", // or 'always' to create profiles for anonymous users as well
68
+ });
50
69
  if (!apiKey) alert("Please provide an API key");
51
70
  setEnsoApiToken(apiKey);
52
71
  }, [apiKey]);
@@ -68,6 +87,7 @@ const Checkout = ({
68
87
  handleClose,
69
88
  enableExchange,
70
89
  cexBridgeChainMapping,
90
+ enforceFlow,
71
91
  }}
72
92
  >
73
93
  <ChakraProvider themeConfig={theme}>
@@ -294,9 +294,9 @@ const ChooseExchangeStep = ({
294
294
 
295
295
  return (
296
296
  <BodyWrapper>
297
- <Box mb={4} width="100%" textAlign="left">
298
- <HeaderTitle>Choose Exchange</HeaderTitle>
299
- </Box>
297
+ {/*<Box mb={4} width="100%" textAlign="left">*/}
298
+ {/* <HeaderTitle>Choose Exchange</HeaderTitle>*/}
299
+ {/*</Box>*/}
300
300
 
301
301
  {integrations?.length > 0 ? (
302
302
  <ListWrapper>
@@ -478,7 +478,6 @@ const DEVICE_ID_KEY = "meshDeviceId";
478
478
  const useDeviceId = () => {
479
479
  return useMemo(() => {
480
480
  let deviceId = localStorage.getItem(DEVICE_ID_KEY);
481
- console.log(deviceId);
482
481
 
483
482
  if (!deviceId) {
484
483
  deviceId = `device_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
@@ -1967,7 +1966,7 @@ const ExchangeFlow = ({
1967
1966
  setFlow: (string) => void;
1968
1967
  initialStep?: WithdrawalStep;
1969
1968
  }) => {
1970
- const { handleClose } = useContext(CheckoutContext);
1969
+ const { handleClose, enforceFlow } = useContext(CheckoutContext);
1971
1970
  const [currentStep, setCurrentStep] = useState(initialStep);
1972
1971
  const [selectedToken, setSelectedToken] = useState<MatchedToken | null>(
1973
1972
  null,
@@ -2047,30 +2046,38 @@ const ExchangeFlow = ({
2047
2046
  <>
2048
2047
  <Modal.Header>
2049
2048
  <HeaderWrapper>
2050
- <IconButton
2051
- minWidth={"16px"}
2052
- minHeight={"16px"}
2053
- maxWidth={"16px"}
2054
- onClick={() => {
2055
- const index =
2056
- (selectedIntegration?.type === "delayed"
2057
- ? balanceSteps
2058
- : withdrawalSteps
2059
- ).findIndex((step) => step === currentStep) - 1;
2060
- if (index > 0) {
2061
- setCurrentStep(withdrawalSteps[index]);
2062
- } else {
2063
- setFlow("");
2064
- }
2065
- }}
2066
- >
2067
- <Icon
2068
- as={ChevronLeft}
2069
- color="gray"
2070
- width={"16px"}
2071
- height={"16px"}
2072
- />
2073
- </IconButton>
2049
+ {!(
2050
+ enforceFlow &&
2051
+ (currentStep === WithdrawalStep.ChooseExchange ||
2052
+ currentStep === WithdrawalStep.ChooseBalanceAsset)
2053
+ ) && (
2054
+ <IconButton
2055
+ minWidth={"16px"}
2056
+ minHeight={"16px"}
2057
+ maxWidth={"16px"}
2058
+ onClick={() => {
2059
+ const index =
2060
+ (selectedIntegration?.type === "delayed"
2061
+ ? balanceSteps
2062
+ : withdrawalSteps
2063
+ ).findIndex(
2064
+ (step) => step === currentStep,
2065
+ ) - 1;
2066
+ if (index >= 0) {
2067
+ setCurrentStep(withdrawalSteps[index]);
2068
+ } else {
2069
+ setFlow("");
2070
+ }
2071
+ }}
2072
+ >
2073
+ <Icon
2074
+ as={ChevronLeft}
2075
+ color="gray"
2076
+ width={"16px"}
2077
+ height={"16px"}
2078
+ />
2079
+ </IconButton>
2080
+ )}
2074
2081
 
2075
2082
  <Box
2076
2083
  display="flex"
@@ -1,6 +1,6 @@
1
- import { Box, Icon, Skeleton } from "@chakra-ui/react";
2
- import { X } from "lucide-react";
3
- import { useContext, useMemo, useState } from "react";
1
+ import { Box, Icon, Skeleton, Text } from "@chakra-ui/react";
2
+ import { X, AlertCircle } from "lucide-react";
3
+ import { useContext, useEffect, useMemo, useState } from "react";
4
4
  import { useAccount } from "wagmi";
5
5
  import { IconButton } from "../ui";
6
6
  import {
@@ -22,17 +22,21 @@ import ExchangeFlow, {
22
22
  EXCHANGE_ICON_BY_TYPE,
23
23
  ExchangeToIntegrationType,
24
24
  } from "@/components/steps/ExchangeFlow";
25
- import WalletFlow from "@/components/steps/WalletFlow/WalletFlow";
25
+ import WalletFlow, {
26
+ WalletFlowStep,
27
+ } from "@/components/steps/WalletFlow/WalletFlow";
26
28
 
27
29
  const FLOWS = {
28
- exchangeFlow: ExchangeFlow,
29
- walletFlow: WalletFlow,
30
+ exchange: ExchangeFlow,
31
+ wallet: WalletFlow,
30
32
  };
31
33
 
32
34
  const FlowSelector = () => {
33
- const { handleClose, enableExchange } = useContext(CheckoutContext);
35
+ const { handleClose, enableExchange, enforceFlow } =
36
+ useContext(CheckoutContext);
34
37
  const [flow, setFlow] = useState("");
35
38
  const [initialStep, setInitialStep] = useState<string | number>("");
39
+ const [enforceError, setEnforceError] = useState<string | null>(null);
36
40
 
37
41
  const { total, isLoading } = useWalletBalance();
38
42
  const { address } = useAccount();
@@ -43,6 +47,28 @@ const FlowSelector = () => {
43
47
  const { total: smartAccountTotal, isLoading: isLoadingSmartAccount } =
44
48
  useSmartAccountBalances(1);
45
49
 
50
+ // Handle enforceFlow on mount
51
+ useEffect(() => {
52
+ if (!enforceFlow) return;
53
+
54
+ if (enforceFlow === "exchange") {
55
+ if (!Array.isArray(enableExchange) || enableExchange.length === 0) {
56
+ setEnforceError(
57
+ "No exchanges configured. Please enable at least one exchange in the widget configuration.",
58
+ );
59
+ return;
60
+ }
61
+ setFlow("exchange");
62
+ setInitialStep(WithdrawalStep.ChooseExchange);
63
+ } else if (enforceFlow === "wallet") {
64
+ if (address) {
65
+ setFlow("wallet");
66
+ setInitialStep(WalletFlowStep.SelectToken);
67
+ }
68
+ // If no address, we'll show the wallet connection prompt in the UI
69
+ }
70
+ }, [enforceFlow, enableExchange, address]);
71
+
46
72
  const formattedBalance = useMemo(() => {
47
73
  if (isLoading)
48
74
  return (
@@ -100,16 +126,10 @@ const FlowSelector = () => {
100
126
  limit: formatUSD(smartAccountTotal),
101
127
  delay: "2 min",
102
128
  icons: [],
103
- flow: "exchangeFlow",
129
+ flow: "exchange",
104
130
  firstStep: WithdrawalStep.ChooseBalanceAsset,
105
131
  });
106
132
  }
107
- console.log(
108
- enableExchange,
109
- enableExchange.map(
110
- (integration) => EXCHANGE_ICON_BY_TYPE[integration],
111
- ),
112
- );
113
133
 
114
134
  if (Array.isArray(enableExchange) && enableExchange.length > 0)
115
135
  options.unshift({
@@ -125,7 +145,7 @@ const FlowSelector = () => {
125
145
  ],
126
146
  )
127
147
  .filter(Boolean),
128
- flow: "exchangeFlow",
148
+ flow: "exchange",
129
149
  // Start at ChooseExchange to allow picking among multiple integrations
130
150
  firstStep: WithdrawalStep.ChooseExchange,
131
151
  });
@@ -138,6 +158,89 @@ const FlowSelector = () => {
138
158
  return <FlowComponent setFlow={setFlow} initialStep={initialStep} />;
139
159
  }
140
160
 
161
+ // Error state for enforced exchange mode with no exchanges configured
162
+ if (enforceError) {
163
+ return (
164
+ <>
165
+ <Modal.Header>
166
+ <InitialStepHeaderWrapper>
167
+ <Box display="flex" flexDirection="column" gap={"4px"}>
168
+ <HeaderTitle>Configuration Error</HeaderTitle>
169
+ </Box>
170
+ {handleClose && (
171
+ <IconButton onClick={handleClose} width={"40px"}>
172
+ <Icon
173
+ as={X}
174
+ color="fg.muted"
175
+ width={"16px"}
176
+ height={"16px"}
177
+ />
178
+ </IconButton>
179
+ )}
180
+ </InitialStepHeaderWrapper>
181
+ </Modal.Header>
182
+ <Modal.Body>
183
+ <BodyWrapper>
184
+ <Box
185
+ display="flex"
186
+ flexDirection="column"
187
+ alignItems="center"
188
+ gap="4"
189
+ p="6"
190
+ textAlign="center"
191
+ >
192
+ <Icon
193
+ as={AlertCircle}
194
+ color="red.500"
195
+ width="48px"
196
+ height="48px"
197
+ />
198
+ <Text color="fg.muted">{enforceError}</Text>
199
+ </Box>
200
+ </BodyWrapper>
201
+ </Modal.Body>
202
+ </>
203
+ );
204
+ }
205
+
206
+ // Wallet connection prompt for enforced wallet mode without wallet connected
207
+ if (enforceFlow === "wallet" && !address) {
208
+ return (
209
+ <>
210
+ <Modal.Header>
211
+ <InitialStepHeaderWrapper>
212
+ <Box display="flex" flexDirection="column" gap={"4px"}>
213
+ <HeaderTitle>Connect Wallet</HeaderTitle>
214
+ </Box>
215
+ {handleClose && (
216
+ <IconButton onClick={handleClose} width={"40px"}>
217
+ <Icon
218
+ as={X}
219
+ color="fg.muted"
220
+ width={"16px"}
221
+ height={"16px"}
222
+ />
223
+ </IconButton>
224
+ )}
225
+ </InitialStepHeaderWrapper>
226
+ </Modal.Header>
227
+ <Modal.Body>
228
+ <BodyWrapper>
229
+ <ListWrapper>
230
+ <WalletCard
231
+ icon={walletIcon}
232
+ walletHash="Not Connected"
233
+ balance="Connect your wallet to continue"
234
+ delay=""
235
+ status={WalletStatus.NONE}
236
+ />
237
+ </ListWrapper>
238
+ </BodyWrapper>
239
+ </Modal.Body>
240
+ </>
241
+ );
242
+ }
243
+
141
244
  return (
142
245
  <>
143
246
  <Modal.Header>
@@ -172,7 +275,7 @@ const FlowSelector = () => {
172
275
  status={WalletStatus.CONNECTED}
173
276
  badge={walletDisplayName}
174
277
  onClick={() => {
175
- setFlow("walletFlow");
278
+ setFlow("wallet");
176
279
  }}
177
280
  />
178
281
  }
@@ -32,7 +32,7 @@ const WalletFlow = ({
32
32
  setFlow?: (step: string) => void;
33
33
  }) => {
34
34
  const [currentStep, setCurrentStep] = useState<WalletFlowStep>(initialStep);
35
- const { handleClose } = useContext(CheckoutContext);
35
+ const { handleClose, enforceFlow } = useContext(CheckoutContext);
36
36
 
37
37
  const handleSetStep = (step: WalletFlowStep | string) => {
38
38
  setCurrentStep(step as WalletFlowStep);
@@ -62,23 +62,31 @@ const WalletFlow = ({
62
62
  }
63
63
  })();
64
64
 
65
+ const enforcedInitialStep =
66
+ enforceFlow && currentStep === WalletFlowStep.SelectToken;
67
+
65
68
  return (
66
69
  <>
67
70
  <Modal.Header>
68
71
  <HeaderWrapper>
69
- <IconButton
70
- minWidth={"16px"}
71
- minHeight={"16px"}
72
- maxWidth={"16px"}
73
- onClick={handleBackClick}
74
- >
75
- <Icon
76
- as={ChevronLeft}
77
- color="gray"
78
- width={"16px"}
79
- height={"16px"}
80
- />
81
- </IconButton>
72
+ {!(
73
+ enforceFlow &&
74
+ currentStep === WalletFlowStep.SelectToken
75
+ ) && (
76
+ <IconButton
77
+ minWidth={"16px"}
78
+ minHeight={"16px"}
79
+ maxWidth={"16px"}
80
+ onClick={handleBackClick}
81
+ >
82
+ <Icon
83
+ as={ChevronLeft}
84
+ color="gray"
85
+ width={"16px"}
86
+ height={"16px"}
87
+ />
88
+ </IconButton>
89
+ )}
82
90
 
83
91
  <Box
84
92
  display="flex"
@@ -8,6 +8,8 @@ enum SupportedExchanges {
8
8
  Bybit = "bybit",
9
9
  }
10
10
 
11
+ export type EnforceFlow = "exchange" | "wallet";
12
+
11
13
  export type CheckoutConfig = {
12
14
  tokenOut: string;
13
15
  chainIdOut: number;
@@ -18,6 +20,8 @@ export type CheckoutConfig = {
18
20
  cexBridgeChainMapping?: Record<number, number>;
19
21
  /** Override recipient address (defaults to connected wallet's smart account) */
20
22
  recipient?: string;
23
+ /** Force the widget to open in a specific flow, bypassing the selector */
24
+ enforceFlow?: EnforceFlow;
21
25
  };
22
26
 
23
27
  export type CheckoutModalProps = {