@rash2x/bridge-widget 0.2.10 → 0.2.11

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.
@@ -10,27 +10,28 @@ import { create } from "zustand";
10
10
  import { useAccount, useConnect, useDisconnect, useWalletClient, usePublicClient } from "wagmi";
11
11
  import { useWallet } from "@tronweb3/tronwallet-adapter-react-hooks";
12
12
  import { useTonAddress, useTonConnectUI } from "@tonconnect/ui-react";
13
- import { Address, beginCell as beginCell$1, storeMessage, loadMessage, Cell } from "@ton/core";
13
+ import { Address, loadMessage, Cell, beginCell as beginCell$1, storeMessage } from "@ton/core";
14
14
  import { useQuery, useQueryClient } from "@tanstack/react-query";
15
15
  import { cn } from "@/lib/utils";
16
16
  import { Skeleton } from "@/components/ui/skeleton";
17
17
  import { Input } from "@/components/ui/input";
18
- import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog";
18
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog";
19
19
  import { Switch } from "@/components/ui/switch";
20
20
  import { X, Loader2, AlertCircleIcon, CheckCircle2, Clock } from "lucide-react";
21
21
  import { AnimatePresence, motion } from "framer-motion";
22
22
  import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@/components/ui/accordion";
23
23
  import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
24
+ import { TonClient, Address as Address$1, beginCell } from "@ton/ton";
24
25
  import { toast, Toaster } from "sonner";
26
+ import { DialogDescription as DialogDescription$1 } from "@radix-ui/react-dialog";
25
27
  import { BrowserProvider, Contract, parseUnits } from "ethers";
26
28
  import { isAddress, formatUnits } from "viem";
27
- import { TonClient, Address as Address$1, beginCell } from "@ton/ton";
28
29
  import { TronLinkAdapterName } from "@tronweb3/tronwallet-adapters";
29
30
  import { CardHeader, CardTitle, CardAction, Card, CardContent, CardFooter } from "@/components/ui/card";
30
31
  import { Badge } from "@/components/ui/badge";
31
32
  const common$1 = { "connecting": "Connecting…", "initializing": "Initializing...", "loading": "Loading...", "paste": "paste", "close": "Close", "zeroPlaceholder": "0", "nativeToken": "Native Token" };
32
33
  const wallets$1 = { "addTonWallet": "Add TON wallet", "addEvmWallet": "Add EVM wallet", "connectTonWallet": "Connect TON wallet", "connectEvmWallet": "Connect EVM wallet", "initializingMetamask": "Initializing MetaMask SDK...", "initializingTronlink": "Initializing TronLink...", "failedToConnectTon": "Failed to connect to TON wallet", "failedToDisconnect": "Failed to disconnect", "metamaskConnectionError": "MetaMask connection error", "failedToConnectMetamask": "Failed to connect to MetaMask", "failedToDisconnectMetamask": "Failed to disconnect from MetaMask", "selectWallet": "Select Wallet", "tonWallets": "TON", "evmWallets": "EVM", "tronWallets": "TRON", "tonKeeper": "TonKeeper", "metaMask": "WalletConnect", "tronLink": "TronLink", "addTronWallet": "Add Tron wallet", "comingSoon": "Coming Soon", "connected": "CONNECTED", "disconnect": "Disconnect", "chooseWallet": "Choose wallet", "oneWalletPerEnv": "You can only connect one wallet per environment.", "connect": "Connect", "connectTronWallet": "Connect Tron wallet", "connectWallet": "Connect wallet" };
33
- const bridge$1 = { "sourceNetwork": "Source network", "destinationNetwork": "Destination network", "selectToken": "Select token", "selectNetwork": "Select network", "searchToken": "Search token", "searchDestinationChain": "Search destination chain", "myTokens": "My tokens", "allTokens": "All tokens", "willChangeSourceChain": "Will Change Source Chain", "noBalancesFound": "No balances found.", "noResults": "No results", "sendToAnotherAddress": "Send to another address", "youWillReceive": "You will receive", "anotherAddressPlaceholder": "Address", "addressDoesntMatch": "Address doesn't match the {{network}} network", "checkBeforeTransfer": "Check correctness before transfer" };
34
+ const bridge$1 = { "max": "Max", "sourceNetwork": "Source network", "destinationNetwork": "Destination network", "selectToken": "Select token", "selectNetwork": "Select network", "searchToken": "Search token", "searchDestinationChain": "Search destination chain", "myTokens": "My tokens", "allTokens": "All tokens", "willChangeSourceChain": "Will Change Source Chain", "noBalancesFound": "No balances found.", "noResults": "No results", "sendToAnotherAddress": "Send to another address", "youWillReceive": "You will receive", "anotherAddressPlaceholder": "Address", "addressDoesntMatch": "Address doesn't match the {{network}} network", "checkBeforeTransfer": "Check correctness before transfer" };
34
35
  const transaction$1 = { "enterAmount": "Enter amount", "transfer": "Transfer", "getQuote": "Get quote", "failed": "Transaction Failed", "confirm": "Confirm transaction", "signTransaction": "Sign in transaction in wallet", "quoting": "Quoting...", "inProgress": "Processing...", "checkingBalance": "Checking balance...", "insufficientBalance": "Insufficient balance", "amountTooSmall": "Min {{min}}", "amountTooLarge": "Max {{max}}", "success": "Success", "successTitle": "Success", "done": "Done", "hashCopied": "Hash copied to clipboard", "bridged": "Bridged", "transferTitle": "Transfer", "hash": "Hash", "finalFee": "Final Fee", "route": "Route", "estTime": "Est. Time", "slippage": "Slippage", "minimumReceived": "Minimum received", "totalFee": "Total Fee", "noRouteFound": "No route found", "notEnoughGas": "Not enough gas", "noRouteFoundForSettings": "No route found for current settings.", "tryAdjustSettings": "Try disabling Gas on Destination, or adjust amount/networks.", "quoteError": "Quote error" };
