@hot-labs/kit 1.0.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/.eslintrc.cjs +11 -0
  2. package/.github/workflows/deploy-example.yml +55 -0
  3. package/.github/workflows/release.yml +74 -0
  4. package/.prettierrc +4 -0
  5. package/.prototools +2 -0
  6. package/.vscode/settings.json +3 -0
  7. package/CHANGELOG.md +1 -0
  8. package/README.md +67 -0
  9. package/build/GoogleConnector.d.ts +20 -0
  10. package/build/GoogleConnector.js +76 -0
  11. package/build/GoogleConnector.js.map +1 -0
  12. package/build/HotConnector.d.ts +91 -0
  13. package/build/HotConnector.js +285 -0
  14. package/build/HotConnector.js.map +1 -0
  15. package/build/OmniConnector.d.ts +95 -0
  16. package/build/OmniConnector.js +123 -0
  17. package/build/OmniConnector.js.map +1 -0
  18. package/build/OmniWallet.d.ts +57 -0
  19. package/build/OmniWallet.js +56 -0
  20. package/build/OmniWallet.js.map +1 -0
  21. package/build/cosmos/connector.d.ts +53 -0
  22. package/build/cosmos/connector.js +207 -0
  23. package/build/cosmos/connector.js.map +1 -0
  24. package/build/cosmos/wallet.d.ts +28 -0
  25. package/build/cosmos/wallet.js +59 -0
  26. package/build/cosmos/wallet.js.map +1 -0
  27. package/build/events.d.ts +42 -0
  28. package/build/events.js +64 -0
  29. package/build/events.js.map +1 -0
  30. package/build/evm/abi.d.ts +17 -0
  31. package/build/evm/abi.js +38 -0
  32. package/build/evm/abi.js.map +1 -0
  33. package/build/evm/connector.d.ts +35 -0
  34. package/build/evm/connector.js +124 -0
  35. package/build/evm/connector.js.map +1 -0
  36. package/build/evm/wallet.d.ts +47 -0
  37. package/build/evm/wallet.js +136 -0
  38. package/build/evm/wallet.js.map +1 -0
  39. package/build/exchange.d.ts +63 -0
  40. package/build/exchange.js +384 -0
  41. package/build/exchange.js.map +1 -0
  42. package/build/hot-wallet/evm.d.ts +1 -0
  43. package/build/hot-wallet/evm.js +33 -0
  44. package/build/hot-wallet/evm.js.map +1 -0
  45. package/build/hot-wallet/hot.d.ts +3 -0
  46. package/build/hot-wallet/hot.js +40 -0
  47. package/build/hot-wallet/hot.js.map +1 -0
  48. package/build/hot-wallet/index.d.ts +4 -0
  49. package/build/hot-wallet/index.js +5 -0
  50. package/build/hot-wallet/index.js.map +1 -0
  51. package/build/hot-wallet/solana/account.d.ts +11 -0
  52. package/build/hot-wallet/solana/account.js +42 -0
  53. package/build/hot-wallet/solana/account.js.map +1 -0
  54. package/build/hot-wallet/solana/index.d.ts +1 -0
  55. package/build/hot-wallet/solana/index.js +85 -0
  56. package/build/hot-wallet/solana/index.js.map +1 -0
  57. package/build/hot-wallet/solana/register.d.ts +2 -0
  58. package/build/hot-wallet/solana/register.js +41 -0
  59. package/build/hot-wallet/solana/register.js.map +1 -0
  60. package/build/hot-wallet/solana/solana-wallet.d.ts +34 -0
  61. package/build/hot-wallet/solana/solana-wallet.js +223 -0
  62. package/build/hot-wallet/solana/solana-wallet.js.map +1 -0
  63. package/build/hot-wallet/solana/utils.d.ts +32 -0
  64. package/build/hot-wallet/solana/utils.js +36 -0
  65. package/build/hot-wallet/solana/utils.js.map +1 -0
  66. package/build/hot-wallet/stellar.d.ts +38 -0
  67. package/build/hot-wallet/stellar.js +32 -0
  68. package/build/hot-wallet/stellar.js.map +1 -0
  69. package/build/hot-wallet/ton.d.ts +1 -0
  70. package/build/hot-wallet/ton.js +49 -0
  71. package/build/hot-wallet/ton.js.map +1 -0
  72. package/build/hot-wallet/wallet.d.ts +1 -0
  73. package/build/hot-wallet/wallet.js +40 -0
  74. package/build/hot-wallet/wallet.js.map +1 -0
  75. package/build/index.d.ts +24 -0
  76. package/build/index.js +25 -0
  77. package/build/index.js.map +1 -0
  78. package/build/near/connector.d.ts +28 -0
  79. package/build/near/connector.js +56 -0
  80. package/build/near/connector.js.map +1 -0
  81. package/build/near/wallet.d.ts +62 -0
  82. package/build/near/wallet.js +233 -0
  83. package/build/near/wallet.js.map +1 -0
  84. package/build/omni/Intents.d.ts +93 -0
  85. package/build/omni/Intents.js +395 -0
  86. package/build/omni/Intents.js.map +1 -0
  87. package/build/omni/bridge.d.ts +3 -0
  88. package/build/omni/bridge.js +34 -0
  89. package/build/omni/bridge.js.map +1 -0
  90. package/build/omni/config.d.ts +99 -0
  91. package/build/omni/config.js +126 -0
  92. package/build/omni/config.js.map +1 -0
  93. package/build/omni/defaultTokens.d.ts +17 -0
  94. package/build/omni/defaultTokens.js +1079 -0
  95. package/build/omni/defaultTokens.js.map +1 -0
  96. package/build/omni/index.d.ts +8 -0
  97. package/build/omni/index.js +9 -0
  98. package/build/omni/index.js.map +1 -0
  99. package/build/omni/nearRpc.d.ts +24 -0
  100. package/build/omni/nearRpc.js +167 -0
  101. package/build/omni/nearRpc.js.map +1 -0
  102. package/build/omni/recipient.d.ts +10 -0
  103. package/build/omni/recipient.js +40 -0
  104. package/build/omni/recipient.js.map +1 -0
  105. package/build/omni/token.d.ts +36 -0
  106. package/build/omni/token.js +136 -0
  107. package/build/omni/token.js.map +1 -0
  108. package/build/omni/tokens.d.ts +14 -0
  109. package/build/omni/tokens.js +57 -0
  110. package/build/omni/tokens.js.map +1 -0
  111. package/build/omni/types.d.ts +41 -0
  112. package/build/omni/types.js +2 -0
  113. package/build/omni/types.js.map +1 -0
  114. package/build/omni/utils.d.ts +24 -0
  115. package/build/omni/utils.js +150 -0
  116. package/build/omni/utils.js.map +1 -0
  117. package/build/settings.d.ts +5 -0
  118. package/build/settings.js +6 -0
  119. package/build/settings.js.map +1 -0
  120. package/build/solana/connector.d.ts +31 -0
  121. package/build/solana/connector.js +138 -0
  122. package/build/solana/connector.js.map +1 -0
  123. package/build/solana/protocol.d.ts +25 -0
  124. package/build/solana/protocol.js +56 -0
  125. package/build/solana/protocol.js.map +1 -0
  126. package/build/solana/wallet.d.ts +47 -0
  127. package/build/solana/wallet.js +188 -0
  128. package/build/solana/wallet.js.map +1 -0
  129. package/build/solana/wallets.d.ts +102 -0
  130. package/build/solana/wallets.js +150 -0
  131. package/build/solana/wallets.js.map +1 -0
  132. package/build/stellar/connector.d.ts +25 -0
  133. package/build/stellar/connector.js +56 -0
  134. package/build/stellar/connector.js.map +1 -0
  135. package/build/stellar/wallet.d.ts +70 -0
  136. package/build/stellar/wallet.js +225 -0
  137. package/build/stellar/wallet.js.map +1 -0
  138. package/build/storage.d.ts +10 -0
  139. package/build/storage.js +12 -0
  140. package/build/storage.js.map +1 -0
  141. package/build/ton/connector.d.ts +25 -0
  142. package/build/ton/connector.js +119 -0
  143. package/build/ton/connector.js.map +1 -0
  144. package/build/ton/utils.d.ts +45 -0
  145. package/build/ton/utils.js +64 -0
  146. package/build/ton/utils.js.map +1 -0
  147. package/build/ton/wallet.d.ts +70 -0
  148. package/build/ton/wallet.js +154 -0
  149. package/build/ton/wallet.js.map +1 -0
  150. package/build/ui/Popup.d.ts +10 -0
  151. package/build/ui/Popup.js +38 -0
  152. package/build/ui/Popup.js.map +1 -0
  153. package/build/ui/connect/AuthPopup.d.ts +2 -0
  154. package/build/ui/connect/AuthPopup.js +48 -0
  155. package/build/ui/connect/AuthPopup.js.map +1 -0
  156. package/build/ui/connect/ConnectWallet.d.ts +9 -0
  157. package/build/ui/connect/ConnectWallet.js +22 -0
  158. package/build/ui/connect/ConnectWallet.js.map +1 -0
  159. package/build/ui/connect/LogoutPopup.d.ts +10 -0
  160. package/build/ui/connect/LogoutPopup.js +8 -0
  161. package/build/ui/connect/LogoutPopup.js.map +1 -0
  162. package/build/ui/connect/WCPopup.d.ts +13 -0
  163. package/build/ui/connect/WCPopup.js +81 -0
  164. package/build/ui/connect/WCPopup.js.map +1 -0
  165. package/build/ui/connect/WCRequest.d.ts +9 -0
  166. package/build/ui/connect/WCRequest.js +13 -0
  167. package/build/ui/connect/WCRequest.js.map +1 -0
  168. package/build/ui/connect/WalletPicker.d.ts +11 -0
  169. package/build/ui/connect/WalletPicker.js +48 -0
  170. package/build/ui/connect/WalletPicker.js.map +1 -0
  171. package/build/ui/icons/arrow-right.d.ts +1 -0
  172. package/build/ui/icons/arrow-right.js +5 -0
  173. package/build/ui/icons/arrow-right.js.map +1 -0
  174. package/build/ui/icons/exchange.d.ts +6 -0
  175. package/build/ui/icons/exchange.js +6 -0
  176. package/build/ui/icons/exchange.js.map +1 -0
  177. package/build/ui/icons/logout.d.ts +1 -0
  178. package/build/ui/icons/logout.js +3 -0
  179. package/build/ui/icons/logout.js.map +1 -0
  180. package/build/ui/icons/pending.d.ts +1 -0
  181. package/build/ui/icons/pending.js +5 -0
  182. package/build/ui/icons/pending.js.map +1 -0
  183. package/build/ui/icons/qr.d.ts +1 -0
  184. package/build/ui/icons/qr.js +5 -0
  185. package/build/ui/icons/qr.js.map +1 -0
  186. package/build/ui/icons/search.d.ts +1 -0
  187. package/build/ui/icons/search.js +5 -0
  188. package/build/ui/icons/search.js.map +1 -0
  189. package/build/ui/icons/switch.d.ts +1 -0
  190. package/build/ui/icons/switch.js +5 -0
  191. package/build/ui/icons/switch.js.map +1 -0
  192. package/build/ui/icons/wallet.d.ts +1 -0
  193. package/build/ui/icons/wallet.js +5 -0
  194. package/build/ui/icons/wallet.js.map +1 -0
  195. package/build/ui/payment/Bridge.d.ts +27 -0
  196. package/build/ui/payment/Bridge.js +340 -0
  197. package/build/ui/payment/Bridge.js.map +1 -0
  198. package/build/ui/payment/DepositQR.d.ts +9 -0
  199. package/build/ui/payment/DepositQR.js +56 -0
  200. package/build/ui/payment/DepositQR.js.map +1 -0
  201. package/build/ui/payment/Payment.d.ts +16 -0
  202. package/build/ui/payment/Payment.js +50 -0
  203. package/build/ui/payment/Payment.js.map +1 -0
  204. package/build/ui/payment/Profile.d.ts +7 -0
  205. package/build/ui/payment/Profile.js +88 -0
  206. package/build/ui/payment/Profile.js.map +1 -0
  207. package/build/ui/payment/SelectRecipient.d.ts +14 -0
  208. package/build/ui/payment/SelectRecipient.js +68 -0
  209. package/build/ui/payment/SelectRecipient.js.map +1 -0
  210. package/build/ui/payment/SelectSender.d.ts +13 -0
  211. package/build/ui/payment/SelectSender.js +23 -0
  212. package/build/ui/payment/SelectSender.js.map +1 -0
  213. package/build/ui/payment/SelectToken.d.ts +13 -0
  214. package/build/ui/payment/SelectToken.js +92 -0
  215. package/build/ui/payment/SelectToken.js.map +1 -0
  216. package/build/ui/payment/TokenCard.d.ts +23 -0
  217. package/build/ui/payment/TokenCard.js +50 -0
  218. package/build/ui/payment/TokenCard.js.map +1 -0
  219. package/build/ui/router.d.ts +37 -0
  220. package/build/ui/router.js +56 -0
  221. package/build/ui/router.js.map +1 -0
  222. package/build/ui/styles.d.ts +11 -0
  223. package/build/ui/styles.js +273 -0
  224. package/build/ui/styles.js.map +1 -0
  225. package/package.json +60 -0
  226. package/skill.md +989 -0
  227. package/src/GoogleConnector.ts +102 -0
  228. package/src/HotConnector.ts +344 -0
  229. package/src/OmniConnector.ts +161 -0
  230. package/src/OmniWallet.ts +94 -0
  231. package/src/cosmos/connector.ts +242 -0
  232. package/src/cosmos/wallet.ts +77 -0
  233. package/src/events.ts +66 -0
  234. package/src/evm/abi.ts +39 -0
  235. package/src/evm/connector.ts +139 -0
  236. package/src/evm/wallet.ts +156 -0
  237. package/src/exchange.ts +426 -0
  238. package/src/hot-wallet/evm.ts +38 -0
  239. package/src/hot-wallet/hot.ts +39 -0
  240. package/src/hot-wallet/index.ts +4 -0
  241. package/src/hot-wallet/solana/account.ts +52 -0
  242. package/src/hot-wallet/solana/index.ts +98 -0
  243. package/src/hot-wallet/solana/register.ts +48 -0
  244. package/src/hot-wallet/solana/solana-wallet.ts +280 -0
  245. package/src/hot-wallet/solana/utils.ts +56 -0
  246. package/src/hot-wallet/stellar.ts +39 -0
  247. package/src/hot-wallet/ton.ts +56 -0
  248. package/src/hot-wallet/wallet.ts +44 -0
  249. package/src/index.ts +30 -0
  250. package/src/near/connector.ts +71 -0
  251. package/src/near/wallet.ts +255 -0
  252. package/src/omni/Intents.ts +454 -0
  253. package/src/omni/bridge.ts +38 -0
  254. package/src/omni/config.ts +130 -0
  255. package/src/omni/defaultTokens.ts +1078 -0
  256. package/src/omni/index.ts +8 -0
  257. package/src/omni/nearRpc.ts +187 -0
  258. package/src/omni/recipient.ts +41 -0
  259. package/src/omni/token.ts +125 -0
  260. package/src/omni/tokens.ts +68 -0
  261. package/src/omni/types.ts +46 -0
  262. package/src/omni/utils.ts +163 -0
  263. package/src/settings.ts +5 -0
  264. package/src/solana/connector.ts +151 -0
  265. package/src/solana/protocol.ts +66 -0
  266. package/src/solana/wallet.ts +230 -0
  267. package/src/solana/wallets.ts +243 -0
  268. package/src/stellar/connector.ts +69 -0
  269. package/src/stellar/wallet.ts +267 -0
  270. package/src/storage.ts +19 -0
  271. package/src/ton/connector.ts +138 -0
  272. package/src/ton/utils.ts +73 -0
  273. package/src/ton/wallet.ts +178 -0
  274. package/src/ui/Popup.tsx +62 -0
  275. package/src/ui/connect/AuthPopup.tsx +78 -0
  276. package/src/ui/connect/ConnectWallet.tsx +63 -0
  277. package/src/ui/connect/LogoutPopup.tsx +20 -0
  278. package/src/ui/connect/WCPopup.tsx +122 -0
  279. package/src/ui/connect/WCRequest.tsx +28 -0
  280. package/src/ui/connect/WalletPicker.tsx +92 -0
  281. package/src/ui/icons/arrow-right.tsx +8 -0
  282. package/src/ui/icons/exchange.tsx +17 -0
  283. package/src/ui/icons/logout.tsx +18 -0
  284. package/src/ui/icons/pending.tsx +18 -0
  285. package/src/ui/icons/qr.tsx +12 -0
  286. package/src/ui/icons/search.tsx +18 -0
  287. package/src/ui/icons/switch.tsx +10 -0
  288. package/src/ui/icons/wallet.tsx +18 -0
  289. package/src/ui/payment/Bridge.tsx +582 -0
  290. package/src/ui/payment/DepositQR.tsx +88 -0
  291. package/src/ui/payment/Payment.tsx +78 -0
  292. package/src/ui/payment/Profile.tsx +140 -0
  293. package/src/ui/payment/SelectRecipient.tsx +111 -0
  294. package/src/ui/payment/SelectSender.tsx +60 -0
  295. package/src/ui/payment/SelectToken.tsx +134 -0
  296. package/src/ui/payment/TokenCard.tsx +83 -0
  297. package/src/ui/router.tsx +92 -0
  298. package/src/ui/styles.ts +284 -0
  299. package/tsconfig.json +22 -0
  300. package/vite.config.ts +17 -0
