@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.
- package/build/core/chains.d.ts +1 -0
- package/build/core/chains.js +28 -0
- package/build/core/chains.js.map +1 -1
- package/build/core/exchange.d.ts +6 -5
- package/build/core/exchange.js +28 -14
- package/build/core/exchange.js.map +1 -1
- package/build/stellar/wallet.js +8 -3
- package/build/stellar/wallet.js.map +1 -1
- package/build/ui/bridge/Bridge.js +57 -11
- package/build/ui/bridge/Bridge.js.map +1 -1
- package/build/ui/bridge/SelectRecipient.js +2 -2
- package/build/ui/bridge/SelectRecipient.js.map +1 -1
- package/package.json +1 -1
- package/src/core/chains.ts +29 -0
- package/src/core/exchange.ts +30 -19
- package/src/stellar/wallet.ts +9 -4
- package/src/ui/bridge/Bridge.tsx +62 -5
- package/src/ui/bridge/SelectRecipient.tsx +3 -3
package/src/core/exchange.ts
CHANGED
|
@@ -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
|
|
40
|
-
|
|
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
|
|
48
|
-
sender
|
|
48
|
+
refund?: OmniWallet;
|
|
49
|
+
sender?: OmniWallet | "qr";
|
|
49
50
|
type?: "exactIn" | "exactOut";
|
|
50
51
|
logger?: ILogger;
|
|
51
|
-
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
|
244
|
-
if (refund
|
|
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
|
|
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
|
|
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");
|
package/src/stellar/wallet.ts
CHANGED
|
@@ -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
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
+
balances.forEach(([address, balance]) => this.setBalance(`${chain}:${address}`, balance));
|
|
77
82
|
return Object.fromEntries(balances);
|
|
78
83
|
}
|
|
79
84
|
}
|
package/src/ui/bridge/Bridge.tsx
CHANGED
|
@@ -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>
|
|
287
|
-
if (recipient == null) return <ActionButton disabled>
|
|
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);
|