35
36
  const app$1 = { "stargateWidgetName": "Stargate Bridge Widget", "liveWidget": "Live Widget", "getStarted": "Get Started" };
36
37
  const settings$1 = { "title": "Settings", "gasOnDestination": "Gas on destination", "slippageTolerance": "Slippage tolerance", "routePriority": "Route Priority", "highSlippageWarning": "High slippage warning", "gasPresets": { "auto": "Auto", "none": "None", "medium": "Medium", "max": "Max" }, "routePresets": { "fastest": "Fastest", "cheapest": "Cheapest", "recommended": "Recommended" } };
@@ -46,7 +47,7 @@ const en = {
46
47
  };
47
48
  const common = { "connecting": "Подключение…", "initializing": "Инициализация...", "loading": "Загрузка...", "paste": "вставить", "close": "Закрыть", "zeroPlaceholder": "0", "nativeToken": "Нативный токен" };
48
49
  const wallets = { "addTonWallet": "Добавить TON кошелёк", "addEvmWallet": "Добавить EVM кошелёк", "connectTonWallet": "Подключить TON кошелёк", "connectEvmWallet": "Подключить EVM кошелёк", "initializingMetamask": "Инициализация MetaMask SDK...", "initializingTronlink": "Инициализация TronLink...", "failedToConnectTon": "Не удалось подключиться к TON кошельку", "failedToDisconnect": "Не удалось отключиться", "metamaskConnectionError": "Ошибка подключения MetaMask", "failedToConnectMetamask": "Не удалось подключиться к MetaMask", "failedToDisconnectMetamask": "Не удалось отключиться от MetaMask", "selectWallet": "Выберите кошелёк", "tonWallets": "TON", "evmWallets": "EVM", "tronWallets": "TRON", "tonKeeper": "TonKeeper", "metaMask": "WalletConnect", "tronLink": "TronLink", "addTronWallet": "Добавить Tron кошелёк", "comingSoon": "Скоро", "connected": "ПОДКЛЮЧЕНО", "disconnect": "Отключить", "chooseWallet": "Выберите кошелёк", "oneWalletPerEnv": "Можно подключить только один кошелёк на окружение.", "connect": "Подключить", "connectTronWallet": "Подключить Tron кошелёк", "connectWallet": "Подключить кошелёк" };
49
- const bridge = { "sourceNetwork": "Исходная сеть", "destinationNetwork": "Целевая сеть", "selectToken": "Выбрать токен", "selectNetwork": "Выбрать сеть", "searchToken": "Поиск токена", "searchDestinationChain": "Поиск целевой сети", "myTokens": "Мои токены", "allTokens": "Все токены", "willChangeSourceChain": "Сменит исходную сеть", "noBalancesFound": "Балансы не найдены.", "noResults": "Нет результатов", "sendToAnotherAddress": "Отправить на другой адрес", "youWillReceive": "Вы получите", "anotherAddressPlaceholder": "Адрес", "addressDoesntMatch": "Адрес не соответствует сети {{network}}", "checkBeforeTransfer": "Проверьте корректность перед переводом" };
50
+ const bridge = { "max": "Макс", "sourceNetwork": "Исходная сеть", "destinationNetwork": "Целевая сеть", "selectToken": "Выбрать токен", "selectNetwork": "Выбрать сеть", "searchToken": "Поиск токена", "searchDestinationChain": "Поиск целевой сети", "myTokens": "Мои токены", "allTokens": "Все токены", "willChangeSourceChain": "Сменит исходную сеть", "noBalancesFound": "Балансы не найдены.", "noResults": "Нет результатов", "sendToAnotherAddress": "Отправить на другой адрес", "youWillReceive": "Вы получите", "anotherAddressPlaceholder": "Адрес", "addressDoesntMatch": "Адрес не соответствует сети {{network}}", "checkBeforeTransfer": "Проверьте корректность перед переводом" };
50
51
  const transaction = { "enterAmount": "Введите сумму", "transfer": "Перевести", "getQuote": "Получить котировку", "quoting": "Расчет котировки...", "failed": "Ошибка транзакции", "confirm": "Подтвердите транзакцию", "signTransaction": "Подпишите транзакцию в кошельке", "inProgress": "Выполнение...", "checkingBalance": "Проверка баланса...", "insufficientBalance": "Недостаточно средств", "amountTooSmall": "Минимум {{min}}", "amountTooLarge": "Максимум {{max}}", "success": "Успех", "successTitle": "Успех", "done": "Готово", "hashCopied": "Хэш скопирован в буфер обмена", "bridged": "Переведено", "transferTitle": "Перевод", "hash": "Хэш", "finalFee": "Итоговая комиссия", "route": "Маршрут", "estTime": "Время", "slippage": "Проскальзывание", "minimumReceived": "Минимум к получению", "totalFee": "Общая комиссия", "noRouteFound": "Маршрут не найден", "notEnoughGas": "Недостаточно газа", "noRouteFoundForSettings": "Маршрут не найден для текущих настроек.", "tryAdjustSettings": "Попробуйте отключить Gas on Destination или измените сумму/сети.", "quoteError": "Ошибка котировки" };
51
52
  const app = { "stargateWidgetName": "Виджет Stargate Bridge", "liveWidget": "Живой виджет", "getStarted": "Начало работы" };
52
53
  const settings = { "title": "Настройки", "gasOnDestination": "Газ на назначении", "slippageTolerance": "Толерантность к проскальзыванию", "routePriority": "Приоритет маршрута", "highSlippageWarning": "Высокое проскальзывание", "gasPresets": { "auto": "Авто", "none": "Нет", "medium": "Средний", "max": "Макс" }, "routePresets": { "fastest": "Быстрейший", "cheapest": "Дешевейший", "recommended": "Рекомендуемый" } };
@@ -1584,18 +1585,30 @@ const SwapButton = () => {
1584
1585
  ) });
1585
1586
  };
