@hot-labs/kit 1.4.15 → 1.4.16

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.
@@ -2,7 +2,7 @@ import { GetExecutionStatusResponse, OneClickService, ApiError, QuoteRequest, Qu
2
2
  import { utils } from "@hot-labs/omni-sdk";
3
3
  import { hex } from "@scure/base";
4
4
 
5
- import { Network, OmniToken, WalletType } from "./chains";
5
+ import { chains, Network, OmniToken, WalletType } from "./chains";
6
6
  import { createHotBridge, ReviewFee } from "./bridge";
7
7
  import { OmniWallet } from "./OmniWallet";
8
8
  import { Recipient } from "./recipient";
@@ -36,19 +36,20 @@ export type BridgeReview = {
36
36
  qoute: QuoteResponse["quote"] | "withdraw" | "deposit";
37
37
  status: "pending" | "success" | "failed";
38
38
  statusMessage: string | null;
39
- sender: OmniWallet | "qr";
40
- recipient: OmniWallet | Recipient;
39
+ sender?: OmniWallet | "qr";
40
+ refund?: OmniWallet;
41
+ recipient?: OmniWallet | Recipient;
41
42
  logger?: ILogger;
42
43
  from: Token;
43
44
  to: Token;
44
45
  };
45
46
 
46
47
  export interface BridgeRequest {
47
- refund: OmniWallet;
48
- sender: OmniWallet | "qr";
48
+ refund?: OmniWallet;
49
+ sender?: OmniWallet | "qr";
49
50
  type?: "exactIn" | "exactOut";
50
51
  logger?: ILogger;
51
- recipient: Recipient;
52
+ recipient?: Recipient;
52
53
  slippage: number;
53
54
  amount: bigint;
54
55
  from: Token;
@@ -155,6 +156,7 @@ export class Exchange {
155
156
  }
156
157
 
157
158
  async withdrawFee(request: BridgeRequest) {
159
+ if (request.recipient == null) return 0n;
158
160
  if (request.sender === "qr") throw new Error("Sender is QR");
159
161
  if (request.to.chain === Network.Near || request.to.chain === Network.Omni) return 0n;
160
162
  const gaslessFee = await this.bridge.getGaslessWithdrawFee({
@@ -195,13 +197,16 @@ export class Exchange {
195
197
  const noFee = from.symbol === to.symbol || (from.symbol.toLowerCase().includes("usd") && to.symbol.toLowerCase().includes("usd"));
196
198
 
197
199
  if (sender !== "qr" && this.isDirectDeposit(from, to)) {
198
- const fee = await this.bridge.getDepositFee({
199
- intentAccount: sender.omniAddress,
200
- sender: sender.address,
201
- token: from.address,
202
- chain: from.chain,
203
- amount: amount,
204
- });
200
+ let fee: ReviewFee | null = null;
201
+ if (sender != null) {
202
+ fee = await this.bridge.getDepositFee({
203
+ intentAccount: sender.omniAddress,
204
+ sender: sender.address,
205
+ token: from.address,
206
+ chain: from.chain,
207
+ amount: amount,
208
+ });
209
+ }
205
210
 
206
211
  return {
207
212
  from: from,
@@ -209,6 +214,7 @@ export class Exchange {
209
214
  minAmountOut: amount,
210
215
  sender: sender,
211
216
  recipient,
217
+ refund: refund,
212
218
  amountIn: amount,
213
219
  amountOut: amount,
214
220
  slippage: slippage,
@@ -236,18 +242,19 @@ export class Exchange {
236
242
  statusMessage: null,
237
243
  status: "pending",
238
244
  qoute: "withdraw",
245
+ refund: refund,
239
246
  logger,
240
247
  };
241
248
  }
242
249
 
243
- const refundParams = { refundType: QuoteRequest.refundType.ORIGIN_CHAIN, refundTo: refund.address };
244
- if (refund.type !== from.type) {
250
+ const refundParams = { refundType: QuoteRequest.refundType.ORIGIN_CHAIN, refundTo: refund?.address || chains.get(from.chain)?.testAddress };
251
+ if (refund?.type !== from.type && refund != null) {
245
252
  if (!refund.omniAddress) throw "Setup refund address";
246
253
  refundParams.refundType = QuoteRequest.refundType.INTENTS;
247
254
  refundParams.refundTo = refund.omniAddress;
248
255
  }
249
256
 
250
- if (recipient.type === WalletType.STELLAR) {
257
+ if (recipient?.type === WalletType.STELLAR) {
251
258
  const isTokenActivated = await StellarWallet.isTokenActivated(recipient.address, request.to.address);
252
259
  if (!isTokenActivated && !(recipient instanceof StellarWallet)) throw "Token not activated for recipient";
253
260
  }
@@ -269,7 +276,7 @@ export class Exchange {
269
276
  depositType: from.chain === Network.Omni ? QuoteRequest.depositType.INTENTS : QuoteRequest.depositType.ORIGIN_CHAIN,
270
277
  depositMode: from.chain === Network.Stellar ? QuoteRequest.depositMode.MEMO : QuoteRequest.depositMode.SIMPLE,
271
278
  recipientType: to.chain === Network.Omni ? QuoteRequest.recipientType.INTENTS : QuoteRequest.recipientType.DESTINATION_CHAIN,
272
- recipient: to.chain === Network.Omni ? recipient.omniAddress : recipient.address,
279
+ recipient: (to.chain === Network.Omni ? recipient?.omniAddress : recipient?.address) || chains.get(to.chain)?.testAddress,
273
280
  appFees: noFee ? [] : [{ recipient: "intents.tg", fee: 25 }],
274
281
  amount: request.amount.toString(),
275
282
  referral: "intents.tg",
@@ -293,7 +300,7 @@ export class Exchange {
293
300
  }
294
301
 
295
302
  let fee: ReviewFee | null = null;
296
- if (request.from.chain !== Network.Omni && sender !== "qr") {
303
+ if (request.from.chain !== Network.Omni && sender !== "qr" && sender != null) {
297
304
  const amount = BigInt(qoute.quote.amountIn);
298
305
  const depositAddress = qoute.quote.depositAddress!;
299
306
  fee = await sender.transferFee(request.from, depositAddress, amount).catch(() => null);
@@ -310,6 +317,7 @@ export class Exchange {
310
317
  statusMessage: null,
311
318
  qoute: qoute.quote,
312
319
  status: "pending",
320
+ refund: refund,
313
321
  sender: sender,
314
322
  fee: fee,
315
323
  logger,
@@ -317,7 +325,10 @@ export class Exchange {
317
325
  }
318
326
 
319
327
  async makeSwap(review: BridgeReview): Promise<{ review: BridgeReview; processing?: () => Promise<BridgeReview> }> {
320
- const { sender, recipient, logger } = review;
328
+ const { sender, recipient, logger, refund } = review;
329
+ if (sender == null) throw new Error("Sender is required");
330
+ if (recipient == null) throw new Error("Recipient is required");
331
+ if (refund == null) throw new Error("Refund is required");
321
332
 
322
333
  if (review.qoute === "withdraw") {
323
334
  if (sender === "qr") throw new Error("Sender is QR");
@@ -53,12 +53,17 @@ class StellarWallet extends OmniWallet {
53
53
  }
54
54
 
55
55
  async fetchBalances(chain: number): Promise<Record<string, bigint>> {
56
+ console.log("fetchBalances", chain);
56
57
  if (chain === Network.Omni) return await super.fetchBalances(chain);
57
58
  try {
58
- return await super.fetchBalances(chain);
59
- } catch {
59
+ const balances = await super.fetchBalances(chain);
60
+ console.log("balances", balances);
61
+ return balances;
62
+ } catch (e) {
63
+ console.log("error", e);
60
64
  const data = await fetch(`https://horizon.stellar.org/accounts/${this.address}`).then((res) => res.json());
61
- const balances = data.balances?.map((ft: { asset_type: string; sponsor?: string | null; asset_code: string; asset_issuer: string; balance: string }) => {
65
+ console.log("data", data);
66
+ const balances: [string, bigint][] = data.balances?.map((ft: { asset_type: string; sponsor?: string | null; asset_code: string; asset_issuer: string; balance: string }) => {
62
67
  const asset = ft.asset_type === "native" ? Asset.native() : new Asset(ft.asset_code, ft.asset_issuer);
63
68
  const contractId = ft.asset_type === "native" ? "native" : asset.contractId(Networks.PUBLIC);
64
69
 
@@ -73,7 +78,7 @@ class StellarWallet extends OmniWallet {
73
78
  return [contractId, BigInt(formatter.parseAmount(ft.balance, 7))];
74
79
  });
75
80
 
76
- Object.entries(balances).forEach(([address, balance]) => this.setBalance(`${chain}:${address}`, BigInt(balance as string)));
81
+ balances.forEach(([address, balance]) => this.setBalance(`${chain}:${address}`, balance));
77
82
  return Object.fromEntries(balances);
78
83
  }
79
84
  }
@@ -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);