@@ -0,0 +1,582 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import styled, { keyframes } from "styled-components";
3
+ import { observer } from "mobx-react-lite";
4
+ import uuid4 from "uuid4";
5
+
6
+ import { SwitchIcon } from "../icons/switch";
7
+ import { ArrowRightIcon } from "../icons/arrow-right";
8
+
9
+ import { formatter } from "../../omni/utils";
10
+ import { tokens } from "../../omni/tokens";
11
+ import { Recipient } from "../../omni/recipient";
12
+ import { OmniWallet } from "../../OmniWallet";
13
+ import { BridgeReview } from "../../exchange";
14
+ import { HotConnector } from "../../HotConnector";
15
+ import { WalletType } from "../../omni/config";
16
+ import { Token } from "../../omni/token";
17
+
18
+ import Popup from "../Popup";
19
+ import { PopupButton } from "../styles";
20
+ import { openSelectRecipient, openSelectTokenPopup, openSelectSender } from "../router";
21
+ import { TokenIcon } from "./TokenCard";
22
+ import DepositQR from "./DepositQR";
23
+
24
+ export interface BridgeProps {
25
+ hot: HotConnector;
26
+ widget?: boolean;
27
+ onClose: () => void;
28
+ onProcess: (task: Promise<BridgeReview>) => void;
29
+ setup?: {
30
+ autoClose?: boolean; // if true, the popup will close automatically when the transaction is successful
31
+ title?: string;
32
+ readonlyAmount?: boolean;
33
+ readonlyTo?: boolean;
34
+ readonlyFrom?: boolean;
35
+ type?: "exactIn" | "exactOut";
36
+ sender?: OmniWallet;
37
+ recipient?: Recipient;
38
+ amount?: number;
39
+ from?: Token;
40
+ to?: Token;
41
+ };
42
+ }
43
+
44
+ const FIXED = 6;
45
+
46
+ export const Bridge = observer(({ hot, widget, setup, onClose, onProcess }: BridgeProps) => {
47
+ const [isFiat, setIsFiat] = useState(false);
48
+ const [type, setType] = useState<"exactIn" | "exactOut">(setup?.type || "exactIn");
49
+ const [value, setValue] = useState<string>(setup?.amount?.toString() ?? "");
50
+ const [from, setFrom] = useState<Token>(setup?.from || tokens.list.find((t) => t.id === localStorage.getItem("bridge:from")) || tokens.list.find((t) => t.symbol === "NEAR")!);
51
+ const [to, setTo] = useState<Token>(setup?.to || tokens.list.find((t) => t.id === localStorage.getItem("bridge:to")) || tokens.list.find((t) => t.symbol === "USDT")!);
52
+
53
+ const [review, setReview] = useState<BridgeReview | null>(null);
54
+ const [isError, setIsError] = useState<string | null>(null);
55
+ const [isReviewing, setIsReviewing] = useState(false);
56
+
57
+ const [processing, setProcessing] = useState<{
58
+ status: "qr" | "processing" | "success" | "error";
59
+ resolve?: (value: BridgeReview) => void;
60
+ reject?: (error: Error) => void;
61
+ message: string;
62
+ review: BridgeReview;
63
+ } | null>(null);
64
+
65
+ const [sender, setSender] = useState<OmniWallet | "qr" | undefined>(() => {
66
+ if (setup?.sender) return setup.sender;
67
+ if (from.type === WalletType.OMNI) return hot.priorityWallet;
68
+ return hot.wallets.find((w) => w.type === from.type);
69
+ });
70
+
71
+ const [recipient, setRecipient] = useState<Recipient | undefined>(() => {
72
+ if (setup?.recipient) return setup.recipient;
73
+ if (to.type === WalletType.OMNI) return Recipient.fromWallet(hot.priorityWallet!);
74
+ return Recipient.fromWallet(hot.wallets.find((w) => w.type === to.type)!);
75
+ });
76
+
77
+ const initialSender = useRef<OmniWallet | "qr" | undefined>(sender);
78
+ const initialRecipient = useRef<Recipient | undefined>(recipient);
79
+
80
+ const valueInTokens = isFiat ? +formatter.fromInput(value) / (type === "exactIn" ? from.usd : to.usd) : +formatter.fromInput(value);
81
+ const amountFrom = type === "exactOut" ? from.float(review?.amountIn ?? 0) : valueInTokens;
82
+ const amountTo = type === "exactIn" ? to.float(review?.amountOut ?? 0) : valueInTokens;
83
+
84
+ const showAmountFrom = type === "exactOut" ? +from.float(review?.amountIn ?? 0).toFixed(FIXED) : formatter.fromInput(value);
85
+ const showAmountTo = type === "exactIn" ? +to.float(review?.amountOut ?? 0).toFixed(FIXED) : formatter.fromInput(value);
86
+
87
+ const availableBalance = sender !== "qr" ? +Math.max(0, from.float(hot.balance(sender, from)) - from.reserve).toFixed(FIXED) : 0;
88
+
89
+ useEffect(() => {
90
+ localStorage.setItem("bridge:from", from.id);
91
+ localStorage.setItem("bridge:to", to.id);
92
+ }, [from, to]);
93
+
94
+ useEffect(() => {
95
+ if (initialSender.current == null) {
96
+ if (from.type === WalletType.OMNI) setSender(hot.priorityWallet);
97
+ else setSender(hot.wallets.find((w) => w.type === from.type));
98
+ }
99
+
100
+ if (initialRecipient.current == null) {
101
+ if (to.type === WalletType.OMNI) setRecipient(Recipient.fromWallet(hot.priorityWallet));
102
+ else setRecipient(Recipient.fromWallet(hot.wallets.find((w) => w.type === to.type)));
103
+ }
104
+ }, [to, from, hot.wallets, hot.priorityWallet]);
105
+
106
+ const reviewId = useRef(uuid4());
107
+ const throwError = (message: string) => {
108
+ setIsError(message);
109
+ setIsReviewing(false);
110
+ };
111
+
112
+ const reviewSwap = useCallback(() => {
113
+ reviewId.current = uuid4();
114
+ const currentReviewId = reviewId.current;
115
+ setIsReviewing(true);
116
+ setReview(null);
117
+ setIsError(null);
118
+
119
+ const refund = sender !== "qr" ? sender : hot.priorityWallet;
120
+ if (valueInTokens <= 0) return throwError("Enter amount");
121
+ if (!sender) return throwError("Set sender");
122
+ if (!recipient) return throwError("Set recipient");
123
+ if (!refund) return throwError("Connect any wallet");
124
+
125
+ const debounceTimer = setTimeout(async () => {
126
+ try {
127
+ if (currentReviewId !== reviewId.current) return;
128
+ const amount = type === "exactIn" ? from.int(valueInTokens) : to.int(valueInTokens);
129
+ const review = await hot.exchange.reviewSwap({ sender, refund, amount, recipient, slippage: 0.005, type, from, to });
130
+ if (currentReviewId !== reviewId.current) return;
131
+ setIsReviewing(false);
132
+ setIsError(null);
133
+ setReview(review);
134
+ } catch (e) {
135
+ if (currentReviewId !== reviewId.current) return;
136
+ setIsError(typeof e === "string" ? e : e instanceof Error ? e.message : "Failed to review swap");
137
+ setIsReviewing(false);
138
+ setReview(null);
139
+ console.error(e);
140
+ }
141
+ }, 500);
142
+
143
+ return () => clearTimeout(debounceTimer);
144
+ }, [valueInTokens, type, from, to, sender, hot.exchange, recipient, hot.priorityWallet]);
145
+
146
+ useEffect(() => {
147
+ reviewSwap();
148
+ }, [reviewSwap]);
149
+
150
+ const process = async (review: BridgeReview) => {
151
+ try {
152
+ setProcessing({ status: "processing", message: "Signing transaction", review });
153
+ const result = await hot.exchange.makeSwap(review, { log: (message: string) => setProcessing({ status: "processing", message, review }) });
154
+ setProcessing({ status: "success", message: "Transaction signed", review: result });
155
+ if (setup?.autoClose) onClose();
156
+ return result;
157
+ } catch (e) {
158
+ setProcessing({ status: "error", message: e?.toString?.() ?? "Unknown error", review });
159
+ throw e;
160
+ }
161
+ };
162
+
163
+ const cancelReview = () => {
164
+ setReview(null);
165
+ setIsReviewing(false);
166
+ setProcessing(null);
167
+ setIsError(null);
168
+ reviewSwap();
169
+ };
170
+
171
+ const handleConfirm = async () => {
172
+ if (sender != "qr") return onProcess(process(review!));
173
+ setProcessing({ status: "qr", message: "Scan QR code to sign transaction", review: review! });
174
+ };
175
+
176
+ const handleMax = () => {
177
+ if (sender === "qr") return;
178
+ if (isFiat) {
179
+ setType("exactIn");
180
+ setValue(String(+(availableBalance * from.usd).toFixed(FIXED)));
181
+ } else {
182
+ setType("exactIn");
183
+ setValue(String(availableBalance));
184
+ }
185
+ };
186
+
187
+ if (processing?.status === "qr") {
188
+ return (
189
+ <Popup widget={widget} onClose={onClose} header={setup?.title ? <p>{setup?.title}</p> : null}>
190
+ <DepositQR //
191
+ review={processing.review}
192
+ onConfirm={() => onProcess(process(processing.review))}
193
+ onCancel={cancelReview}
194
+ />
195
+ </Popup>
196
+ );
197
+ }
198
+
199
+ if (processing?.status === "processing") {
200
+ return (
201
+ <Popup widget={widget} onClose={onClose} header={setup?.title ? <p>{setup?.title}</p> : null}>
202
+ <div style={{ width: "100%", height: 400, display: "flex", justifyContent: "center", alignItems: "center", flexDirection: "column" }}>
203
+ {/* @ts-expect-error: dotlottie-wc is not typed */}
204
+ <dotlottie-wc key="loading" src="/loading.json" speed="1" style={{ width: 300, height: 300 }} mode="forward" loop autoplay></dotlottie-wc>
205
+ <p style={{ marginTop: -32, fontSize: 16 }}>{processing.message}</p>
206
+ </div>
207
+ </Popup>
208
+ );
209
+ }
210
+
211
+ if (processing?.status === "success") {
212
+ return (
213
+ <Popup widget={widget} onClose={onClose} header={setup?.title ? <p>{setup?.title}</p> : null}>
214
+ <div style={{ width: "100%", height: 400, display: "flex", justifyContent: "center", alignItems: "center", flexDirection: "column" }}>
215
+ {/* @ts-expect-error: dotlottie-wc is not typed */}
216
+ <dotlottie-wc key="success" src="/success.json" speed="1" style={{ width: 300, height: 300 }} mode="forward" loop autoplay></dotlottie-wc>
217
+ <p style={{ fontSize: 24, marginTop: -32, fontWeight: "bold" }}>Exchange successful</p>
218
+ </div>
219
+ <PopupButton style={{ marginTop: "auto" }} onClick={() => (cancelReview(), onClose())}>
220
+ Continue
221
+ </PopupButton>
222
+ </Popup>
223
+ );
224
+ }
225
+
226
+ if (processing?.status === "error") {
227
+ return (
228
+ <Popup widget={widget} onClose={onClose} header={setup?.title ? <p>{setup?.title}</p> : null}>
229
+ <div style={{ width: "100%", height: 400, gap: 8, display: "flex", justifyContent: "center", alignItems: "center", flexDirection: "column" }}>
230
+ {/* @ts-expect-error: dotlottie-wc is not typed */}
231
+ <dotlottie-wc key="error" src="/error.json" speed="1" style={{ width: 300, height: 300 }} mode="forward" loop autoplay></dotlottie-wc>
232
+ <p style={{ fontSize: 24, marginTop: -32, fontWeight: "bold" }}>Exchange failed</p>
233
+ <p style={{ fontSize: 14 }}>{processing.message}</p>
234
+ </div>
235
+ <PopupButton onClick={() => (cancelReview(), onClose())}>Continue</PopupButton>
236
+ </Popup>
237
+ );
238
+ }
239
+
240
+ const button = () => {
241
+ if (sender == null) return <PopupButton disabled>Set sender</PopupButton>;
242
+ if (recipient == null) return <PopupButton disabled>Set recipient</PopupButton>;
243
+ if (sender !== "qr" && +from.float(hot.balance(sender, from)).toFixed(FIXED) < +amountFrom.toFixed(FIXED)) return <PopupButton disabled>Insufficient balance</PopupButton>;
244
+ return (
245
+ <PopupButton disabled={isReviewing || isError != null} onClick={handleConfirm}>
246
+ {isReviewing ? "Quoting..." : isError != null ? isError : "Confirm"}
247
+ </PopupButton>
248
+ );
249
+ };
250
+
251
+ return (
252
+ <Popup widget={widget} onClose={onClose} header={setup?.title ? <p>{setup?.title}</p> : null}>
253
+ <div style={{ display: "flex", flexDirection: "column", gap: 32, width: "100%", height: "100%" }}>
254
+ <Card>
255
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", width: "100%" }}>
256
+ <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
257
+ <p style={{ fontWeight: "bold" }}>{from.chain === -4 ? "Withdraw HEX from:" : "Send from:"}</p>
258
+ <BadgeButton onClick={() => openSelectSender({ hot, type: from.type, onSelect: (wallet) => setSender(wallet) })}>
259
+ <p>{formatter.truncateAddress(sender === "qr" ? "QR code" : sender?.address ?? "Connect wallet")}</p>
260
+ </BadgeButton>
261
+ </div>
262
+ </div>
263
+
264
+ <div style={{ width: "100%", display: "flex", alignItems: "center", gap: 8, justifyContent: "space-between" }}>
265
+ <TokenPreview //
266
+ token={from}
267
+ style={{ pointerEvents: setup?.readonlyFrom ? "none" : "all" }}
268
+ onSelect={() => openSelectTokenPopup({ hot, onSelect: (token, wallet) => (setFrom(token), setSender(wallet)) })}
269
+ />
270
+
271
+ {isReviewing && type === "exactOut" ? (
272
+ <SkeletonShine />
273
+ ) : (
274
+ <input //
275
+ name="from"
276
+ type="text"
277
+ className="input"
278
+ autoComplete="off"
279
+ autoCapitalize="off"
280
+ autoCorrect="off"
281
+ readOnly={setup?.readonlyAmount}
282
+ value={isFiat ? `$${showAmountFrom}` : showAmountFrom}
283
+ onChange={(e) => (setType("exactIn"), setValue(e.target.value))}
284
+ placeholder="0"
285
+ autoFocus
286
+ />
287
+ )}
288
+ </div>
289
+
290
+ {isFiat && (
291
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", width: "100%" }}>
292
+ {sender !== "qr" && (
293
+ <AvailableBalance>
294
+ <p>Balance: ${from.readable(availableBalance, from.usd)}</p>
295
+ <RefreshButton onClick={() => sender && hot.fetchToken(from, sender)} />
296
+ </AvailableBalance>
297
+ )}
298
+
299
+ {sender === "qr" && <div />}
300
+
301
+ <div style={{ display: "flex", alignItems: "center", gap: 4, flexShrink: 0 }}>
302
+ {from.usd !== 0 && <p style={{ marginRight: 8 }}>{`${from.readable(amountFrom / from.usd)} ${from.symbol}`}</p>}
303
+ {from.usd !== 0 && (
304
+ <BadgeButton style={{ border: `1px solid #fff` }} onClick={() => setIsFiat(!isFiat)}>
305
+ USD
306
+ </BadgeButton>
307
+ )}
308
+ {sender !== "qr" && <BadgeButton onClick={handleMax}>MAX</BadgeButton>}
309
+ </div>
310
+ </div>
311
+ )}
312
+
313
+ {!isFiat && (
314
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", width: "100%" }}>
315
+ {sender !== "qr" && (
316
+ <AvailableBalance>
317
+ <p>Balance: {`${from.readable(availableBalance)} ${from.symbol}`}</p>
318
+ <RefreshButton onClick={() => sender && hot.fetchToken(from, sender)} />
319
+ </AvailableBalance>
320
+ )}
321
+ {sender === "qr" && <div />}
322
+ <div style={{ display: "flex", alignItems: "center", gap: 4 }}>
323
+ {from.usd !== 0 && <p style={{ marginRight: 8 }}>${from.readable(amountFrom, from.usd)}</p>}
324
+ {from.usd !== 0 && <BadgeButton onClick={() => setIsFiat(!isFiat)}>USD</BadgeButton>}
325
+ {sender !== "qr" && <BadgeButton onClick={handleMax}>MAX</BadgeButton>}
326
+ </div>
327
+ </div>
328
+ )}
329
+ </Card>
330
+
331
+ <div style={{ position: "relative" }}>
332
+ <div style={{ width: "100%", height: 1, backgroundColor: "rgba(255, 255, 255, 0.07)" }} />
333
+ <SwitchButton
334
+ onClick={() => {
335
+ setFrom(to);
336
+ setTo(from);
337
+ setSender(hot.wallets.find((w) => w.address === recipient?.address));
338
+ setRecipient(sender === "qr" ? undefined : sender ? Recipient.fromWallet(sender) : undefined);
339
+ setType(type === "exactIn" ? "exactOut" : "exactIn");
340
+ setValue("");
341
+ }}
342
+ >
343
+ <SwitchIcon />
344
+ </SwitchButton>
345
+ </div>
346
+
347
+ <Card>
348
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", width: "100%" }}>
349
+ <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
350
+ <p style={{ fontWeight: "bold" }}>{to.chain !== -4 ? "To:" : "Deposit HEX to:"}</p>
351
+ <BadgeButton onClick={() => openSelectRecipient({ hot, recipient, type: to.type, onSelect: (recipient) => setRecipient(recipient) })}>
352
+ <p>{formatter.truncateAddress(recipient?.address ?? "Connect wallet")}</p>
353
+ </BadgeButton>
354
+ </div>
355
+ </div>
356
+
357
+ <div style={{ width: "100%", display: "flex", alignItems: "center", gap: 8, justifyContent: "space-between" }}>
358
+ <TokenPreview //
359
+ token={to}
360
+ style={{ pointerEvents: setup?.readonlyTo ? "none" : "all" }}
361
+ onSelect={() =>
362
+ openSelectTokenPopup({
363
+ hot,
364
+ onSelect: (token, wallet) => {
365
+ setRecipient(wallet ? Recipient.fromWallet(wallet) : undefined);
366
+ setTo(token);
367
+ },
368
+ })
369
+ }
370
+ />
371
+
372
+ {isReviewing && type === "exactIn" ? (
373
+ <SkeletonShine />
374
+ ) : (
375
+ <input //
376
+ name="to"
377
+ type="text"
378
+ className="input"
379
+ autoComplete="off"
380
+ autoCapitalize="off"
381
+ autoCorrect="off"
382
+ readOnly={setup?.readonlyAmount}
383
+ value={isFiat ? `$${+(+showAmountTo * to.usd).toFixed(FIXED)}` : showAmountTo}
384
+ onChange={(e) => (setType("exactOut"), setValue(e.target.value))}
385
+ placeholder="0"
386
+ />
387
+ )}
388
+ </div>
389
+
390
+ <div style={{ width: "100%", display: "flex", justifyContent: "flex-end", marginTop: -8 }}>
391
+ {isFiat && <p>Receive: ${`${to.readable(amountTo ?? 0)} ${to.symbol}`}</p>}
392
+ {!isFiat && <p>Receive: ${to.readable(amountTo ?? 0, to.usd)}</p>}
393
+ </div>
394
+ </Card>
395
+
396
+ <div style={{ marginTop: "auto" }}>{button()}</div>
397
+ </div>
398
+ </Popup>
399
+ );
400
+ });
401
+
402
+ const TokenPreview = ({ style, token, onSelect }: { style?: React.CSSProperties; token: Token; onSelect: (token: Token) => void }) => {
403
+ return (
404
+ <SelectTokenButton style={style} onClick={() => onSelect(token)}>
405
+ <TokenIcon token={token} />
406
+ <p style={{ fontSize: 24, fontWeight: "bold" }}>{token.symbol}</p>
407
+ <ArrowRightIcon style={{ flexShrink: 0, position: "absolute", right: 4 }} />
408
+ </SelectTokenButton>
409
+ );
410
+ };
411
+
412
+ const BadgeButton = styled.button`
413
+ font-size: 12px;
414
+ font-weight: 500;
415
+ color: #fff;
416
+ background: #282c30;
417
+ padding: 4px 8px;
418
+ border-radius: 16px;
419
+ cursor: pointer;
420
+ outline: none;
421
+ border: none;
422
+ border: 1px solid transparent;
423
+ transition: 0.2s border-color;
424
+
425
+ &:hover {
426
+ border-color: #4e4e4e;
427
+ }
428
+
429
+ * {
430
+ font-size: 14px;
431
+ font-weight: bold;
432
+ }
433
+ `;
434
+
435
+ const SelectTokenButton = styled.button`
436
+ display: flex;
437
+ align-items: center;
438
+ gap: 12px;
439
+ flex-shrink: 0;
440
+ cursor: pointer;
441
+ outline: none;
442
+ border: none;
443
+ position: relative;
444
+ background: transparent;
445
+ border-radius: 32px;
446
+ padding: 8px;
447
+ padding-right: 32px;
448
+ margin: -8px;
449
+ max-width: 160px;
450
+ transition: 0.2s background-color;
451
+
452
+ &:hover {
453
+ background: rgba(255, 255, 255, 0.2);
454
+ }
455
+
456
+ p {
457
+ overflow: hidden;
458
+ text-overflow: ellipsis;
459
+ white-space: nowrap;
460
+ }
461
+ `;
462
+
463
+ const AvailableBalance = styled.div`
464
+ display: flex;
465
+ align-items: center;
466
+ gap: 4;
467
+ overflow: hidden;
468
+ max-width: 200px;
469
+ text-wrap: nowrap;
470
+
471
+ p {
472
+ overflow: hidden;
473
+ text-overflow: ellipsis;
474
+ white-space: nowrap;
475
+ }
476
+ `;
477
+
478
+ const Card = styled.div`
479
+ display: flex;
480
+ width: 100%;
481
+ flex-direction: column;
482
+ gap: 16px;
483
+ width: 100%;
484
+ text-align: left;
485
+ align-items: flex-start;
486
+ justify-content: center;
487
+ border-radius: 12px;
488
+
489
+ input {
490
+ outline: none;
491
+ border: none;
492
+ background: none;
493
+ color: #fff;
494
+ font-size: 32px;
495
+ font-weight: bold;
496
+ width: 100%;
497
+ line-height: 40px;
498
+ text-align: left;
499
+ align-items: flex-start;
500
+ justify-content: center;
501
+ background: transparent;
502
+ text-align: right;
503
+ border: none;
504
+ padding: 0;
505
+ margin: 0;
506
+ }
507
+ `;
508
+
509
+ const SwitchButton = styled.button`
510
+ position: absolute;
511
+ left: 50%;
512
+ top: -18px;
513
+ transform: translate(-50%, 0);
514
+ background: #232323;
515
+ border-radius: 50%;
516
+ width: 36px;
517
+ height: 36px;
518
+ display: flex;
519
+ align-items: center;
520
+ justify-content: center;
521
+ z-index: 2;
522
+ cursor: pointer;
523
+ border: 2px solid #181818;
524
+ box-shadow: 0 2px 8px 0 #18181870;
525
+ outline: none;
526
+ border: none;
527
+ cursor: pointer;
528
+ `;
529
+
530
+ const shine = keyframes`
531
+ 0% {
532
+ background-position: -200px 0;
533
+ }
534
+ 100% {
535
+ background-position: calc(200px + 100%) 0;
536
+ }
537
+ `;
538
+
539
+ const SkeletonShine = styled.div`
540
+ display: inline-block;
541
+ width: 100px;
542
+ height: 40px;
543
+ border-radius: 8px;
544
+ background: #2e2e2e;
545
+ position: relative;
546
+ overflow: hidden;
547
+
548
+ &:after {
549
+ content: "";
550
+ display: block;
551
+ height: 100%;
552
+ width: 100%;
553
+ position: absolute;
554
+ top: 0;
555
+ left: 0;
556
+ background: linear-gradient(90deg, rgba(34, 34, 34, 0) 0%, rgba(255, 255, 255, 0.06) 40%, rgba(255, 255, 255, 0.12) 50%, rgba(255, 255, 255, 0.06) 60%, rgba(34, 34, 34, 0) 100%);
557
+ background-size: 200px 100%;
558
+ animation: ${shine} 1.4s infinite linear;
559
+ }
560
+ `;
561
+
562
+ const RefreshButton = ({ onClick }: { onClick: () => void }) => {
563
+ return (
564
+ <svg
565
+ onClick={onClick}
566
+ style={{ width: 18, height: 18, verticalAlign: "middle", marginLeft: 8, cursor: "pointer", opacity: 0.7, transition: "opacity 0.2s" }}
567
+ viewBox="0 0 24 24"
568
+ fill="none"
569
+ stroke="#fff"
570
+ strokeWidth="2"
571
+ strokeLinecap="round"
572
+ strokeLinejoin="round"
573
+ onMouseOver={(e) => (e.currentTarget.style.opacity = "1")}
574
+ onMouseOut={(e) => (e.currentTarget.style.opacity = "0.7")}
575
+ >
576
+ <path d="M23 4v6h-6" />
577
+ <path d="M1 20v-6h6" />
578
+ <path d="M3.51 9a9 9 0 0114.13-3.36L23 10" />
579
+ <path d="M20.49 15a9 9 0 01-14.13 3.36L1 14" />
580
+ </svg>
581
+ );
582
+ };
@@ -0,0 +1,88 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import QRCodeStyling from "qr-code-styling";
3
+ import { observer } from "mobx-react-lite";
4
+ import styled from "styled-components";
5
+
6
+ import { BridgeReview } from "../../exchange";
7
+ import { PopupButton } from "../styles";
8
+
9
+ const DepositQR = observer(({ review, onConfirm, onCancel }: { review: BridgeReview; onConfirm: () => void; onCancel: () => void }) => {
10
+ const qrCodeRef = useRef<HTMLDivElement>(null);
11
+ const [qrCode] = useState<QRCodeStyling | null>(() => {
12
+ if (review.qoute === "deposit" || review.qoute === "withdraw") return null;
13
+ return new QRCodeStyling({
14
+ data: review.qoute.depositAddress,
15
+ dotsOptions: { color: "#eeeeee", type: "rounded" },
16
+ backgroundOptions: { color: "transparent" },
17
+ shape: "circle",
18
+ width: 180,
19
+ height: 180,
20
+ type: "svg",
21
+ });
22
+ });
23
+
24
+ useEffect(() => {
25
+ if (!qrCodeRef.current) return;
26
+ if (review.qoute === "deposit" || review.qoute === "withdraw") return;
27
+ qrCode?.append(qrCodeRef.current);
28
+ }, []);
29
+
30
+ if (review.qoute === "deposit" || review.qoute === "withdraw") return null;
31
+
32
+ return (
33
+ <div style={{ position: "relative", width: "100%", height: "100%", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", flex: 1 }}>
34
+ <CloseButton onClick={onCancel} aria-label="Close">
35
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
36
+ <circle cx="12" cy="12" r="11" stroke="#2d2d2d" strokeWidth="2" fill="#181818" />
37
+ <path d="M8 8l8 8M16 8l-8 8" stroke="#fff" strokeWidth="2" strokeLinecap="round" />
38
+ </svg>
39
+ </CloseButton>
40
+
41
+ <div
42
+ ref={qrCodeRef}
43
+ style={{
44
+ marginTop: "auto",
45
+ borderRadius: "50%",
46
+ display: "flex",
47
+ alignItems: "center",
48
+ justifyContent: "center",
49
+ padding: 8,
50
+ paddingTop: 10,
51
+ paddingLeft: 10,
52
+ border: "1px solid #2d2d2d",
53
+ background: "#1c1c1c",
54
+ }}
55
+ ></div>
56
+
57
+ <p style={{ marginTop: 24 }}>
58
+ Send{" "}
59
+ <b>
60
+ {review.qoute.amountInFormatted} {review.from.symbol}
61
+ </b>{" "}
62
+ on <b>{review.from.chainName}</b> to
63
+ </p>
64
+
65
+ <div style={{ width: "100%", marginTop: 8, padding: 12, marginBottom: 24, border: "1px solid #2d2d2d", borderRadius: 12, background: "#1c1c1c" }}>
66
+ <p style={{ wordBreak: "break-all" }}>{review.qoute.depositAddress}</p>
67
+ </div>
68
+
69
+ <PopupButton style={{ marginTop: "auto" }} onClick={onConfirm}>
70
+ I sent the funds
71
+ </PopupButton>
72
+ </div>
73
+ );
74
+ });
75
+
76
+ const CloseButton = styled.button`
77
+ position: absolute;
78
+ top: -8px;
79
+ right: -12px;
80
+ background: transparent;
81
+ border: none;
82
+ cursor: pointer;
83
+ padding: 6;
84
+ border-radius: 50%;
85
+ transition: background 0.2s;
86
+ `;
87
+
88
+ export default DepositQR;