@hot-labs/kit 1.4.15 → 1.4.17

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 (50) hide show
  1. package/CHANGELOG.md +3 -1
  2. package/README.md +7 -5
  3. package/build/core/OmniConnector.d.ts +1 -0
  4. package/build/core/OmniConnector.js +1 -0
  5. package/build/core/OmniConnector.js.map +1 -1
  6. package/build/core/chains.d.ts +1 -0
  7. package/build/core/chains.js +28 -0
  8. package/build/core/chains.js.map +1 -1
  9. package/build/core/exchange.d.ts +6 -5
  10. package/build/core/exchange.js +28 -14
  11. package/build/core/exchange.js.map +1 -1
  12. package/build/evm/wallet.d.ts +1 -1
  13. package/build/evm/wallet.js +1 -3
  14. package/build/evm/wallet.js.map +1 -1
  15. package/build/hot-wallet/google.d.ts +1 -0
  16. package/build/hot-wallet/google.js +10 -7
  17. package/build/hot-wallet/google.js.map +1 -1
  18. package/build/stellar/wallet.js +8 -3
  19. package/build/stellar/wallet.js.map +1 -1
  20. package/build/ui/bridge/Bridge.js +57 -11
  21. package/build/ui/bridge/Bridge.js.map +1 -1
  22. package/build/ui/bridge/SelectRecipient.js +2 -2
  23. package/build/ui/bridge/SelectRecipient.js.map +1 -1
  24. package/build/ui/connect/ConnectWallet.js +2 -2
  25. package/build/ui/connect/ConnectWallet.js.map +1 -1
  26. package/build/ui/icons/home.d.ts +1 -0
  27. package/build/ui/icons/home.js +5 -0
  28. package/build/ui/icons/home.js.map +1 -0
  29. package/build/ui/profile/Profile.js +6 -3
  30. package/build/ui/profile/Profile.js.map +1 -1
  31. package/build/ui/styles.js +1 -1
  32. package/build/ui/uikit/button.js +2 -2
  33. package/examples-node/.env +2 -0
  34. package/examples-node/transfer.ts +2 -2
  35. package/examples-node/withdraw.ts +2 -1
  36. package/package.json +1 -1
  37. package/skill.md +19 -20
  38. package/src/core/OmniConnector.ts +2 -0
  39. package/src/core/chains.ts +29 -0
  40. package/src/core/exchange.ts +30 -19
  41. package/src/evm/wallet.ts +1 -4
  42. package/src/hot-wallet/google.ts +11 -7
  43. package/src/stellar/wallet.ts +9 -4
  44. package/src/ui/bridge/Bridge.tsx +62 -5
  45. package/src/ui/bridge/SelectRecipient.tsx +3 -3
  46. package/src/ui/connect/ConnectWallet.tsx +18 -18
  47. package/src/ui/icons/home.tsx +12 -0
  48. package/src/ui/profile/Profile.tsx +15 -3
  49. package/src/ui/styles.ts +1 -1
  50. package/src/ui/uikit/button.tsx +2 -2