1586
1587
  const WalletBalance = (props) => {
1587
- const { value, isLoading = false } = props;
1588
+ const { value, isLoading = false, showMax = false, onMaxClick } = props;
1589
+ const { t } = useBridgeTranslation();
1588
1590
  const hasNoData = !value || value === "0" || value === "0.00" || value === "0.0";
1589
1591
  const shouldShowSkeleton = isLoading && hasNoData;
1592
+ const shouldShowMaxButton = showMax && !hasNoData && !isLoading;
1590
1593
  if (shouldShowSkeleton) {
1591
1594
  return /* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center", children: [
1592
1595
  /* @__PURE__ */ jsx(WalletIcon, { className: "text-muted-foreground" }),
1593
- /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-16 rounded-md" })
1596
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-12 rounded-md" })
1594
1597
  ] });
1595
1598
  }
1596
1599
  return /* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center", children: [
1597
1600
  /* @__PURE__ */ jsx(WalletIcon, { className: "text-muted-foreground" }),
1598
- /* @__PURE__ */ jsx("span", { className: "text-sm leading-5 font-medium text-muted-foreground", children: value })
1601
+ /* @__PURE__ */ jsx("span", { className: "text-sm leading-5 font-medium text-muted-foreground", children: value }),
1602
+ shouldShowMaxButton && /* @__PURE__ */ jsx(
1603
+ Button,
1604
+ {
1605
+ variant: "ghost",
1606
+ size: "sm",
1607
+ onClick: onMaxClick,
1608
+ className: "h-auto p-0 px-0 text-xs font-medium border-b border-foreground rounded-none",
1609
+ children: t("bridge.max")
1610
+ }
1611
+ )
1599
1612
  ] });
1600
1613
  };
1601
1614
  const BASE_URL$1 = "https://icons-ckg.pages.dev/stargate-light/networks";
@@ -1836,7 +1849,7 @@ const ChainSelectModal = ({
1836
1849
  );
1837
1850
  };
