@hot-labs/kit 1.6.1 → 1.6.2
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/build/activity.d.ts +2 -0
- package/build/activity.js +8 -1
- package/build/activity.js.map +1 -1
- package/build/core/defuse.d.ts +61 -0
- package/build/core/defuse.js +47 -0
- package/build/core/defuse.js.map +1 -0
- package/build/core/exchange.d.ts +8 -4
- package/build/core/exchange.js +33 -7
- package/build/core/exchange.js.map +1 -1
- package/build/core/pendings.d.ts +26 -12
- package/build/core/pendings.js +43 -34
- package/build/core/pendings.js.map +1 -1
- package/build/ui/bridge/Bridge.js +11 -186
- package/build/ui/bridge/Bridge.js.map +1 -1
- package/build/ui/bridge/SelectSender.d.ts +2 -1
- package/build/ui/bridge/SelectSender.js +10 -2
- package/build/ui/bridge/SelectSender.js.map +1 -1
- package/build/ui/bridge/SelectToken.d.ts +2 -1
- package/build/ui/bridge/SelectToken.js +14 -6
- package/build/ui/bridge/SelectToken.js.map +1 -1
- package/build/ui/bridge/TokenAmountCard.d.ts +39 -0
- package/build/ui/bridge/TokenAmountCard.js +186 -0
- package/build/ui/bridge/TokenAmountCard.js.map +1 -0
- package/build/ui/icons/warning.js +1 -1
- package/build/ui/icons/warning.js.map +1 -1
- package/build/ui/profile/DepositFlow.d.ts +3 -0
- package/build/ui/profile/DepositFlow.js +128 -9
- package/build/ui/profile/DepositFlow.js.map +1 -1
- package/build/ui/profile/DepositQR.d.ts +11 -4
- package/build/ui/profile/DepositQR.js +81 -37
- package/build/ui/profile/DepositQR.js.map +1 -1
- package/build/ui/profile/Payment.js +3 -9
- package/build/ui/profile/Payment.js.map +1 -1
- package/build/ui/profile/Profile.d.ts +8 -3
- package/build/ui/profile/Profile.js +15 -10
- package/build/ui/profile/Profile.js.map +1 -1
- package/build/ui/router.d.ts +4 -2
- package/build/ui/router.js +4 -6
- package/build/ui/router.js.map +1 -1
- package/build/ui/styles.js +2 -1
- package/build/ui/styles.js.map +1 -1
- package/build/ui/toast/index.js +2 -0
- package/build/ui/toast/index.js.map +1 -1
- package/build/ui/uikit/animationts.d.ts +5 -0
- package/build/ui/uikit/animationts.js +19 -0
- package/build/ui/uikit/animationts.js.map +1 -0
- package/build/ui/uikit/badge.d.ts +5 -0
- package/build/ui/uikit/badge.js +19 -0
- package/build/ui/uikit/badge.js.map +1 -0
- package/build/ui/uikit/tabs.d.ts +2 -1
- package/build/ui/uikit/tabs.js +31 -17
- package/build/ui/uikit/tabs.js.map +1 -1
- package/package.json +1 -1
- package/src/activity.ts +10 -1
- package/src/core/defuse.ts +102 -0
- package/src/core/exchange.ts +38 -10
- package/src/core/pendings.ts +51 -38
- package/src/ui/bridge/Bridge.tsx +30 -312
- package/src/ui/bridge/SelectSender.tsx +24 -2
- package/src/ui/bridge/SelectToken.tsx +15 -6
- package/src/ui/bridge/TokenAmountCard.tsx +347 -0
- package/src/ui/icons/warning.tsx +1 -1
- package/src/ui/profile/DepositFlow.tsx +286 -10
- package/src/ui/profile/DepositQR.tsx +194 -74
- package/src/ui/profile/Payment.tsx +4 -27
- package/src/ui/profile/Profile.tsx +100 -92
- package/src/ui/router.tsx +5 -7
- package/src/ui/styles.ts +2 -1
- package/src/ui/toast/index.tsx +2 -0
- package/src/ui/uikit/animationts.ts +20 -0
- package/src/ui/uikit/badge.tsx +24 -0
- package/src/ui/uikit/tabs.tsx +38 -24
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { observer } from "mobx-react-lite";
|
|
2
|
+
import styled from "styled-components";
|
|
3
|
+
|
|
4
|
+
import { chains, WalletType } from "../../core/chains";
|
|
5
|
+
import { OmniWallet } from "../../core/OmniWallet";
|
|
6
|
+
import { Token } from "../../core/token";
|
|
7
|
+
import { formatter } from "../../core";
|
|
8
|
+
|
|
9
|
+
import { ArrowRightIcon } from "../icons/arrow-right";
|
|
10
|
+
import RefreshIcon from "../icons/refresh";
|
|
11
|
+
|
|
12
|
+
import { HotKit } from "../../HotKit";
|
|
13
|
+
import { PLarge, PSmall, PTiny } from "../uikit/text";
|
|
14
|
+
import { ImageView } from "../uikit/image";
|
|
15
|
+
import { Skeleton } from "../uikit/loader";
|
|
16
|
+
import { Button } from "../uikit/button";
|
|
17
|
+
|
|
18
|
+
import { TokenIcon } from "./TokenCard";
|
|
19
|
+
|
|
20
|
+
interface TokenAmountCardProps {
|
|
21
|
+
setValue: (value: string) => void;
|
|
22
|
+
setIsFiat: (isFiat: boolean) => void;
|
|
23
|
+
setToken: (token: Token) => void;
|
|
24
|
+
setSender: (sender: OmniWallet | "qr" | undefined) => void;
|
|
25
|
+
handleMax: () => void;
|
|
26
|
+
|
|
27
|
+
kit: HotKit;
|
|
28
|
+
style?: React.CSSProperties;
|
|
29
|
+
disableChains?: number[];
|
|
30
|
+
|
|
31
|
+
disableQR?: boolean;
|
|
32
|
+
sender: OmniWallet | "qr" | undefined;
|
|
33
|
+
isReviewing: boolean;
|
|
34
|
+
readonlyAmount?: boolean;
|
|
35
|
+
readableAmount: string;
|
|
36
|
+
availableBalance: number;
|
|
37
|
+
amount: number;
|
|
38
|
+
isFiat: boolean;
|
|
39
|
+
token?: Token;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const TokenAmountCard = observer((props: TokenAmountCardProps) => {
|
|
43
|
+
const { style, token, sender, kit, isReviewing, isFiat, amount, disableChains, readableAmount, availableBalance, readonlyAmount, disableQR } = props;
|
|
44
|
+
const { setValue, setIsFiat, setToken, setSender, handleMax } = props;
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<Card style={{ borderRadius: "20px 20px 2px 2px", ...style }}>
|
|
48
|
+
<CardHeader>
|
|
49
|
+
<ChainButton onClick={() => kit.router.openSelectTokenPopup({ kit, disableChains, onSelect: (token, wallet) => (setToken(token), setSender(wallet)) })}>
|
|
50
|
+
<PSmall>From:</PSmall>
|
|
51
|
+
{token != null && <ImageView src={chains.get(token.chain)?.logo || ""} alt={token.symbol} size={16} />}
|
|
52
|
+
<PSmall>{token != null ? chains.get(token.chain)?.name : "Select chain"}</PSmall>
|
|
53
|
+
<ArrowRightIcon style={{ marginLeft: -8, transform: "rotate(-270deg)" }} color="#ababab" />
|
|
54
|
+
</ChainButton>
|
|
55
|
+
|
|
56
|
+
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
|
57
|
+
<PSmall>Sender:</PSmall>
|
|
58
|
+
<BadgeButton onClick={() => (token ? kit.router.openSelectSender({ disableQR, kit, type: token.type, onSelect: (sender) => setSender(sender) }) : {})}>
|
|
59
|
+
<PSmall>{sender == null ? "Select" : sender !== "qr" ? formatter.truncateAddress(sender.address, 8) : "QR"}</PSmall>
|
|
60
|
+
<Tooltip id="sender-tooltip">
|
|
61
|
+
<PSmall>Select sender wallet</PSmall>
|
|
62
|
+
</Tooltip>
|
|
63
|
+
</BadgeButton>
|
|
64
|
+
</div>
|
|
65
|
+
</CardHeader>
|
|
66
|
+
|
|
67
|
+
<CardBody>
|
|
68
|
+
<div style={{ width: "100%", display: "flex", alignItems: "center", gap: 8, justifyContent: "space-between" }}>
|
|
69
|
+
<TokenPreview
|
|
70
|
+
token={token}
|
|
71
|
+
onSelect={() =>
|
|
72
|
+
kit.router.openSelectTokenPopup({
|
|
73
|
+
onSelect: (token, wallet) => (setToken(token), setSender(wallet)),
|
|
74
|
+
disableChains: disableChains,
|
|
75
|
+
initialChain: token?.chain,
|
|
76
|
+
kit,
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
/>
|
|
80
|
+
|
|
81
|
+
{isReviewing ? (
|
|
82
|
+
<Skeleton />
|
|
83
|
+
) : (
|
|
84
|
+
<input //
|
|
85
|
+
name="from"
|
|
86
|
+
type="text"
|
|
87
|
+
className="input"
|
|
88
|
+
autoComplete="off"
|
|
89
|
+
autoCapitalize="off"
|
|
90
|
+
autoCorrect="off"
|
|
91
|
+
readOnly={readonlyAmount}
|
|
92
|
+
value={isFiat ? `$${readableAmount}` : readableAmount}
|
|
93
|
+
onChange={(e) => setValue(e.target.value)}
|
|
94
|
+
placeholder="0"
|
|
95
|
+
autoFocus
|
|
96
|
+
/>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
{isFiat && (
|
|
101
|
+
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", width: "100%" }}>
|
|
102
|
+
{sender !== "qr" && (
|
|
103
|
+
<AvailableBalance>
|
|
104
|
+
<PSmall>Balance: ${token != null ? token.readable(availableBalance, token.usd) : 0}</PSmall>
|
|
105
|
+
<Button onClick={() => sender && token != null && kit.fetchToken(token, sender)}>
|
|
106
|
+
<RefreshIcon color="#fff" />
|
|
107
|
+
</Button>
|
|
108
|
+
</AvailableBalance>
|
|
109
|
+
)}
|
|
110
|
+
|
|
111
|
+
{sender === "qr" && <div />}
|
|
112
|
+
|
|
113
|
+
<div style={{ display: "flex", alignItems: "center", gap: 4, flexShrink: 0 }}>
|
|
114
|
+
{token != null && token.usd !== 0 && <PSmall style={{ marginRight: 8 }}>{`${token.readable(amount / token.usd)} ${token.symbol}`}</PSmall>}
|
|
115
|
+
|
|
116
|
+
{token != null && token.usd !== 0 && (
|
|
117
|
+
<BadgeButton style={{ border: `1px solid #fff` }} onClick={() => setIsFiat(!isFiat)}>
|
|
118
|
+
<PTiny>USD</PTiny>
|
|
119
|
+
</BadgeButton>
|
|
120
|
+
)}
|
|
121
|
+
|
|
122
|
+
{sender !== "qr" && (
|
|
123
|
+
<BadgeButton onClick={handleMax}>
|
|
124
|
+
<PTiny>MAX</PTiny>
|
|
125
|
+
</BadgeButton>
|
|
126
|
+
)}
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
)}
|
|
130
|
+
|
|
131
|
+
{!isFiat && (
|
|
132
|
+
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", width: "100%" }}>
|
|
133
|
+
{sender !== "qr" && token != null && (
|
|
134
|
+
<AvailableBalance>
|
|
135
|
+
<PSmall>Balance: {`${token.readable(availableBalance)} ${token.symbol}`}</PSmall>
|
|
136
|
+
<Button style={{ marginTop: 2 }} onClick={() => sender && kit.fetchToken(token, sender)}>
|
|
137
|
+
<RefreshIcon color="#fff" />
|
|
138
|
+
</Button>
|
|
139
|
+
</AvailableBalance>
|
|
140
|
+
)}
|
|
141
|
+
|
|
142
|
+
{sender === "qr" && <div />}
|
|
143
|
+
|
|
144
|
+
<div style={{ display: "flex", alignItems: "center", gap: 4 }}>
|
|
145
|
+
{token?.usd !== 0 && <PSmall style={{ marginRight: 8 }}>${token?.readable(amount, token.usd) || 0}</PSmall>}
|
|
146
|
+
{token?.usd !== 0 && (
|
|
147
|
+
<BadgeButton onClick={() => setIsFiat(!isFiat)}>
|
|
148
|
+
<PTiny>USD</PTiny>
|
|
149
|
+
</BadgeButton>
|
|
150
|
+
)}
|
|
151
|
+
{sender !== "qr" && (
|
|
152
|
+
<BadgeButton onClick={handleMax}>
|
|
153
|
+
<PTiny>MAX</PTiny>
|
|
154
|
+
</BadgeButton>
|
|
155
|
+
)}
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
)}
|
|
159
|
+
</CardBody>
|
|
160
|
+
</Card>
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
export const TokenPreview = ({ style, token, onSelect }: { style?: React.CSSProperties; token?: Token; onSelect: () => void }) => {
|
|
165
|
+
if (token == null)
|
|
166
|
+
return (
|
|
167
|
+
<SelectTokenButton style={style} onClick={() => onSelect()}>
|
|
168
|
+
<ImageView style={{ flexShrink: 0 }} src={""} alt="I" size={32} />
|
|
169
|
+
<PLarge>Select</PLarge>
|
|
170
|
+
<ArrowRightIcon style={{ flexShrink: 0, position: "absolute", right: 4 }} />
|
|
171
|
+
</SelectTokenButton>
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<SelectTokenButton style={style} onClick={() => onSelect()}>
|
|
176
|
+
<TokenIcon withoutChain token={token} size={32} />
|
|
177
|
+
<PLarge>{token.symbol}</PLarge>
|
|
178
|
+
<ArrowRightIcon style={{ flexShrink: 0, position: "absolute", right: 4 }} />
|
|
179
|
+
</SelectTokenButton>
|
|
180
|
+
);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export const Card = styled.div`
|
|
184
|
+
text-align: left;
|
|
185
|
+
align-items: flex-start;
|
|
186
|
+
justify-content: center;
|
|
187
|
+
flex-direction: column;
|
|
188
|
+
|
|
189
|
+
display: flex;
|
|
190
|
+
width: 100%;
|
|
191
|
+
|
|
192
|
+
border-radius: 20px 20px 2px 2px;
|
|
193
|
+
border: 1px solid #323232;
|
|
194
|
+
background: #1f1f1f;
|
|
195
|
+
|
|
196
|
+
input {
|
|
197
|
+
outline: none;
|
|
198
|
+
border: none;
|
|
199
|
+
background: none;
|
|
200
|
+
color: #fff;
|
|
201
|
+
font-size: 32px;
|
|
202
|
+
font-weight: bold;
|
|
203
|
+
width: 100%;
|
|
204
|
+
line-height: 40px;
|
|
205
|
+
text-align: left;
|
|
206
|
+
align-items: flex-start;
|
|
207
|
+
justify-content: center;
|
|
208
|
+
background: transparent;
|
|
209
|
+
text-align: right;
|
|
210
|
+
border: none;
|
|
211
|
+
padding: 0;
|
|
212
|
+
margin: 0;
|
|
213
|
+
}
|
|
214
|
+
`;
|
|
215
|
+
|
|
216
|
+
export const CardHeader = styled.div`
|
|
217
|
+
display: flex;
|
|
218
|
+
align-items: center;
|
|
219
|
+
justify-content: space-between;
|
|
220
|
+
padding: 8px 16px;
|
|
221
|
+
width: 100%;
|
|
222
|
+
gap: 8px;
|
|
223
|
+
`;
|
|
224
|
+
|
|
225
|
+
export const CardBody = styled.div`
|
|
226
|
+
padding: 16px;
|
|
227
|
+
width: 100%;
|
|
228
|
+
flex-direction: column;
|
|
229
|
+
align-items: flex-start;
|
|
230
|
+
border-radius: 20px 20px 0 0;
|
|
231
|
+
border-top: 1px solid #323232;
|
|
232
|
+
background: #272727;
|
|
233
|
+
display: flex;
|
|
234
|
+
gap: 8px;
|
|
235
|
+
`;
|
|
236
|
+
|
|
237
|
+
export const BadgeButton = styled.button`
|
|
238
|
+
display: flex;
|
|
239
|
+
border-radius: 8px;
|
|
240
|
+
border: 1px solid #323232;
|
|
241
|
+
padding: 4px 8px;
|
|
242
|
+
background: transparent;
|
|
243
|
+
transition: 0.2s border-color;
|
|
244
|
+
position: relative;
|
|
245
|
+
cursor: pointer;
|
|
246
|
+
outline: none;
|
|
247
|
+
gap: 4px;
|
|
248
|
+
|
|
249
|
+
&:hover {
|
|
250
|
+
border-color: #4e4e4e;
|
|
251
|
+
}
|
|
252
|
+
`;
|
|
253
|
+
|
|
254
|
+
export const ChainButton = styled.button`
|
|
255
|
+
display: flex;
|
|
256
|
+
align-items: center;
|
|
257
|
+
padding: 0;
|
|
258
|
+
gap: 8px;
|
|
259
|
+
flex-shrink: 0;
|
|
260
|
+
cursor: pointer;
|
|
261
|
+
outline: none;
|
|
262
|
+
border: none;
|
|
263
|
+
background: transparent;
|
|
264
|
+
transition: 0.2s opacity;
|
|
265
|
+
|
|
266
|
+
&:hover {
|
|
267
|
+
opacity: 0.8;
|
|
268
|
+
}
|
|
269
|
+
`;
|
|
270
|
+
|
|
271
|
+
export const AvailableBalance = styled.div`
|
|
272
|
+
display: flex;
|
|
273
|
+
align-items: center;
|
|
274
|
+
overflow: hidden;
|
|
275
|
+
max-width: 200px;
|
|
276
|
+
white-space: nowrap;
|
|
277
|
+
gap: 4px;
|
|
278
|
+
|
|
279
|
+
p {
|
|
280
|
+
overflow: hidden;
|
|
281
|
+
text-overflow: ellipsis;
|
|
282
|
+
white-space: nowrap;
|
|
283
|
+
}
|
|
284
|
+
`;
|
|
285
|
+
|
|
286
|
+
export const Tooltip = styled.div`
|
|
287
|
+
transition: 0.2s transform, 0.2s opacity;
|
|
288
|
+
transform: translateY(8px);
|
|
289
|
+
opacity: 0;
|
|
290
|
+
position: absolute;
|
|
291
|
+
top: -48px;
|
|
292
|
+
right: 0;
|
|
293
|
+
z-index: 100000000;
|
|
294
|
+
border-radius: 16px;
|
|
295
|
+
background: var(--surface-white, #fff);
|
|
296
|
+
padding: 4px 12px;
|
|
297
|
+
justify-content: center;
|
|
298
|
+
pointer-events: none;
|
|
299
|
+
align-items: center;
|
|
300
|
+
gap: 4px;
|
|
301
|
+
|
|
302
|
+
p {
|
|
303
|
+
white-space: nowrap;
|
|
304
|
+
color: #000;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
&::after {
|
|
308
|
+
content: "";
|
|
309
|
+
position: absolute;
|
|
310
|
+
top: 100%;
|
|
311
|
+
right: 8px;
|
|
312
|
+
transform: translateX(-50%);
|
|
313
|
+
width: 0;
|
|
314
|
+
height: 0;
|
|
315
|
+
border-left: 8px solid transparent;
|
|
316
|
+
border-right: 8px solid transparent;
|
|
317
|
+
border-top: 8px solid #fff;
|
|
318
|
+
}
|
|
319
|
+
`;
|
|
320
|
+
|
|
321
|
+
export const SelectTokenButton = styled.button`
|
|
322
|
+
display: flex;
|
|
323
|
+
align-items: center;
|
|
324
|
+
gap: 8px;
|
|
325
|
+
flex-shrink: 0;
|
|
326
|
+
cursor: pointer;
|
|
327
|
+
outline: none;
|
|
328
|
+
border: none;
|
|
329
|
+
position: relative;
|
|
330
|
+
background: transparent;
|
|
331
|
+
border-radius: 24px;
|
|
332
|
+
padding: 4px;
|
|
333
|
+
padding-right: 28px;
|
|
334
|
+
margin: -4px;
|
|
335
|
+
max-width: 160px;
|
|
336
|
+
transition: 0.2s background-color;
|
|
337
|
+
|
|
338
|
+
&:hover {
|
|
339
|
+
background: rgba(255, 255, 255, 0.2);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
p {
|
|
343
|
+
overflow: hidden;
|
|
344
|
+
text-overflow: ellipsis;
|
|
345
|
+
white-space: nowrap;
|
|
346
|
+
}
|
|
347
|
+
`;
|
package/src/ui/icons/warning.tsx
CHANGED
|
@@ -3,7 +3,7 @@ export const WarningIcon = (props: React.SVGProps<SVGSVGElement>) => {
|
|
|
3
3
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
|
4
4
|
<path
|
|
5
5
|
d="M11.7427 2.00439C17.2636 1.86272 21.8537 6.22376 21.9956 11.7446C22.1371 17.2653 17.777 21.8556 12.2563 21.9976C6.73553 22.139 2.14412 17.7782 2.00244 12.2573C1.86095 6.73664 6.22204 2.14639 11.7427 2.00439ZM12.0005 9.75049C11.5863 9.75049 11.2505 10.0863 11.2505 10.5005V15.5005C11.2507 15.9145 11.5864 16.2505 12.0005 16.2505C12.4144 16.2503 12.7503 15.9144 12.7505 15.5005V10.5005C12.7505 10.0864 12.4145 9.75071 12.0005 9.75049ZM12.0005 7.25049C11.5863 7.25049 11.2505 7.58627 11.2505 8.00049V8.50049C11.2507 8.91452 11.5864 9.25049 12.0005 9.25049C12.4144 9.25027 12.7503 8.91438 12.7505 8.50049V8.00049C12.7505 7.58641 12.4145 7.25071 12.0005 7.25049Z"
|
|
6
|
-
fill="#F3AE47"
|
|
6
|
+
fill={props.color || "#F3AE47"}
|
|
7
7
|
/>
|
|
8
8
|
</svg>
|
|
9
9
|
);
|
|
@@ -1,27 +1,303 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { observer } from "mobx-react-lite";
|
|
3
|
+
import styled from "styled-components";
|
|
2
4
|
|
|
3
5
|
import { Token } from "../../core/token";
|
|
6
|
+
import { OmniWallet } from "../../core/OmniWallet";
|
|
7
|
+
import { chains, Network, OmniToken } from "../../core/chains";
|
|
8
|
+
import { Recipient } from "../../core/recipient";
|
|
9
|
+
import { formatter } from "../../core/utils";
|
|
10
|
+
import { tokens } from "../../core/tokens";
|
|
11
|
+
|
|
12
|
+
import SegmentedControl from "../uikit/tabs";
|
|
13
|
+
import { useAnimations } from "../uikit/animationts";
|
|
14
|
+
import { ActionButton, Button } from "../uikit/button";
|
|
15
|
+
import { H5, PMedium, PSmall, PTiny } from "../uikit/text";
|
|
16
|
+
import { WarningBox } from "../uikit/badge";
|
|
17
|
+
import { ImageView } from "../uikit/image";
|
|
4
18
|
|
|
5
19
|
import Popup from "../Popup";
|
|
6
|
-
import { ActionButton } from "../uikit/button";
|
|
7
|
-
import { SelectTokenPopup } from "../bridge/SelectToken";
|
|
8
20
|
import { HotKit } from "../../HotKit";
|
|
21
|
+
import { ArrowRightIcon } from "../icons/arrow-right";
|
|
22
|
+
import { TokenAmountCard } from "../bridge/TokenAmountCard";
|
|
23
|
+
import DepositQR, { QRAnimation } from "./DepositQR";
|
|
9
24
|
|
|
10
25
|
interface DepositFlowProps {
|
|
11
26
|
kit: HotKit;
|
|
12
27
|
onClose: () => void;
|
|
28
|
+
initialToken?: Token;
|
|
29
|
+
widget?: boolean;
|
|
13
30
|
}
|
|
14
31
|
|
|
15
|
-
export const DepositFlow: React.FC<DepositFlowProps> = ({ kit, onClose }) => {
|
|
16
|
-
const [
|
|
32
|
+
export const DepositFlow: React.FC<DepositFlowProps> = observer(({ kit, initialToken, onClose, widget }) => {
|
|
33
|
+
const [type, setType] = useState<"external" | "connected">("external");
|
|
34
|
+
|
|
35
|
+
const [token, setToken] = useState<Token | null>(initialToken ?? null);
|
|
36
|
+
const [state, setState] = useState<"loading" | "success" | "error" | null>(null);
|
|
37
|
+
const [sender, setSender] = useState<OmniWallet | undefined>(kit.wallets.find((w) => w.type === token?.type));
|
|
38
|
+
const animations = useAnimations();
|
|
39
|
+
|
|
40
|
+
const availableChainsToConnect = Array.from(new Set(kit.connectors.flatMap((c) => c.walletTypes)));
|
|
41
|
+
|
|
42
|
+
const [depositQoute, setDepositQoute] = useState<{
|
|
43
|
+
execute: (sender: OmniWallet, amount: bigint) => Promise<void>;
|
|
44
|
+
depositAddress?: string;
|
|
45
|
+
minAmount: number;
|
|
46
|
+
memo?: string;
|
|
47
|
+
} | null>(null);
|
|
48
|
+
|
|
49
|
+
const [error, setError] = useState<string | null>(null);
|
|
50
|
+
const [isFiat, setIsFiat] = useState<boolean>(false);
|
|
51
|
+
const [amount, setAmount] = useState<string>("0");
|
|
52
|
+
|
|
53
|
+
const [recipient, setRecipient] = useState<Recipient | undefined>(() => {
|
|
54
|
+
if (kit.priorityWallet == null) return;
|
|
55
|
+
return Recipient.fromWallet(kit.priorityWallet);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
setError(null);
|
|
60
|
+
setDepositQoute(null);
|
|
61
|
+
if (token == null || recipient == null) return;
|
|
62
|
+
kit.exchange
|
|
63
|
+
.getDepositQoute(token, recipient)
|
|
64
|
+
.then((qoute) => setDepositQoute(qoute))
|
|
65
|
+
.catch((error) => {
|
|
66
|
+
setError(error?.toString?.() || "Failed to get deposit address");
|
|
67
|
+
setDepositQoute(null);
|
|
68
|
+
console.error(error);
|
|
69
|
+
});
|
|
70
|
+
}, [token, recipient]);
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (token?.omniAddress === OmniToken.GONKA) setType("connected");
|
|
74
|
+
}, [token]);
|
|
75
|
+
|
|
76
|
+
if (type === "connected") {
|
|
77
|
+
const amountInTokens = isFiat ? +formatter.fromInput(amount) / (token?.usd || 0) : +formatter.fromInput(amount);
|
|
78
|
+
const availableBalance = token != null ? +Math.max(0, token.float(kit.balance(sender, token)) - token.reserve).toFixed(4) : 0;
|
|
79
|
+
const minimumAmount = token?.float(depositQoute?.minAmount ?? 0) ?? 0;
|
|
80
|
+
const isDisabled = amountInTokens <= 0 || amountInTokens > availableBalance || depositQoute == null || minimumAmount > amountInTokens;
|
|
81
|
+
|
|
82
|
+
const handleDeposit = async () => {
|
|
83
|
+
setState("loading");
|
|
84
|
+
try {
|
|
85
|
+
if (!token) throw new Error("No token selected");
|
|
86
|
+
if (!sender) throw new Error("No sender selected");
|
|
87
|
+
if (!depositQoute) throw new Error("No deposit qoute found");
|
|
88
|
+
await depositQoute.execute(sender, token.int(amountInTokens));
|
|
89
|
+
setState("success");
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error(error);
|
|
92
|
+
setError(error?.toString?.() || "Failed to deposit");
|
|
93
|
+
setState("error");
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (state === "loading") {
|
|
98
|
+
return (
|
|
99
|
+
<Popup widget={widget} header={<p>Deposit to HEX</p>} onClose={onClose}>
|
|
100
|
+
<div style={{ width: "100%", height: 400, display: "flex", justifyContent: "center", alignItems: "center", flexDirection: "column" }}>
|
|
101
|
+
{/* @ts-expect-error: dotlottie-wc is not typed */}
|
|
102
|
+
<dotlottie-wc key="loading" src={animations.loading} speed="1" style={{ width: 300, height: 300 }} mode="forward" loop autoplay></dotlottie-wc>
|
|
103
|
+
<H5 style={{ marginTop: -32 }}>Deposit in progress</H5>
|
|
104
|
+
</div>
|
|
105
|
+
</Popup>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (state === "success") {
|
|
110
|
+
return (
|
|
111
|
+
<Popup widget={widget} header={<p>Deposit to HEX</p>} onClose={onClose}>
|
|
112
|
+
<div style={{ width: "100%", height: 400, display: "flex", justifyContent: "center", alignItems: "center", flexDirection: "column" }}>
|
|
113
|
+
{/* @ts-expect-error: dotlottie-wc is not typed */}
|
|
114
|
+
<dotlottie-wc key="success" src={animations.success} speed="1" style={{ width: 300, height: 300 }} mode="forward" loop autoplay></dotlottie-wc>
|
|
115
|
+
<H5 style={{ marginTop: -32 }}>Deposit in progress</H5>
|
|
116
|
+
<PSmall style={{ marginTop: 8 }}>You can track your transaction in the activity</PSmall>
|
|
117
|
+
</div>
|
|
118
|
+
<ActionButton style={{ marginTop: "auto" }} onClick={() => onClose()}>
|
|
119
|
+
Continue
|
|
120
|
+
</ActionButton>
|
|
121
|
+
</Popup>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (state === "error") {
|
|
126
|
+
return (
|
|
127
|
+
<Popup widget={widget} header={<p>Deposit to HEX</p>} onClose={onClose}>
|
|
128
|
+
<div style={{ width: "100%", height: 400, marginBottom: 8, gap: 8, display: "flex", justifyContent: "center", alignItems: "center", flexDirection: "column" }}>
|
|
129
|
+
{/* @ts-expect-error: dotlottie-wc is not typed */}
|
|
130
|
+
<dotlottie-wc key="error" src={animations.failed} speed="1" style={{ width: 300, height: 300 }} mode="forward" loop autoplay></dotlottie-wc>
|
|
131
|
+
<H5 style={{ marginTop: -32 }}>Deposit failed</H5>
|
|
132
|
+
<TextField>{error}</TextField>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<ActionButton style={{ marginTop: "auto" }} onClick={() => onClose()}>
|
|
136
|
+
Continue
|
|
137
|
+
</ActionButton>
|
|
138
|
+
</Popup>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
17
141
|
|
|
18
|
-
|
|
19
|
-
|
|
142
|
+
return (
|
|
143
|
+
<Popup height={600} widget={widget} header={<p>Deposit to HEX</p>} onClose={onClose}>
|
|
144
|
+
<SegmentedControl
|
|
145
|
+
value={type}
|
|
146
|
+
onChange={(value) => setType(value as "external" | "connected")}
|
|
147
|
+
options={[
|
|
148
|
+
{ label: "External wallet", value: "external", background: "#161616" },
|
|
149
|
+
{ label: "Connected wallet", value: "connected", background: "#161616" },
|
|
150
|
+
]}
|
|
151
|
+
/>
|
|
152
|
+
|
|
153
|
+
<Card onClick={() => kit.router.openSelectRecipient({ kit, chain: Network.Omni, onSelect: (r) => setRecipient(r) })}>
|
|
154
|
+
<ImageView src={chains.get(Network.Omni)?.logo || ""} alt="HEX Wallet" size={24} />
|
|
155
|
+
{recipient == null && <PSmall>Choose receiver</PSmall>}
|
|
156
|
+
{recipient != null && (
|
|
157
|
+
<PSmall>
|
|
158
|
+
Deposit to <b style={{ color: "#b4b4b4" }}>{formatter.truncateAddress(recipient.address)}</b>
|
|
159
|
+
</PSmall>
|
|
160
|
+
)}
|
|
161
|
+
|
|
162
|
+
<ArrowRightIcon style={{ transform: "rotate(90deg)", marginLeft: "auto" }} />
|
|
163
|
+
</Card>
|
|
164
|
+
|
|
165
|
+
<TokenAmountCard
|
|
166
|
+
kit={kit}
|
|
167
|
+
disableQR
|
|
168
|
+
sender={sender}
|
|
169
|
+
isFiat={isFiat}
|
|
170
|
+
isReviewing={false}
|
|
171
|
+
amount={amountInTokens}
|
|
172
|
+
token={token ?? undefined}
|
|
173
|
+
availableBalance={availableBalance}
|
|
174
|
+
readableAmount={formatter.fromInput(amount)}
|
|
175
|
+
disableChains={[Network.Omni, Network.HotCraft].concat(tokens.list.filter((t) => !availableChainsToConnect.includes(t.type)).map((t) => t.chain))}
|
|
176
|
+
style={{ marginTop: 12, borderRadius: "20px 20px", overflow: "hidden", marginBottom: 8 }}
|
|
177
|
+
setSender={(sender) => setSender(sender as OmniWallet)}
|
|
178
|
+
handleMax={() => setAmount(String(isFiat ? availableBalance * (token?.usd || 0) : availableBalance))}
|
|
179
|
+
setValue={setAmount}
|
|
180
|
+
setIsFiat={setIsFiat}
|
|
181
|
+
setToken={setToken}
|
|
182
|
+
/>
|
|
183
|
+
|
|
184
|
+
{minimumAmount > 0 && (
|
|
185
|
+
<div style={{ width: "100%", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
|
186
|
+
<PSmall>Minimum deposit:</PSmall>
|
|
187
|
+
<PSmall>
|
|
188
|
+
{formatter.amount(minimumAmount)} {token?.symbol}
|
|
189
|
+
</PSmall>
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
192
|
+
|
|
193
|
+
<ActionButton style={{ marginTop: "auto" }} disabled={isDisabled} onClick={handleDeposit}>
|
|
194
|
+
Deposit
|
|
195
|
+
</ActionButton>
|
|
196
|
+
</Popup>
|
|
197
|
+
);
|
|
20
198
|
}
|
|
21
199
|
|
|
22
200
|
return (
|
|
23
|
-
<Popup header={<p>Deposit
|
|
24
|
-
<
|
|
201
|
+
<Popup height={670} widget={widget} header={<p>Deposit to HEX</p>} onClose={onClose}>
|
|
202
|
+
<SegmentedControl
|
|
203
|
+
options={[
|
|
204
|
+
{ label: "External wallet", value: "external", background: "#161616" },
|
|
205
|
+
{ label: "Connected wallet", value: "connected", background: "#161616" },
|
|
206
|
+
]}
|
|
207
|
+
onChange={(value) => setType(value as "external" | "connected")}
|
|
208
|
+
value={type}
|
|
209
|
+
/>
|
|
210
|
+
|
|
211
|
+
<Card onClick={() => kit.router.openSelectTokenPopup({ kit, disableChains: [Network.Omni, Network.HotCraft], onSelect: (t) => setToken(t) })}>
|
|
212
|
+
<ImageView src={token?.icon || ""} alt={token?.symbol || ""} size={24} />
|
|
213
|
+
{token == null && <PSmall>Choose token</PSmall>}
|
|
214
|
+
{token != null && (
|
|
215
|
+
<PSmall>
|
|
216
|
+
{token.symbol} ({token.chainName})
|
|
217
|
+
</PSmall>
|
|
218
|
+
)}
|
|
219
|
+
<ArrowRightIcon style={{ transform: "rotate(90deg)", marginLeft: "auto" }} />
|
|
220
|
+
</Card>
|
|
221
|
+
|
|
222
|
+
<Card onClick={() => kit.router.openSelectRecipient({ kit, chain: Network.Omni, onSelect: (r) => setRecipient(r) })}>
|
|
223
|
+
<ImageView src={chains.get(Network.Omni)?.logo || ""} alt="HEX Wallet" size={24} />
|
|
224
|
+
{recipient == null && <PSmall>Choose receiver</PSmall>}
|
|
225
|
+
{recipient != null && (
|
|
226
|
+
<PSmall>
|
|
227
|
+
Deposit to <b style={{ color: "#b4b4b4" }}>{formatter.truncateAddress(recipient.address)}</b>
|
|
228
|
+
</PSmall>
|
|
229
|
+
)}
|
|
230
|
+
<ArrowRightIcon style={{ transform: "rotate(90deg)", marginLeft: "auto" }} />
|
|
231
|
+
</Card>
|
|
232
|
+
|
|
233
|
+
{depositQoute?.depositAddress != null && token != null && (
|
|
234
|
+
<DepositQR //
|
|
235
|
+
kit={kit}
|
|
236
|
+
token={token}
|
|
237
|
+
memo={depositQoute.memo}
|
|
238
|
+
minimumAmount={token?.float(depositQoute.minAmount)}
|
|
239
|
+
depositAddress={depositQoute.depositAddress}
|
|
240
|
+
onConfirm={() => {
|
|
241
|
+
setError(null);
|
|
242
|
+
setDepositQoute(null);
|
|
243
|
+
onClose();
|
|
244
|
+
}}
|
|
245
|
+
/>
|
|
246
|
+
)}
|
|
247
|
+
|
|
248
|
+
{(depositQoute?.depositAddress == null || token == null) && (
|
|
249
|
+
<div style={{ width: "100%", height: "100%", minHeight: 300, display: "flex", justifyContent: "center", alignItems: "center", flexDirection: "column" }}>
|
|
250
|
+
<QRAnimation />
|
|
251
|
+
{depositQoute == null && <PMedium style={{ marginTop: 12, opacity: 0.5 }}>Calculating deposit address</PMedium>}
|
|
252
|
+
{depositQoute != null && (
|
|
253
|
+
<PMedium style={{ marginTop: 16, opacity: 0.5 }}>
|
|
254
|
+
{token?.symbol} token does not support
|
|
255
|
+
<br />
|
|
256
|
+
deposits via QR code. Try to deposit from a connected wallet.
|
|
257
|
+
</PMedium>
|
|
258
|
+
)}
|
|
259
|
+
</div>
|
|
260
|
+
)}
|
|
261
|
+
|
|
262
|
+
{error != null && (
|
|
263
|
+
<WarningBox style={{ marginTop: 8 }} type="error">
|
|
264
|
+
{error}
|
|
265
|
+
</WarningBox>
|
|
266
|
+
)}
|
|
25
267
|
</Popup>
|
|
26
268
|
);
|
|
27
|
-
};
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const Card = styled(Button)`
|
|
272
|
+
border-radius: 12px;
|
|
273
|
+
border: 1px solid #323232;
|
|
274
|
+
background: #1f1f1f;
|
|
275
|
+
display: flex;
|
|
276
|
+
padding: 8px;
|
|
277
|
+
align-items: center;
|
|
278
|
+
gap: 8px;
|
|
279
|
+
align-self: stretch;
|
|
280
|
+
cursor: pointer;
|
|
281
|
+
margin-top: 4px;
|
|
282
|
+
transition: background 0.2s ease-in-out;
|
|
283
|
+
|
|
284
|
+
&:hover {
|
|
285
|
+
background: #272727;
|
|
286
|
+
opacity: 1;
|
|
287
|
+
}
|
|
288
|
+
`;
|
|
289
|
+
|
|
290
|
+
const TextField = styled(PTiny)`
|
|
291
|
+
max-width: 100%;
|
|
292
|
+
min-width: 300px;
|
|
293
|
+
min-height: 64px;
|
|
294
|
+
overflow: auto;
|
|
295
|
+
max-height: 200px;
|
|
296
|
+
background: #2c2c2c;
|
|
297
|
+
border-radius: 12px;
|
|
298
|
+
font-size: 12px;
|
|
299
|
+
padding: 8px;
|
|
300
|
+
margin-bottom: 12px;
|
|
301
|
+
white-space: pre-wrap;
|
|
302
|
+
line-break: anywhere;
|
|
303
|
+
`;
|