@@ -142,6 +142,17 @@ export const Bridge = observer(({ hot, widget, setup, onClose, onProcess, onSele
142
142
  setIsReviewing(false);
143
143
  };
144
144
 
145
+ const openTooltip = (id: string) => {
146
+ const tooltip = document.getElementById(id);
147
+ if (!tooltip) return;
148
+ tooltip.style.transform = "translateY(0)";
149
+ tooltip.style.opacity = "1";
150
+ setTimeout(() => {
151
+ tooltip.style.transform = "translateY(8px)";
152
+ tooltip.style.opacity = "0";
153
+ }, 3000);
154
+ };
155
+
145
156
  const reviewSwap = useCallback(() => {
146
157
  reviewId.current = uuid4();
147
158
  const currentReviewId = reviewId.current;
@@ -151,9 +162,6 @@ export const Bridge = observer(({ hot, widget, setup, onClose, onProcess, onSele
151
162
 
152
163
  const refund = sender !== "qr" ? sender : hot.priorityWallet;
153
164
  if (valueInTokens <= 0) return throwError("Enter amount");
154
- if (!sender) return throwError("Set sender");
155
- if (!recipient) return throwError("Set recipient");
156
- if (!refund) return throwError("Connect any wallet");
157
165
 
158
166
  const debounceTimer = setTimeout(async () => {
159
167
  try {
@@ -162,6 +170,12 @@ export const Bridge = observer(({ hot, widget, setup, onClose, onProcess, onSele
162
170
  const recipientWallet = hot.wallets.find((w) => w.address === recipient?.address && w.type === recipient?.type) || recipient;
163
171
  const review = await hot.exchange.reviewSwap({ recipient: recipientWallet, slippage: 0.005, sender, refund, amount, type, from, to });
164
172
  if (currentReviewId !== reviewId.current) return;
173
+
174
+ if (amount > 0) {
175
+ if (!sender) setTimeout(() => openTooltip("sender-tooltip"), 100);
176
+ if (!recipient) openTooltip("recipient-tooltip");
177
+ }
178
+
165
179
  setIsReviewing(false);
166
180
  setIsError(null);
167
181
  setReview(review);
@@ -283,8 +297,8 @@ export const Bridge = observer(({ hot, widget, setup, onClose, onProcess, onSele
283
297
  }
284
298
 
285
299
  const button = () => {
286
- if (sender == null) return <ActionButton disabled>Set sender</ActionButton>;
287
- if (recipient == null) return <ActionButton disabled>Set recipient</ActionButton>;
300
+ if (sender == null) return <ActionButton disabled>Confirm</ActionButton>;
301
+ if (recipient == null) return <ActionButton disabled>Confirm</ActionButton>;
288
302
  if (sender !== "qr" && +from.float(hot.balance(sender, from)).toFixed(FIXED) < +amountFrom.toFixed(FIXED)) return <ActionButton disabled>Insufficient balance</ActionButton>;
289
303
  return (
290
304
  <ActionButton style={{ width: "100%", marginTop: 40 }} disabled={isReviewing || isError != null} onClick={handleConfirm}>
@@ -307,6 +321,7 @@ export const Bridge = observer(({ hot, widget, setup, onClose, onProcess, onSele
307
321
 
308
322
  <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
309
323
  <PSmall>Sender:</PSmall>
324
+
310
325
  <BadgeButton
311
326
  onClick={() => {
312
327
  if (from.type === WalletType.OMNI) openSelectSender({ hot, type: from.type, onSelect: (sender) => setSender(sender) });
@@ -314,6 +329,9 @@ export const Bridge = observer(({ hot, widget, setup, onClose, onProcess, onSele
314
329
  }}
315
330
  >
316
331
  <PSmall>{sender == null ? "Select" : sender !== "qr" ? formatter.truncateAddress(sender.address, 8) : "QR"}</PSmall>
332
+ <Tooltip id="sender-tooltip">
333
+ <PSmall>Select sender wallet</PSmall>
334
+ </Tooltip>
317
335
  </BadgeButton>
318
336
  </div>
319
337
  </CardHeader>
@@ -433,6 +451,9 @@ export const Bridge = observer(({ hot, widget, setup, onClose, onProcess, onSele
433
451
  <PSmall>Recipient:</PSmall>
434
452
  <BadgeButton onClick={() => openSelectRecipient({ hot, chain: to.chain, onSelect: (recipient) => setRecipient(recipient) })}>
435
453
  <PSmall>{recipient == null ? "Select" : formatter.truncateAddress(recipient.address, 8)}</PSmall>
454
+ <Tooltip id="recipient-tooltip">
455
+ <PSmall>Select recipient wallet</PSmall>
456
+ </Tooltip>
436
457
  </BadgeButton>
437
458
  </div>
438
459
  </CardHeader>
@@ -495,6 +516,41 @@ const TokenPreview = ({ style, token, onSelect }: { style?: React.CSSProperties;
495
516
  );
496
517
  };
497
518
 
519
+ const Tooltip = styled.div`
520
+ transition: 0.2s transform, 0.2s opacity;
521
+ transform: translateY(8px);
522
+ opacity: 0;
523
+ position: absolute;
524
+ top: -48px;
525
+ right: 0;
526
+ z-index: 100000000;
527
+ border-radius: 16px;
528
+ background: var(--surface-white, #fff);
529
+ padding: 4px 12px;
530
+ justify-content: center;
531
+ pointer-events: none;
532
+ align-items: center;
533
+ gap: 4px;
534
+
535
+ p {
536
+ white-space: nowrap;
537
+ color: #000;
538
+ }
539
+
540
+ &::after {
541
+ content: "";
542
+ position: absolute;
543
+ top: 100%;
544
+ right: 8px;
545
+ transform: translateX(-50%);
546
+ width: 0;
547
+ height: 0;
548
+ border-left: 8px solid transparent;
549
+ border-right: 8px solid transparent;
550
+ border-top: 8px solid #fff;
551
+ }
552
+ `;
553
+
498
554
  const BadgeButton = styled.button`
499
555
  display: flex;
500
556
  border-radius: 8px;
@@ -502,6 +558,7 @@ const BadgeButton = styled.button`
502
558
  padding: 4px 8px;
503
559
  background: transparent;
504
560
  transition: 0.2s border-color;
561
+ position: relative;
505
562
  cursor: pointer;
506
563
  outline: none;
507
564
  gap: 4px;
@@ -25,10 +25,7 @@ interface SelectRecipientProps {
25
25
  }
26
26
 
27
27
  export const SelectRecipient = observer(({ recipient, hot, chain, onSelect, onClose }: SelectRecipientProps) => {
28
- const connectors = hot.connectors.filter((t) => t.walletTypes.includes(type) && t.type !== ConnectorType.SOCIAL);
29
28
  const [customAddress, setCustomAddress] = useState<string>(recipient?.address || "");
30
-
31
- const isError = !Recipient.isValidAddress(chain, customAddress) && customAddress.length > 0;
32
29
  const type = chains.get(chain)?.type;
33
30
 
34
31
  if (!type)
@@ -38,6 +35,9 @@ export const SelectRecipient = observer(({ recipient, hot, chain, onSelect, onCl
38
35
  </Popup>
39
36
  );
40
37
 
38
+ const connectors = hot.connectors.filter((t) => t.walletTypes.includes(type) && t.type !== ConnectorType.SOCIAL);
39
+ const isError = !Recipient.isValidAddress(chain, customAddress) && customAddress.length > 0;
40
+
41
41
  const selectCustom = async () => {
42
42
  const recipient = await Recipient.fromAddress(type, customAddress);
43
43
  onSelect(recipient);
@@ -23,7 +23,7 @@ interface MultichainPopupProps {
23
23
 
24
24
  export const Connector = observer(({ hot, onClose, title, walletType, widget }: MultichainPopupProps) => {
25
25
  const onechain = hot.connectors.filter((t) => t.type === ConnectorType.WALLET && (walletType == null || t.walletTypes.includes(walletType as WalletType)) && t.options.length > 0);
26
- const social = hot.connectors.filter((t) => t.type === ConnectorType.SOCIAL && (walletType == null || t.walletTypes.includes(walletType as WalletType)) && t.options.length > 0);
26
+ const social = hot.connectors.filter((t) => t.type === ConnectorType.SOCIAL && (walletType == null || t.walletTypes.includes(walletType as WalletType)));
27
27
 
28
28
  const selectConnector = async (t: OmniConnector) => {
29
29
  if (t.wallets[0]) return t.disconnect();
@@ -33,25 +33,8 @@ export const Connector = observer(({ hot, onClose, title, walletType, widget }:
33
33
 
34
34
  return (
35
35
  <Popup header={<p>{title || "Connect wallet"}</p>} onClose={onClose} widget={widget}>
36
- {onechain.map((t) => (
37
- <PopupOption key={t.id} onClick={() => selectConnector(t)}>
38
- <ImageView src={t.icon} alt={t.name} size={44} />
39
- <PopupOptionInfo>
40
- <p>{t.name}</p>
41
- {t.wallets[0]?.address && <span className="wallet-address">{formatter.truncateAddress(t.wallets[0].address, 24)}</span>}
42
- </PopupOptionInfo>
43
- {t.wallets[0]?.address && <LogoutIcon width={32} height={32} />}
44
- </PopupOption>
45
- ))}
46
-
47
36
  {social.length > 0 && (
48
37
  <>
49
- <div style={{ margin: "4px 0", display: "flex", width: "100%", alignItems: "center", justifyContent: "center", gap: "8px" }}>
50
- <div style={{ height: "1px", flex: 1, background: "rgba(255,255,255,0.1)" }}></div>
51
- <div>or</div>
52
- <div style={{ height: "1px", flex: 1, background: "rgba(255,255,255,0.1)" }}></div>
53
- </div>
54
-
55
38
  {social.map((t) => (
56
39
  <PopupOption key={t.id} onClick={() => selectConnector(t)}>
57
40
  <ImageView src={t.icon} alt={t.name} size={44} />
@@ -62,8 +45,25 @@ export const Connector = observer(({ hot, onClose, title, walletType, widget }:
62
45
  {t.wallets[0]?.address && <LogoutIcon width={32} height={32} />}
63
46
  </PopupOption>
64
47
  ))}
48
+
49
+ <div style={{ margin: "4px 0", display: "flex", width: "100%", alignItems: "center", justifyContent: "center", gap: "8px" }}>
50
+ <div style={{ height: "1px", flex: 1, background: "rgba(255,255,255,0.1)" }}></div>
51
+ <div>or</div>
52
+ <div style={{ height: "1px", flex: 1, background: "rgba(255,255,255,0.1)" }}></div>
53
+ </div>
65
54
  </>
66
55
  )}
56
+
57
+ {onechain.map((t) => (
58
+ <PopupOption key={t.id} onClick={() => selectConnector(t)}>
59
+ <ImageView src={t.icon} alt={t.name} size={44} />
60
+ <PopupOptionInfo>
61
+ <p>{t.name}</p>
62
+ {t.wallets[0]?.address && <span className="wallet-address">{formatter.truncateAddress(t.wallets[0].address, 24)}</span>}
63
+ </PopupOptionInfo>
64
+ {t.wallets[0]?.address && <LogoutIcon width={32} height={32} />}
65
+ </PopupOption>
66
+ ))}
67
67
  </Popup>
68
68
  );
69
69
  });
@@ -0,0 +1,12 @@
1
+ export const HomeIcon = (props: React.SVGProps<SVGSVGElement>) => {
2
+ return (
3
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
4
+ <path
5
+ fill-rule="evenodd"
6
+ clip-rule="evenodd"
7
+ d="M3.16669 11.7087C3.16669 11.9848 3.39054 12.2087 3.66669 12.2087H6.34718C6.33809 12.1545 6.33335 12.0988 6.33335 12.042V10.042C6.33335 9.48973 6.78107 9.04202 7.33335 9.04202H8.00002C8.5523 9.04202 9.00002 9.48973 9.00002 10.042V12.042C9.00002 12.0988 8.99529 12.1545 8.98619 12.2087H11.6667C11.9428 12.2087 12.1667 11.9848 12.1667 11.7087V6.85598C12.1667 6.70408 12.0976 6.56043 11.979 6.46554L7.95259 3.24438C7.77663 3.10362 7.52835 3.09799 7.34624 3.23039C7.34568 3.2308 7.34512 3.2312 7.34456 3.23161L3.37101 6.14556C3.33507 6.17191 3.30325 6.20261 3.27605 6.23665C3.21212 6.31668 3.17375 6.41517 3.16757 6.51902C3.16698 6.52889 3.16669 6.5388 3.16669 6.54875V6.54876M8.56531 2.4331C8.03636 2.02861 7.30406 2.02125 6.76709 2.41503L6.75611 2.42308C6.75514 2.42379 6.75416 2.4245 6.75319 2.42521L2.77964 5.33915C2.39433 5.62171 2.16669 6.07094 2.16669 6.54875V11.7087C2.16669 11.7088 2.16669 11.7088 2.16669 11.7089V12.3754C2.16669 13.2038 2.83826 13.8754 3.66669 13.8754H12.3334C13.1618 13.8754 13.8334 13.2038 13.8334 12.3754V7.20286C13.8334 6.73566 13.6157 6.29513 13.2445 6.01133L8.56531 2.4331Z"
8
+ fill="#2C3034"
9
+ />
10
+ </svg>
11
+ );
12
+ };
@@ -5,7 +5,10 @@ import { useEffect } from "react";
5
5
  import PlusIcon from "../icons/plus";
6
6
  import { LogoutIcon } from "../icons/logout";
7
7
  import ExchangeIcon from "../icons/exchange";
8
+ import { ImageView } from "../uikit/image";
9
+ import { Loader } from "../uikit/loader";
8
10
 
11
+ import { ConnectorType } from "../../core/OmniConnector";
9
12
  import { formatter } from "../../core/utils";
10
13
  import { OmniToken } from "../../core/chains";
11
14
  import { tokens } from "../../core/tokens";
@@ -13,8 +16,6 @@ import { tokens } from "../../core/tokens";
13
16
  import { HotConnector } from "../../HotConnector";
14
17
  import { openBridge, openConnector } from "../router";
15
18
  import { TokenCard, TokenIcon } from "../bridge/TokenCard";
16
- import { ImageView } from "../uikit/image";
17
- import { Loader } from "../uikit/loader";
18
19
  import { PopupOption } from "../styles";
19
20
  import Popup from "../Popup";
20
21
 
@@ -45,6 +46,7 @@ export const Profile = observer(({ hot, onClose }: { hot: HotConnector; onClose:
45
46
 
46
47
  const omniTokens = tokensList.filter((t) => t.chain === -4);
47
48
  const nonOmniTokens = tokensList.filter((t) => t.chain !== -4);
49
+ const socialConnector = hot.connectors.find((connector) => connector.type === ConnectorType.SOCIAL && connector.wallets.length > 0);
48
50
 
49
51
  useEffect(() => {
50
52
  if (hot.wallets.length > 0) return;
@@ -58,6 +60,7 @@ export const Profile = observer(({ hot, onClose }: { hot: HotConnector; onClose:
58
60
  connector.wallets.map((wallet) => (
59
61
  <WalletCard onClick={() => connector.disconnect()}>
60
62
  <ImageView src={wallet.icon} alt={connector.name} size={20} />
63
+ {connector.type === ConnectorType.SOCIAL && <ImageView style={{ position: "absolute", bottom: 4, left: 20 }} src={connector.icon} alt={connector.name} size={12} />}
61
64
  <div>{formatter.truncateAddress(wallet.address, 8)}</div>
62
65
  <LogoutIcon />
63
66
  </WalletCard>
@@ -76,11 +79,19 @@ export const Profile = observer(({ hot, onClose }: { hot: HotConnector; onClose:
76
79
  <PSmall>YOUR BALANCE</PSmall>
77
80
  <BalanceCard>${formatter.amount(totalBalance)}</BalanceCard>
78
81
 
79
- <div style={{ width: "100%", display: "flex", gap: 12, marginTop: 24 }}>
82
+ <div style={{ width: "100%", display: "flex", gap: 12, marginTop: 12, flexWrap: "wrap" }}>
80
83
  <ActionButton onClick={() => (onClose(), openBridge(hot, { title: "Exchange" }))}>
81
84
  <ExchangeIcon />
82
85
  Exchange
83
86
  </ActionButton>
87
+
88
+ {socialConnector != null && (
89
+ <ActionButton onClick={() => socialConnector.openWallet()}>
90
+ <ImageView src={socialConnector.icon} alt={socialConnector.name} size={20} />
91
+ Open wallet
92
+ </ActionButton>
93
+ )}
94
+
84
95
  <ActionButton disabled onClick={() => (onClose(), openBridge(hot, { title: "Deposit" }))}>
85
96
  Deposit
86
97
  </ActionButton>
@@ -184,6 +195,7 @@ const WalletCard = styled.div`
184
195
  background: #1a1a1a;
185
196
  cursor: pointer;
186
197
  transition: background 0.2s ease-in-out;
198
+ position: relative;
187
199
 
188
200
  &:hover {
189
201
  background: rgba(255, 255, 255, 0.04);
package/src/ui/styles.ts CHANGED
@@ -116,7 +116,7 @@ export const ModalBody = styled.div`
116
116
  text-align: center;
117
117
  gap: 4px;
118
118
  overflow: auto;
119
- border-radius: 24px;
119
+ border-radius: 22px;
120
120
  background: rgba(255, 255, 255, 0.08);
121
121
  width: 100%;
122
122
  flex: 1;
@@ -19,7 +19,7 @@ export const ActionButton = styled.button<{ $stroke?: boolean }>`
19
19
  flex: 1;
20
20
  width: 100%;
21
21
 
22
- color: #121212;
22
+ color: #121212 !important;
23
23
  text-align: center;
24
24
  font-family: "Golos Text";
25
25
  font-size: 16px;
@@ -43,7 +43,7 @@ export const ActionButton = styled.button<{ $stroke?: boolean }>`
43
43
  css`
44
44
  background: transparent;
45
45
  border: 1px solid #d2d2d2;
46
- color: #fff;
46
+ color: #fff !important;
47
47
 
48
48
  &:hover {
49
49
  background: rgba(255, 255, 255, 0.1);