1838
1851
  return /* @__PURE__ */ jsx(Dialog, { open: isOpen, onOpenChange: (open) => !open && handleClose(), children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-h-[90dvh] h-[90dvh] overflow-hidden flex flex-col", children: [
1839
- /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: t("bridge.selectNetwork") }) }),
1852
+ /* @__PURE__ */ jsx(DialogHeader, { className: "text-left", children: /* @__PURE__ */ jsx(DialogTitle, { children: t("bridge.selectNetwork") }) }),
1840
1853
  /* @__PURE__ */ jsx(
1841
1854
  SearchInput,
1842
1855
  {
@@ -1961,7 +1974,7 @@ const WalletInlineButton = ({
1961
1974
  disabled: isButtonDisabled,
1962
1975
  variant: "ghost",
1963
1976
  size: "sm",
1964
- className: "flex gap-1 cursor-pointer px-0 pr-1 h-5",
1977
+ className: "flex gap-1 cursor-pointer !px-0 pr-1 h-5",
1965
1978
  children: [
1966
1979
  /* @__PURE__ */ jsx("span", { children: isConnected ? prefixIcons[wallet] : null }),
1967
1980
  /* @__PURE__ */ jsx("span", { className: "leading-3 text-sm border-b border-dotted border-link text-link", children: buttonText })
@@ -2026,6 +2039,11 @@ const SwapSection = ({
2026
2039
  },
2027
2040
  [onSelect, onClose]
2028
2041
  );
2042
+ const handleMaxClick = useCallback(() => {
2043
+ if (balance.balance && onAmountChange) {
2044
+ onAmountChange(balance.balance.toString());
2045
+ }
2046
+ }, [balance.balance, onAmountChange]);
2029
2047
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2030
2048
  /* @__PURE__ */ jsxs(
2031
2049
  "div",
@@ -2042,7 +2060,9 @@ const SwapSection = ({
2042
2060
  WalletBalance,
2043
2061
  {
2044
2062
  value: truncateToDecimals(balance.balance, 2),
2045
- isLoading: balance.isLoading
2063
+ isLoading: balance.isLoading,
2064
+ showMax: isSource,
2065
+ onMaxClick: handleMaxClick
2046
2066
  }
2047
2067
  )
2048
2068
  ] }),
@@ -2292,6 +2312,53 @@ const TokenSymbol = ({
2292
2312
  const src = `${BASE_URL}/${normalizedSymbol}.svg`;
2293
2313
  return /* @__PURE__ */ jsx("img", { src, alt: alt ?? symbol, className });
2294
2314
  };
2315
+ const EVM_CONFIG = {
2316
+ usdtAddress: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
2317
+ gasEstimates: {
2318
+ approve: 65000n,
2319
+ bridge: 300000n
2320
+ },
2321
+ gasBuffer: 1.2,
2322
+ // 20% buffer
2323
+ timeout: 3e5,
2324
+ // 5 minutes (increased for slower networks)
2325
+ requiredConfirmations: 3
2326
+ // Wait for 3 confirmations for reorg protection
2327
+ };
2328
+ const TON_CONFIG = {
2329
+ apiUrl: "https://toncenter.com/api/v2",
2330
+ timeout: 36e4,
2331
+ // 6 minutes
2332
+ validUntil: 600,
2333
+ // 10 minutes
2334
+ pollingInterval: 5e3,
2335
+ // 5 seconds between transaction status checks
2336
+ estimatedNetworkFee: "100000000"
2337
+ // 0.1 TON in nanoton (conservative estimate)
2338
+ };
2339
+ const TRON_CONFIG = {
2340
+ timeout: 12e4,
2341
+ // 2 minutes (for 19 confirmations)
2342
+ feeLimit: 1e8,
2343
+ // 100 TRX in sun
2344
+ requiredConfirmations: 19,
2345
+ // TRON standard: 19 blocks for confirmation
2346
+ pollingInterval: 3e3
2347
+ // 3 seconds between checks
2348
+ };
2349
+ let tonClientInstance = null;
2350
+ function getTonClient(customClient, apiKey) {
2351
+ if (customClient) {
2352
+ return customClient;
2353
+ }
2354
+ if (!tonClientInstance) {
2355
+ tonClientInstance = new TonClient({
2356
+ endpoint: `${TON_CONFIG.apiUrl}/jsonRPC`,
2357
+ apiKey
2358
+ });
2359
+ }
2360
+ return tonClientInstance;
2361
+ }
2295
2362
  function getQuoteAmounts(quote, srcToken, dstToken) {
2296
2363
  if (!quote || !srcToken || !dstToken) {
2297
2364
  return {
@@ -2371,6 +2438,34 @@ function calculateMinReceived(quote, slippageBps, dstToken) {
2371
2438
  const minAmountLD = dstAmountLD * BigInt(1e4 - slippageBps) / BigInt(1e4);
2372
2439
  return fromLD(minAmountLD.toString(), dstToken.decimals);
2373
2440
  }
2441
+ function addTonNetworkFee(quote, chains) {
2442
+ if (!quote || quote.srcChainKey.toLowerCase() !== "ton") {
2443
+ return quote;
2444
+ }
2445
+ const tonChain = chains?.find(
2446
+ (c) => c.chainKey.toLowerCase() === "ton"
2447
+ );
2448
+ if (!tonChain?.nativeCurrency?.address) {
2449
+ console.warn("Could not find TON native currency address");
2450
+ return quote;
2451
+ }
2452
+ const networkFee = {
2453
+ token: tonChain.nativeCurrency.address,
2454
+ chainKey: "ton",
2455
+ amount: TON_CONFIG.estimatedNetworkFee,
2456
+ type: "network"
2457
+ };
2458
+ const hasNetworkFee = quote.fees?.some(
2459
+ (fee) => fee.type === "network" && fee.chainKey === "ton"
2460
+ );
2461
+ if (hasNetworkFee) {
2462
+ return quote;
2463
+ }
2464
+ return {
2465
+ ...quote,
2466
+ fees: [...quote.fees || [], networkFee]
2467
+ };
2468
+ }
2374
2469
  function getQuoteDetails(quote, srcToken, dstToken, tokens, chains, slippageBps) {
2375
2470
  const amounts = getQuoteAmounts(quote, srcToken, dstToken);
2376
2471
  const fees = getQuoteFees(quote, tokens, chains, srcToken, dstToken);
@@ -2409,8 +2504,9 @@ const Details = () => {
2409
2504
  selectedAssetSymbol,
2410
2505
  fromChain?.chainKey
2411
2506
  );
2507
+ const quoteWithFees = addTonNetworkFee(quote, chains);
2412
2508
  const quoteDetails = getQuoteDetails(
2413
- quote,
2509
+ quoteWithFees || quote,
2414
2510
  srcToken,
2415
2511
  dstToken,
2416
2512
  tokens,
@@ -2438,7 +2534,7 @@ const Details = () => {
2438
2534
  })();
2439
2535
  const currentSlippageText = formatPercentage(slippageBps);
2440
2536
  const routeText = quote?.route ? getRouteDisplayName(quote.route) : t(`settings.routePresets.${routePriority}`);
2441
- return /* @__PURE__ */ jsx(Accordion, { type: "single", collapsible: true, className: "w-full", children: /* @__PURE__ */ jsxs(AccordionItem, { value: "item-1", className: "bg-muted rounded-sm", children: [
2537
+ return /* @__PURE__ */ jsx(Accordion, { type: "single", collapsible: true, className: "w-full", children: /* @__PURE__ */ jsxs(AccordionItem, { value: "item-1", className: "bg-muted/50 rounded-sm", children: [
2442
2538
  /* @__PURE__ */ jsx(AccordionTrigger, { className: "w-full gap-1 items-center py-6 px-5 rounded-b-sm data-[state=open]:pb-3", children: /* @__PURE__ */ jsxs("div", { className: "w-full flex items-center justify-between", children: [
2443
2539
  /* @__PURE__ */ jsx("p", { className: "text-sm font-normal text-priority leading-4", children: t("bridge.youWillReceive", { defaultValue: "You will receive" }) }),
2444
2540
  /* @__PURE__ */ jsxs("div", { className: "bg-transparent hover:bg-transparent shadow-none h-4 p-0 px-0 py-0 flex items-center gap-2", children: [
@@ -2755,8 +2851,9 @@ function useBridgeTransaction() {
2755
2851
  }
2756
2852
  const srcChain = chains?.find((c) => c.chainKey === quote.srcChainKey);
2757
2853
  const dstChain = chains?.find((c) => c.chainKey === quote.dstChainKey);
2758
- const amounts = getQuoteAmounts(quote, srcToken, dstToken);
2759
- const fees = getQuoteFees(quote, tokens, chains, srcToken, dstToken);
2854
+ const quoteWithFees = addTonNetworkFee(quote, chains) || quote;
2855
+ const amounts = getQuoteAmounts(quoteWithFees, srcToken, dstToken);
2856
+ const fees = getQuoteFees(quoteWithFees, tokens, chains, srcToken, dstToken);
2760
2857
  const metadata = {
2761
2858
  srcChainName: srcChain?.name || quote.srcChainKey,
2762
2859
  dstChainName: dstChain?.name || quote.dstChainKey,
@@ -3155,6 +3252,7 @@ const WalletModalButton = (props) => {
3155
3252
  const { icon: IconComponent, name, onClose } = props;
3156
3253
  const { chainRegistry } = useChainStrategies();
3157
3254
  const { connect, isPending } = useConnect();
3255
+ const [isConnecting, setIsConnecting] = useState(false);
3158
3256
  if (props.variant === "connected") {
3159
3257
  const { address, onDisconnect } = props;
3160
3258
  return /* @__PURE__ */ jsx("div", { className: "-mx-3", children: /* @__PURE__ */ jsxs("div", { className: buttonBaseClasses, children: [
@@ -3178,6 +3276,7 @@ const WalletModalButton = (props) => {
3178
3276
  }
3179
3277
  const { walletId, connector } = props;
3180
3278
  const handleConnect = async () => {
3279
+ setIsConnecting(true);
3181
3280
  try {
3182
3281
  if (connector) {
3183
3282
  connect({ connector });
@@ -3190,9 +3289,13 @@ const WalletModalButton = (props) => {
3190
3289
  onClose?.();
3191
3290
  } catch (error) {
3192
3291
  console.error("Failed to connect wallet:", error);
3292
+ const errorMessage = error instanceof Error ? error.message : "Failed to connect wallet. Please try again.";
3293
+ toast.error(errorMessage);
3294
+ } finally {
3295
+ setIsConnecting(false);
3193
3296
  }
3194
3297
  };
3195
- const isDisabled = connector ? isPending : false;
3298
+ const isDisabled = connector ? isPending : isConnecting;
3196
3299
  return /* @__PURE__ */ jsxs(
3197
3300
  Button,
3198
3301
  {
@@ -3303,7 +3406,7 @@ const WalletSelectModal = () => {
3303
3406
  }
3304
3407
  ].filter((category) => category.wallets.length > 0);
3305
3408
  return /* @__PURE__ */ jsx(Dialog, { open: isOpen, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs(DialogContent, { children: [
3306
- /* @__PURE__ */ jsxs(DialogHeader, { children: [
3409
+ /* @__PURE__ */ jsxs(DialogHeader, { className: "text-left", children: [
3307
3410
  /* @__PURE__ */ jsx(DialogTitle, { children: t("wallets.chooseWallet") }),
3308
3411
  /* @__PURE__ */ jsx(DialogDescription, { children: t("wallets.oneWalletPerEnv") })
3309
3412
  ] }),
@@ -3346,7 +3449,7 @@ const WalletSelectModal = () => {
3346
3449
  ] }) });
3347
3450
  };
3348
3451
  const ProgressStep = ({
3349
- icon = /* @__PURE__ */ jsx(Loader2, { className: "w-12 h-12 animate-spin" })
3452
+ icon = /* @__PURE__ */ jsx(Loader2, { className: "w-16 h-16 animate-spin" })
3350
3453
  }) => {
3351
3454
  const { t } = useBridgeTranslation();
3352
3455
  return /* @__PURE__ */ jsx(DialogContent, { showCloseButton: false, children: /* @__PURE__ */ jsxs("div", { className: "flex relative flex-col gap-4 py-10 px-8 flex-1 items-center justify-start text-center noise bg-background", children: [
@@ -3356,33 +3459,123 @@ const ProgressStep = ({
3356
3459
  ] }) });
3357
3460
  };
3358
3461
  const FailedStep = ({
3359
- icon = /* @__PURE__ */ jsx(AlertCircleIcon, { className: "w-12 h-12" })
3462
+ icon = /* @__PURE__ */ jsx(AlertCircleIcon, { className: "w-16 h-16" })
3360
3463
  }) => {
3361
3464
  const { current, reset } = useTransactionStore();
3362
3465
  const { t } = useBridgeTranslation();
3363
3466
  return /* @__PURE__ */ jsxs(DialogContent, { showCloseButton: false, children: [
3364
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col relative gap-4 pt-10 px-8 flex-1 items-center justify-start text-center noise", children: [
3467
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col relative gap-4 py-10 px-8 flex-1 items-center justify-start text-center noise", children: [
3365
3468
  icon,
3366
- /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: t("transaction.failed") }) }),
3367
- current?.errorCode && /* @__PURE__ */ jsx("div", { className: "w-full space-y-2 mt-6 relative z-10", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t(
3368
- `errors.${current.errorCode}`,
3369
- current.errorParams || {}
3370
- ) }) })
3469
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
3470
+ /* @__PURE__ */ jsx(DialogTitle, { children: t("transaction.failed") }),
3471
+ current?.errorCode && /* @__PURE__ */ jsx(DialogDescription$1, { children: t(
3472
+ `errors.${current.errorCode}`,
3473
+ current.errorParams || {}
3474
+ ) })
3475
+ ] })
3371
3476
  ] }),
3372
- /* @__PURE__ */ jsx("div", { className: "flex-col gap-3 pb-10 px-8", children: /* @__PURE__ */ jsx(Button, { variant: "outline", className: "w-full", onClick: reset, children: t("common.close") }) })
3477
+ /* @__PURE__ */ jsx(DialogFooter, { children: /* @__PURE__ */ jsx(Button, { variant: "outline", className: "w-full min-w-40", onClick: reset, children: t("common.close") }) })
3373
3478
  ] });
3374
3479
  };
3480
+ const EXPLORER_CONFIGS = {
3481
+ // TON
3482
+ ton: {
3483
+ baseUrl: "https://tonscan.org",
3484
+ txPath: "/tx/"
3485
+ },
3486
+ // TRON
3487
+ tron: {
3488
+ baseUrl: "https://tronscan.org",
3489
+ txPath: "/#/transaction/"
3490
+ },
3491
+ // Ethereum & EVM chains
3492
+ ethereum: {
3493
+ baseUrl: "https://etherscan.io",
3494
+ txPath: "/tx/"
3495
+ },
3496
+ eth: {
3497
+ baseUrl: "https://etherscan.io",
3498
+ txPath: "/tx/"
3499
+ },
3500
+ // BSC (Binance Smart Chain)
3501
+ bsc: {
3502
+ baseUrl: "https://bscscan.com",
3503
+ txPath: "/tx/"
3504
+ },
3505
+ "binance-smart-chain": {
3506
+ baseUrl: "https://bscscan.com",
3507
+ txPath: "/tx/"
3508
+ },
3509
+ // Polygon
3510
+ polygon: {
3511
+ baseUrl: "https://polygonscan.com",
3512
+ txPath: "/tx/"
3513
+ },
3514
+ matic: {
3515
+ baseUrl: "https://polygonscan.com",
3516
+ txPath: "/tx/"
3517
+ },
3518
+ // Avalanche
3519
+ avalanche: {
3520
+ baseUrl: "https://snowtrace.io",
3521
+ txPath: "/tx/"
3522
+ },
3523
+ avax: {
3524
+ baseUrl: "https://snowtrace.io",
3525
+ txPath: "/tx/"
3526
+ },
3527
+ // Arbitrum
3528
+ arbitrum: {
3529
+ baseUrl: "https://arbiscan.io",
3530
+ txPath: "/tx/"
3531
+ },
3532
+ // Optimism
3533
+ optimism: {
3534
+ baseUrl: "https://optimistic.etherscan.io",
3535
+ txPath: "/tx/"
3536
+ },
3537
+ // Base
3538
+ base: {
3539
+ baseUrl: "https://basescan.org",
3540
+ txPath: "/tx/"
3541
+ },
3542
+ // Fantom
3543
+ fantom: {
3544
+ baseUrl: "https://ftmscan.com",
3545
+ txPath: "/tx/"
3546
+ }
3547
+ };
3548
+ function getExplorerTxUrl(chainKey, txHash) {
3549
+ if (!chainKey || !txHash) {
3550
+ return null;
3551
+ }
3552
+ const normalizedChainKey = chainKey.toLowerCase();
3553
+ const config = EXPLORER_CONFIGS[normalizedChainKey];
3554
+ if (!config) {
3555
+ console.warn(
3556
+ `No explorer config found for chain: ${chainKey}. Please add it to EXPLORER_CONFIGS.`
3557
+ );
3558
+ return null;
3559
+ }
3560
+ return `${config.baseUrl}${config.txPath}${txHash}`;
3561
+ }
3562
+ function openTransactionInExplorer(chainKey, txHash) {
3563
+ const url = getExplorerTxUrl(chainKey, txHash);
3564
+ if (url && typeof window !== "undefined") {
3565
+ window.open(url, "_blank", "noopener,noreferrer");
3566
+ }
3567
+ }
3375
3568
  const SuccessStep = ({
3376
- icon = /* @__PURE__ */ jsx(CheckCircle2, { className: "w-12 h-12" })
3569
+ icon = /* @__PURE__ */ jsx(CheckCircle2, { className: "w-16 h-16" })
3377
3570
  }) => {
3378
3571
  const { current, reset } = useTransactionStore();
3379
3572
  const { t } = useBridgeTranslation();
3380
3573
  const metadata = current?.metadata;
3381
3574
  const srcTxHash = current?.srcTxHash;
3382
- const handleCopyHash = () => {
3383
- if (srcTxHash) {
3384
- navigator.clipboard.writeText(srcTxHash);
3385
- toast.success(t("transaction.hashCopied"));
3575
+ const srcChainKey = current?.quote?.srcChainKey;
3576
+ const handleOpenExplorer = () => {
3577
+ if (srcTxHash && srcChainKey) {
3578
+ openTransactionInExplorer(srcChainKey, srcTxHash);
3386
3579
  }
3387
3580
  };
3388
3581
  return /* @__PURE__ */ jsxs(DialogContent, { showCloseButton: false, children: [
@@ -3422,8 +3615,8 @@ const SuccessStep = ({
3422
3615
  /* @__PURE__ */ jsx(
3423
3616
  "button",
3424
3617
  {
3425
- onClick: handleCopyHash,
3426
- className: "font-medium hover:underline cursor-pointer",
3618
+ onClick: handleOpenExplorer,
3619
+ className: "font-medium hover:underline cursor-pointer inline-flex items-center gap-1",
3427
3620
  children: formatHash(srcTxHash)
3428
3621
  }
3429
3622
  )
@@ -3459,7 +3652,7 @@ const useCountdown = (initialSeconds) => {
3459
3652
  };
3460
3653
  };
3461
3654
  const ConfirmStep = ({
3462
- icon = /* @__PURE__ */ jsx(Clock, { className: "w-12 h-12" })
3655
+ icon = /* @__PURE__ */ jsx(Clock, { className: "w-16 h-16" })
3463
3656
  }) => {
3464
3657
  const { t } = useBridgeTranslation();
3465
3658
  const { formatTime } = useCountdown(90);
@@ -3613,49 +3806,6 @@ class ChainStrategyRegistry {
3613
3806
  await strategy.disconnect();
3614
3807
  }
3615
3808
  }
3616
- const EVM_CONFIG = {
3617
- usdtAddress: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
3618
- gasEstimates: {
3619
- approve: 65000n,
3620
- bridge: 300000n
3621
- },
3622
- gasBuffer: 1.2,
3623
- // 20% buffer
3624
- timeout: 3e5,
3625
- // 5 minutes (increased for slower networks)
3626
- requiredConfirmations: 3
3627
- // Wait for 3 confirmations for reorg protection
3628
- };
3629
- const TON_CONFIG = {
3630
- apiUrl: "https://toncenter.com/api/v2",
3631
- timeout: 36e4,
3632
- // 6 minutes
3633
- validUntil: 600
3634
- // 10 minutes
3635
- };
3636
- const TRON_CONFIG = {
3637
- timeout: 12e4,
3638
- // 2 minutes (for 19 confirmations)
3639
- feeLimit: 1e8,
3640
- // 100 TRX in sun
3641
- requiredConfirmations: 19,
3642
- // TRON standard: 19 blocks for confirmation
3643
- pollingInterval: 3e3
3644
- // 3 seconds between checks
3645
- };
3646
- let tonClientInstance = null;
3647
- function getTonClient(customClient, apiKey) {
3648
- if (customClient) {
3649
- return customClient;
3650
- }
3651
- if (!tonClientInstance) {
3652
- tonClientInstance = new TonClient({
3653
- endpoint: `${TON_CONFIG.apiUrl}/jsonRPC`,
3654
- apiKey
3655
- });
3656
- }
3657
- return tonClientInstance;
3658
- }
3659
3809
  function isNativeAddress(addr) {
3660
3810
  if (!addr) return false;
3661
3811
  const a = addr.toLowerCase();
@@ -4399,13 +4549,17 @@ class TonChainStrategy {
4399
4549
  nativeTokenSymbol,
4400
4550
  amount,
4401
4551
  balances,
4552
+ nativeDecimals = 9,
4402
4553
  reserveFallback
4403
4554
  } = params;
4404
4555
  const nativeSym = nativeTokenSymbol.toUpperCase();
4405
4556
  const isNativeSelected = nativeSym === (selectedToken?.symbol ?? "").toUpperCase();
4406
4557
  const nativeBalance = Number(balances[nativeSym]?.balance ?? 0);
4407
- const estimatedGas = null;
4408
- const requiredNative = reserveFallback;
4558
+ const { formatUnits: formatUnits2 } = await import("ethers");
4559
+ const estimatedGas = Number(
4560
+ formatUnits2(TON_CONFIG.estimatedNetworkFee, nativeDecimals)
4561
+ );
4562
+ const requiredNative = estimatedGas > 0 ? estimatedGas : reserveFallback;
4409
4563
  const amountNum = amount ?? 0;
4410
4564
  let hasEnoughGas = true;
4411
4565
  if (isNativeSelected) {
@@ -4474,12 +4628,24 @@ class TonChainStrategy {
4474
4628
  const result = await this.config.tonConnectUI.sendTransaction(
4475
4629
  transaction2
4476
4630
  );
4477
- const hash = result.boc;
4478
- onFirstHash?.(hash);
4479
- return {
4480
- chainKey: "ton",
4481
- hash
4482
- };
4631
+ const bocBase64 = result.boc;
4632
+ try {
4633
+ const inMessage = loadMessage(Cell.fromBase64(bocBase64).beginParse());
4634
+ const messageHash = this.getNormalizedExtMessageHash(inMessage);
4635
+ const hexHash = messageHash.toString("hex");
4636
+ onFirstHash?.(hexHash);
4637
+ return {
4638
+ chainKey: "ton",
4639
+ hash: hexHash
4640
+ };
4641
+ } catch (error) {
4642
+ console.error("Error parsing BOC to hex hash:", error);
4643
+ onFirstHash?.(bocBase64);
4644
+ return {
4645
+ chainKey: "ton",
4646
+ hash: bocBase64
4647
+ };
4648
+ }
4483
4649
  } catch (error) {
4484
4650
  throw toChainStrategyError(error, "ton", "transaction");
4485
4651
  }
@@ -4532,20 +4698,31 @@ class TonChainStrategy {
4532
4698
  };
4533
4699
  return beginCell$1().store(storeMessage(normalizedMessage, { forceRef: true })).endCell().hash();
4534
4700
  }
4535
- async checkTonTransaction(bocBase64, timeoutMs = 36e4) {
4701
+ async checkTonTransaction(hashOrBoc, timeoutMs = 36e4) {
4536
4702
  const deadline = Date.now() + timeoutMs;
4537
4703
  const client = getTonClient(this.config.tonClient, this.config.tonApiKey);
4538
4704
  try {
4539
- const inMessage = loadMessage(Cell.fromBase64(bocBase64).beginParse());
4540
- if (inMessage.info.type !== "external-in") {
4541
- console.debug(
4542
- "Expected external-in message, got:",
4543
- inMessage.info.type
4544
- );
4545
- return false;
4705
+ let targetMessageHash;
4706
+ let accountAddress;
4707
+ try {
4708
+ const inMessage = loadMessage(Cell.fromBase64(hashOrBoc).beginParse());
4709
+ if (inMessage.info.type !== "external-in") {
4710
+ console.debug(
4711
+ "Expected external-in message, got:",
4712
+ inMessage.info.type
4713
+ );
4714
+ return false;
4715
+ }
4716
+ accountAddress = inMessage.info.dest;
4717
+ targetMessageHash = this.getNormalizedExtMessageHash(inMessage);
4718
+ } catch {
4719
+ targetMessageHash = Buffer.from(hashOrBoc, "hex");
4720
+ if (!this.config.tonAddress) {
4721
+ console.debug("No wallet address available for hex hash lookup");
4722
+ return false;
4723
+ }
4724
+ accountAddress = Address.parse(this.config.tonAddress);
4546
4725
  }
4547
- const accountAddress = inMessage.info.dest;
4548
- const targetMessageHash = this.getNormalizedExtMessageHash(inMessage);
4549
4726
  let lt = void 0;
4550
4727
  let hash = void 0;
4551
4728
  while (Date.now() < deadline) {
@@ -4557,7 +4734,7 @@ class TonChainStrategy {
4557
4734
  archival: true
4558
4735
  });
4559
4736
  if (transactions.length === 0) {
4560
- await new Promise((r) => setTimeout(r, 3e3));
4737
+ await new Promise((r) => setTimeout(r, TON_CONFIG.pollingInterval));
4561
4738
  lt = void 0;
4562
4739
  hash = void 0;
4563
4740
  continue;
@@ -4576,9 +4753,10 @@ class TonChainStrategy {
4576
4753
  const lastTx = transactions[transactions.length - 1];
4577
4754
  lt = lastTx.lt.toString();
4578
4755
  hash = lastTx.hash().toString("base64");
4756
+ await new Promise((r) => setTimeout(r, TON_CONFIG.pollingInterval));
4579
4757
  } catch (error) {
4580
4758
  console.debug("Error fetching transactions:", error);
4581
- await new Promise((r) => setTimeout(r, 3e3));
4759
+ await new Promise((r) => setTimeout(r, TON_CONFIG.pollingInterval));
4582
4760
  lt = void 0;
4583
4761
  hash = void 0;
4584
4762
  }
@@ -4605,9 +4783,14 @@ class TronChainStrategy {
4605
4783
  return "TRON Chain Strategy";
4606
4784
  }
4607
4785
  async connect() {
4608
- const tronWeb = this.getTronWeb();
4609
- if (!tronWeb && (typeof window === "undefined" || !window.tronLink)) {
4610
- throw new WalletNotFoundError("tron", "TronLink");
4786
+ if (!this.isTronLinkInstalled()) {
4787
+ if (typeof window !== "undefined") {
4788
+ window.open("https://www.tronlink.org/", "_blank");
4789
+ }
4790
+ throw new WalletNotFoundError(
4791
+ "tron",
4792
+ "TronLink wallet is not installed. Please install TronLink extension and try again."
4793
+ );
4611
4794
  }
4612
4795
  this.config.tronSelect(TronLinkAdapterName);
4613
4796
  await new Promise((resolve) => setTimeout(resolve, 100));
@@ -4886,6 +5069,23 @@ class TronChainStrategy {
4886
5069
  getTronWeb() {
4887
5070
  return typeof window !== "undefined" ? window.tronWeb : void 0;
4888
5071
  }
5072
+ /**
5073
+ * Check if TronLink wallet is actually installed
5074
+ * This excludes Bybit Wallet which also injects tronLink for compatibility
5075
+ */
5076
+ isTronLinkInstalled() {
5077
+ if (typeof window === "undefined") {
5078
+ return false;
5079
+ }
5080
+ const hasBybitWallet = typeof window.bybitWallet !== "undefined" && typeof window.bybitWallet.tronLink !== "undefined";
5081
+ if (hasBybitWallet && !window.tronLink) {
5082
+ return false;
5083
+ }
5084
+ if (!window.tronLink) {
5085
+ return false;
5086
+ }
5087
+ return true;
5088
+ }
4889
5089
  hexToAscii(h) {
4890
5090
  if (!h) return null;
4891
5091
  const clean = h.replace(/^0x/, "");
@@ -5289,7 +5489,7 @@ const routePresets = [
5289
5489
  RoutePriority.CHEAPEST,
5290
5490
  RoutePriority.RECOMMENDED
5291
5491
  ];
5292
- const SettingModal = ({ isOpen, onClose }) => {
5492
+ const SettingsModal = ({ isOpen, onClose }) => {
5293
5493
  const { t } = useBridgeTranslation();
5294
5494
  const { toChain } = useChainsStore();
5295
5495
  const { tokens } = useTokensStore();
@@ -5322,8 +5522,8 @@ const SettingModal = ({ isOpen, onClose }) => {
5322
5522
  );
5323
5523
  const activeBtn = "bg-primary hover:bg-primary/80 text-primary-foreground transition-colors";
5324
5524
  const notActiveBtn = "bg-accent hover:bg-accent/80 text-accent-foreground transition-colors";
5325
- return /* @__PURE__ */ jsx(Dialog, { open: isOpen, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs(DialogContent, { children: [
5326
- /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: t("settings.title") }) }),
5525
+ return /* @__PURE__ */ jsx(Dialog, { open: isOpen, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs(DialogContent, { onOpenAutoFocus: (e) => e.preventDefault(), children: [
5526
+ /* @__PURE__ */ jsx(DialogHeader, { className: "text-left", children: /* @__PURE__ */ jsx(DialogTitle, { children: t("settings.title") }) }),
5327
5527
  /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
5328
5528
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-5", children: [
5329
5529
  /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
@@ -5351,6 +5551,7 @@ const SettingModal = ({ isOpen, onClose }) => {
5351
5551
  Badge,
5352
5552
  {
5353
5553
  onClick: () => setGasPreset(g),
5554
+ size: "lg",
5354
5555
  className: cn(
5355
5556
  "cursor-pointer",
5356
5557
  gasPreset === g ? activeBtn : notActiveBtn
@@ -5377,6 +5578,7 @@ const SettingModal = ({ isOpen, onClose }) => {
5377
5578
  /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: slippagePresets.map((p) => /* @__PURE__ */ jsx(
5378
5579
  Badge,
5379
5580
  {
5581
+ size: "lg",
5380
5582
  onClick: () => {
5381
5583
  const bps = parseFloat(p.replace("%", "")) * 100;
5382
5584
  setSlippageBps(bps);
@@ -5400,6 +5602,7 @@ const SettingModal = ({ isOpen, onClose }) => {
5400
5602
  /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end gap-2", children: routePresets.map((r) => /* @__PURE__ */ jsx(
5401
5603
  Badge,
5402
5604
  {
5605
+ size: "lg",
5403
5606
  onClick: () => setRoutePriority(r),
5404
5607
  className: cn(
5405
5608
  "cursor-pointer",
@@ -5592,7 +5795,7 @@ const TokenSelectModal = ({
5592
5795
  );
5593
5796
  const hasNoResults = tokensToRender.length === 0 && willChangeSrcTokens.length === 0;
5594
5797
  return /* @__PURE__ */ jsx(Dialog, { open: isOpen, onOpenChange: (open) => !open && handleClose(), children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-h-[90dvh] h-[90dvh] overflow-hidden flex flex-col", children: [
5595
- /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: t("bridge.selectToken") }) }),
5798
+ /* @__PURE__ */ jsx(DialogHeader, { className: "text-left", children: /* @__PURE__ */ jsx(DialogTitle, { children: t("bridge.selectToken") }) }),
5596
5799
  /* @__PURE__ */ jsx(
5597
5800
  SearchInput,
5598
5801
  {
@@ -5816,7 +6019,7 @@ const Toolbar = () => {
5816
6019
  }
5817
6020
  }
5818
6021
  ),
5819
- /* @__PURE__ */ jsx(SettingModal, { isOpen: isOpenSettings, onClose: onCloseSettings })
6022
+ /* @__PURE__ */ jsx(SettingsModal, { isOpen: isOpenSettings, onClose: onCloseSettings })
5820
6023
  ] });
5821
6024
  };
5822
6025
  const EvaaBridgeWithProviders = (props) => {
@@ -6079,6 +6282,7 @@ export {
6079
6282
  EvaaBridge,
6080
6283
  RoutePriority,
6081
6284
  RouteType,
6285
+ addTonNetworkFee,
6082
6286
  addrForApi,
6083
6287
  buildAssetMatrix,
6084
6288
  calculateMinReceived,