@rash2x/bridge-widget 0.2.10 → 0.3.0

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,35 +2,36 @@ var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
5
- import { useEffect, useMemo, useState, createContext, useContext, useCallback, forwardRef } from "react";
5
+ import { useEffect, useMemo, createContext, useContext, useState, useCallback, forwardRef } from "react";
6
6
  import { initReactI18next, I18nextProvider, useTranslation } from "react-i18next";
7
7
  import i18n from "i18next";
8
8
  import { Button } from "@/components/ui/button";
9
9
  import { create } from "zustand";
10
- import { useAccount, useConnect, useDisconnect, useWalletClient, usePublicClient } from "wagmi";
10
+ import { useAccount, usePublicClient, useConnect, useDisconnect, useWalletClient } 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
+ import { TonClient, Address as Address$1, beginCell } from "@ton/ton";
14
15
  import { useQuery, useQueryClient } from "@tanstack/react-query";
15
16
  import { cn } from "@/lib/utils";
16
17
  import { Skeleton } from "@/components/ui/skeleton";
17
18
  import { Input } from "@/components/ui/input";
18
- import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog";
19
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog";
19
20
  import { Switch } from "@/components/ui/switch";
20
21
  import { X, Loader2, AlertCircleIcon, CheckCircle2, Clock } from "lucide-react";
21
22
  import { AnimatePresence, motion } from "framer-motion";
22
23
  import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@/components/ui/accordion";
23
24
  import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
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
- 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" };
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", "tonconnect": "TonConnect", "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" };
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 network", "willChangeSourceNetworkAndToken": "Will change source network and token", "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" } };
@@ -45,8 +46,8 @@ const en = {
45
46
  errors: errors$1
46
47
  };
47
48
  const common = { "connecting": "Подключение…", "initializing": "Инициализация...", "loading": "Загрузка...", "paste": "вставить", "close": "Закрыть", "zeroPlaceholder": "0", "nativeToken": "Нативный токен" };
48
- 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": "Проверьте корректность перед переводом" };
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", "tonconnect": "TonConnect", "metaMask": "WalletConnect", "tronLink": "TronLink", "addTronWallet": "Добавить Tron кошелёк", "comingSoon": "Скоро", "connected": "ПОДКЛЮЧЕНО", "disconnect": "Отключить", "chooseWallet": "Выберите кошелёк", "oneWalletPerEnv": "Можно подключить только один кошелёк на окружение.", "connect": "Подключить", "connectTronWallet": "Подключить Tron кошелёк", "connectWallet": "Подключить кошелёк" };
50
+ const bridge = { "max": "Макс", "sourceNetwork": "Исходная сеть", "destinationNetwork": "Целевая сеть", "selectToken": "Выбрать токен", "selectNetwork": "Выбрать сеть", "searchToken": "Поиск токена", "searchDestinationChain": "Поиск целевой сети", "myTokens": "Мои токены", "allTokens": "Все токены", "willChangeSourceChain": "Сменит исходную сеть", "willChangeSourceNetworkAndToken": "Сменит исходную сеть и токен", "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": "Рекомендуемый" } };
@@ -97,72 +98,282 @@ function BridgeI18nProvider({
97
98
  function useBridgeTranslation() {
98
99
  return useTranslation("evaa-bridge", { i18n: bridgeI18n });
99
100
  }
100
- const useChainsStore = create((set, get) => ({
101
- chains: void 0,
102
- fromChain: void 0,
103
- toChain: void 0,
104
- allowedFromChains: void 0,
105
- allowedToChains: void 0,
106
- isLoadingToChains: false,
107
- setChains: async (data) => {
108
- const prev = get().chains;
109
- const same = (() => {
110
- if (!prev && !data) return true;
111
- if (!prev || !data) return false;
112
- if (prev.length !== data.length) return false;
113
- for (let i = 0; i < prev.length; i++) {
114
- if (prev[i]?.chainKey !== data[i]?.chainKey) return false;
101
+ function createJSONStorage(getStorage, options) {
102
+ let storage;
103
+ try {
104
+ storage = getStorage();
105
+ } catch (e) {
106
+ return;
107
+ }
108
+ const persistStorage = {
109
+ getItem: (name) => {
110
+ var _a;
111
+ const parse = (str2) => {
112
+ if (str2 === null) {
113
+ return null;
114
+ }
115
+ return JSON.parse(str2, void 0);
116
+ };
117
+ const str = (_a = storage.getItem(name)) != null ? _a : null;
118
+ if (str instanceof Promise) {
119
+ return str.then(parse);
115
120
  }
116
- return true;
117
- })();
118
- if (same) return;
119
- set({ chains: data });
120
- },
121
- setFromChain: (data) => {
122
- const prev = get().fromChain;
123
- if ((prev?.chainKey ?? null) === (data?.chainKey ?? null)) return;
124
- set({ fromChain: data });
125
- },
126
- setToChain: (data) => {
127
- const prev = get().toChain;
128
- if ((prev?.chainKey ?? null) === (data?.chainKey ?? null)) return;
129
- set({ toChain: data });
130
- },
131
- setAllowedFromChains: (data) => {
132
- const prev = get().allowedFromChains;
133
- const same = (() => {
134
- if (!prev && !data) return true;
135
- if (!prev || !data) return false;
136
- if (prev.length !== data.length) return false;
137
- for (let i = 0; i < prev.length; i++) {
138
- if (prev[i]?.chainKey !== data[i]?.chainKey) return false;
121
+ return parse(str);
122
+ },
123
+ setItem: (name, newValue) => storage.setItem(name, JSON.stringify(newValue, void 0)),
124
+ removeItem: (name) => storage.removeItem(name)
125
+ };
126
+ return persistStorage;
127
+ }
128
+ const toThenable = (fn) => (input) => {
129
+ try {
130
+ const result = fn(input);
131
+ if (result instanceof Promise) {
132
+ return result;
133
+ }
134
+ return {
135
+ then(onFulfilled) {
136
+ return toThenable(onFulfilled)(result);
137
+ },
138
+ catch(_onRejected) {
139
+ return this;
139
140
  }
140
- return true;
141
- })();
142
- if (same) return;
143
- set({ allowedFromChains: data });
144
- },
145
- setAllowedToChains: (data) => {
146
- const prev = get().allowedToChains;
147
- const same = (() => {
148
- if (!prev && !data) return true;
149
- if (!prev || !data) return false;
150
- if (prev.length !== data.length) return false;
151
- for (let i = 0; i < prev.length; i++) {
152
- if (prev[i]?.chainKey !== data[i]?.chainKey) return false;
141
+ };
142
+ } catch (e) {
143
+ return {
144
+ then(_onFulfilled) {
145
+ return this;
146
+ },
147
+ catch(onRejected) {
148
+ return toThenable(onRejected)(e);
153
149
  }
154
- return true;
155
- })();
156
- if (same) return;
157
- set({ allowedToChains: data });
158
- },
159
- setIsLoadingToChains: (v) => set({ isLoadingToChains: v }),
160
- swapChains: () => set((state) => ({
161
- fromChain: state.toChain,
162
- toChain: state.fromChain
163
- }))
164
- }));
165
- const norm = (s) => (s ?? "").toUpperCase().replace(/₮/g, "T").replace(/[^A-Z0-9]/g, "");
150
+ };
151
+ }
152
+ };
153
+ const persistImpl = (config, baseOptions) => (set, get, api) => {
154
+ let options = {
155
+ storage: createJSONStorage(() => localStorage),
156
+ partialize: (state) => state,
157
+ version: 0,
158
+ merge: (persistedState, currentState) => ({
159
+ ...currentState,
160
+ ...persistedState
161
+ }),
162
+ ...baseOptions
163
+ };
164
+ let hasHydrated = false;
165
+ const hydrationListeners = /* @__PURE__ */ new Set();
166
+ const finishHydrationListeners = /* @__PURE__ */ new Set();
167
+ let storage = options.storage;
168
+ if (!storage) {
169
+ return config(
170
+ (...args) => {
171
+ console.warn(
172
+ `[zustand persist middleware] Unable to update item '${options.name}', the given storage is currently unavailable.`
173
+ );
174
+ set(...args);
175
+ },
176
+ get,
177
+ api
178
+ );
179
+ }
180
+ const setItem = () => {
181
+ const state = options.partialize({ ...get() });
182
+ return storage.setItem(options.name, {
183
+ state,
184
+ version: options.version
185
+ });
186
+ };
187
+ const savedSetState = api.setState;
188
+ api.setState = (state, replace) => {
189
+ savedSetState(state, replace);
190
+ return setItem();
191
+ };
192
+ const configResult = config(
193
+ (...args) => {
194
+ set(...args);
195
+ return setItem();
196
+ },
197
+ get,
198
+ api
199
+ );
200
+ api.getInitialState = () => configResult;
201
+ let stateFromStorage;
202
+ const hydrate = () => {
203
+ var _a, _b;
204
+ if (!storage) return;
205
+ hasHydrated = false;
206
+ hydrationListeners.forEach((cb) => {
207
+ var _a2;
208
+ return cb((_a2 = get()) != null ? _a2 : configResult);
209
+ });
210
+ const postRehydrationCallback = ((_b = options.onRehydrateStorage) == null ? void 0 : _b.call(options, (_a = get()) != null ? _a : configResult)) || void 0;
211
+ return toThenable(storage.getItem.bind(storage))(options.name).then((deserializedStorageValue) => {
212
+ if (deserializedStorageValue) {
213
+ if (typeof deserializedStorageValue.version === "number" && deserializedStorageValue.version !== options.version) {
214
+ if (options.migrate) {
215
+ const migration = options.migrate(
216
+ deserializedStorageValue.state,
217
+ deserializedStorageValue.version
218
+ );
219
+ if (migration instanceof Promise) {
220
+ return migration.then((result) => [true, result]);
221
+ }
222
+ return [true, migration];
223
+ }
224
+ console.error(
225
+ `State loaded from storage couldn't be migrated since no migrate function was provided`
226
+ );
227
+ } else {
228
+ return [false, deserializedStorageValue.state];
229
+ }
230
+ }
231
+ return [false, void 0];
232
+ }).then((migrationResult) => {
233
+ var _a2;
234
+ const [migrated, migratedState] = migrationResult;
235
+ stateFromStorage = options.merge(
236
+ migratedState,
237
+ (_a2 = get()) != null ? _a2 : configResult
238
+ );
239
+ set(stateFromStorage, true);
240
+ if (migrated) {
241
+ return setItem();
242
+ }
243
+ }).then(() => {
244
+ postRehydrationCallback == null ? void 0 : postRehydrationCallback(stateFromStorage, void 0);
245
+ stateFromStorage = get();
246
+ hasHydrated = true;
247
+ finishHydrationListeners.forEach((cb) => cb(stateFromStorage));
248
+ }).catch((e) => {
249
+ postRehydrationCallback == null ? void 0 : postRehydrationCallback(void 0, e);
250
+ });
251
+ };
252
+ api.persist = {
253
+ setOptions: (newOptions) => {
254
+ options = {
255
+ ...options,
256
+ ...newOptions
257
+ };
258
+ if (newOptions.storage) {
259
+ storage = newOptions.storage;
260
+ }
261
+ },
262
+ clearStorage: () => {
263
+ storage == null ? void 0 : storage.removeItem(options.name);
264
+ },
265
+ getOptions: () => options,
266
+ rehydrate: () => hydrate(),
267
+ hasHydrated: () => hasHydrated,
268
+ onHydrate: (cb) => {
269
+ hydrationListeners.add(cb);
270
+ return () => {
271
+ hydrationListeners.delete(cb);
272
+ };
273
+ },
274
+ onFinishHydration: (cb) => {
275
+ finishHydrationListeners.add(cb);
276
+ return () => {
277
+ finishHydrationListeners.delete(cb);
278
+ };
279
+ }
280
+ };
281
+ if (!options.skipHydration) {
282
+ hydrate();
283
+ }
284
+ return stateFromStorage || configResult;
285
+ };
286
+ const persist = persistImpl;
287
+ const useChainsStore = create()(
288
+ persist(
289
+ (set, get) => ({
290
+ chains: void 0,
291
+ fromChain: void 0,
292
+ toChain: void 0,
293
+ allowedFromChains: void 0,
294
+ allowedToChains: void 0,
295
+ isLoadingToChains: false,
296
+ setChains: async (data) => {
297
+ const prev = get().chains;
298
+ const same = (() => {
299
+ if (!prev && !data) return true;
300
+ if (!prev || !data) return false;
301
+ if (prev.length !== data.length) return false;
302
+ for (let i = 0; i < prev.length; i++) {
303
+ if (prev[i]?.chainKey !== data[i]?.chainKey) return false;
304
+ }
305
+ return true;
306
+ })();
307
+ if (same) return;
308
+ set({ chains: data });
309
+ },
310
+ setFromChain: (data) => {
311
+ const prev = get().fromChain;
312
+ if ((prev?.chainKey ?? null) === (data?.chainKey ?? null)) return;
313
+ set({ fromChain: data });
314
+ },
315
+ setToChain: (data) => {
316
+ const prev = get().toChain;
317
+ if ((prev?.chainKey ?? null) === (data?.chainKey ?? null)) return;
318
+ set({ toChain: data });
319
+ },
320
+ setAllowedFromChains: (data) => {
321
+ const prev = get().allowedFromChains;
322
+ const same = (() => {
323
+ if (!prev && !data) return true;
324
+ if (!prev || !data) return false;
325
+ if (prev.length !== data.length) return false;
326
+ for (let i = 0; i < prev.length; i++) {
327
+ if (prev[i]?.chainKey !== data[i]?.chainKey) return false;
328
+ }
329
+ return true;
330
+ })();
331
+ if (same) return;
332
+ set({ allowedFromChains: data });
333
+ },
334
+ setAllowedToChains: (data) => {
335
+ const prev = get().allowedToChains;
336
+ const same = (() => {
337
+ if (!prev && !data) return true;
338
+ if (!prev || !data) return false;
339
+ if (prev.length !== data.length) return false;
340
+ for (let i = 0; i < prev.length; i++) {
341
+ if (prev[i]?.chainKey !== data[i]?.chainKey) return false;
342
+ }
343
+ return true;
344
+ })();
345
+ if (same) return;
346
+ set({ allowedToChains: data });
347
+ },
348
+ setIsLoadingToChains: (v) => set({ isLoadingToChains: v }),
349
+ swapChains: () => set((state) => ({
350
+ fromChain: state.toChain,
351
+ toChain: state.fromChain
352
+ }))
353
+ }),
354
+ {
355
+ name: "evaa-bridge-chains",
356
+ storage: createJSONStorage(() => sessionStorage),
357
+ partialize: (state) => ({
358
+ fromChain: state.fromChain,
359
+ toChain: state.toChain
360
+ })
361
+ }
362
+ )
363
+ );
364
+ function normalizeTickerSymbol$1(s) {
365
+ return s.toUpperCase().replace(/₮/g, "T").replace(/[^A-Z0-9]/g, "");
366
+ }
367
+ function normalizeTokenSymbol(token) {
368
+ let normalizedSymbol = token.symbol.toUpperCase().replace(/₮/g, "T");
369
+ normalizedSymbol = normalizedSymbol.replace(/^S\*/, "");
370
+ normalizedSymbol = normalizedSymbol.replace(/0\.S$/i, "").replace(/0$/, "").replace(/\.S$/i, "");
371
+ normalizedSymbol = normalizedSymbol.replace(/\.E$/i, "").replace(/\.N$/i, "");
372
+ return {
373
+ ...token,
374
+ symbol: normalizedSymbol
375
+ };
376
+ }
166
377
  const POPULAR_ORDER = [
167
378
  "USDT",
168
379
  "USDC",
@@ -198,6 +409,16 @@ function buildAssetMatrix(tokens) {
198
409
  if (!symbol) continue;
199
410
  (m[symbol] || (m[symbol] = {}))[t.chainKey] = t;
200
411
  }
412
+ const chainStats = {};
413
+ Object.values(m).forEach((byChain) => {
414
+ Object.keys(byChain).forEach((chainKey) => {
415
+ chainStats[chainKey] = (chainStats[chainKey] || 0) + 1;
416
+ });
417
+ });
418
+ console.log(
419
+ `[DEBUG] Asset matrix built: ${Object.keys(m).length} assets across chains:`,
420
+ chainStats
421
+ );
201
422
  return m;
202
423
  }
203
424
  function listAssetsForSelect(tokens) {
@@ -207,11 +428,11 @@ function listAssetsForSelect(tokens) {
207
428
  const preferred = pickOrder.map((k) => byChain[k]).find(Boolean) ?? Object.values(byChain)[0];
208
429
  return preferred;
209
430
  });
210
- const byNorm = new Map(base.map((e) => [norm(e.symbol), e]));
431
+ const byNorm = new Map(base.map((e) => [normalizeTickerSymbol$1(e.symbol), e]));
211
432
  const used = /* @__PURE__ */ new Set();
212
433
  const popular = [];
213
434
  for (const s of POPULAR_ORDER) {
214
- const key = norm(s);
435
+ const key = normalizeTickerSymbol$1(s);
215
436
  const item = byNorm.get(key);
216
437
  if (item && !used.has(key)) {
217
438
  popular.push(item);
@@ -219,11 +440,11 @@ function listAssetsForSelect(tokens) {
219
440
  }
220
441
  }
221
442
  const stable = base.filter((e) => {
222
- const k = norm(e.symbol);
443
+ const k = normalizeTickerSymbol$1(e.symbol);
223
444
  return !used.has(k) && STABLES.has(k);
224
445
  }).sort((a, b) => a.symbol.localeCompare(b.symbol));
225
- for (const e of stable) used.add(norm(e.symbol));
226
- const other = base.filter((e) => !used.has(norm(e.symbol))).sort((a, b) => a.symbol.localeCompare(b.symbol));
446
+ for (const e of stable) used.add(normalizeTickerSymbol$1(e.symbol));
447
+ const other = base.filter((e) => !used.has(normalizeTickerSymbol$1(e.symbol))).sort((a, b) => a.symbol.localeCompare(b.symbol));
227
448
  return [...popular, ...stable, ...other];
228
449
  }
229
450
  function resolveTokenOnChain(tokens, assetSymbol, chainKey) {
@@ -628,9 +849,398 @@ function isAddressValidForChain(chainKey, addr) {
628
849
  if (chainKey === "tron") return isTronAddress(addr);
629
850
  return isEvmAddress(addr);
630
851
  }
852
+ const ChainStrategyContext = createContext(void 0);
853
+ function useChainStrategies() {
854
+ const context = useContext(ChainStrategyContext);
855
+ if (!context) {
856
+ throw new Error(
857
+ "useChainStrategies must be used within ChainStrategyProvider"
858
+ );
859
+ }
860
+ return context;
861
+ }
862
+ const truncateToDecimals = (num, decimals) => {
863
+ if (!isFinite(num) || isNaN(num)) return "0.00";
864
+ const multiplier = Math.pow(10, decimals);
865
+ const truncated = Math.floor(num * multiplier) / multiplier;
866
+ return truncated.toFixed(decimals);
867
+ };
868
+ const formatTokenAmount = (amount, symbol, options) => {
869
+ const normalizedSymbol = (symbol ?? "").toUpperCase();
870
+ if (["USDT", "USDC", "DAI", "BUSD"].includes(normalizedSymbol) && amount >= 1) {
871
+ return `${Math.floor(amount)} ${normalizedSymbol}`;
872
+ }
873
+ if (options?.decimals !== void 0) {
874
+ return `${amount.toFixed(options.decimals)} ${normalizedSymbol}`;
875
+ }
876
+ if (amount >= 1) {
877
+ return `${amount.toFixed(0)} ${normalizedSymbol}`;
878
+ } else if (amount >= 1e-3) {
879
+ return `${amount.toFixed(3)} ${normalizedSymbol}`;
880
+ } else {
881
+ return `${amount.toFixed(6)} ${normalizedSymbol}`;
882
+ }
883
+ };
884
+ const formatUsd = (value) => {
885
+ if (!value || !isFinite(value)) return "$0";
886
+ if (value >= 1) return `$${value.toFixed(2)}`;
887
+ return `$${value.toFixed(6).replace(/0+$/, "").replace(/\.$/, "")}`;
888
+ };
889
+ const formatPercentage = (bps, decimals = 2) => {
890
+ return `${(bps / 100).toFixed(decimals)}%`;
891
+ };
892
+ const formatBalance = (amount, decimals = 2) => {
893
+ if (!isFinite(amount) || isNaN(amount) || amount <= 0) {
894
+ return "0.00";
895
+ }
896
+ return amount.toFixed(decimals);
897
+ };
898
+ const formatHash = (hash, startChars = 4, endChars = 4) => {
899
+ if (!hash) return "";
900
+ if (hash.length <= startChars + endChars) return hash;
901
+ return `${hash.slice(0, startChars)}...${hash.slice(-endChars)}`;
902
+ };
903
+ const formatAddress = formatHash;
904
+ const EVM_CONFIG = {
905
+ usdtAddress: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
906
+ gasEstimates: {
907
+ approve: 65000n,
908
+ bridge: 300000n
909
+ },
910
+ gasBuffer: 1.2,
911
+ // 20% buffer
912
+ timeout: 3e5,
913
+ // 5 minutes (increased for slower networks)
914
+ requiredConfirmations: 3
915
+ // Wait for 3 confirmations for reorg protection
916
+ };
917
+ const TON_CONFIG = {
918
+ apiUrl: "https://toncenter.com/api/v2",
919
+ timeout: 36e4,
920
+ // 6 minutes
921
+ validUntil: 600,
922
+ // 10 minutes
923
+ pollingInterval: 5e3,
924
+ // 5 seconds between transaction status checks
925
+ estimatedNetworkFee: "100000000"
926
+ // 0.1 TON in nanoton (conservative estimate)
927
+ };
928
+ const TRON_CONFIG = {
929
+ timeout: 12e4,
930
+ // 2 minutes (for 19 confirmations)
931
+ feeLimit: 1e8,
932
+ // 100 TRX in sun
933
+ requiredConfirmations: 19,
934
+ // TRON standard: 19 blocks for confirmation
935
+ pollingInterval: 3e3,
936
+ // 3 seconds between checks
937
+ estimatedNetworkFee: "10000000"
938
+ // 10 TRX in SUN (fallback estimate)
939
+ };
940
+ let tonClientInstance = null;
941
+ function getTonClient(customClient, apiKey) {
942
+ if (customClient) {
943
+ return customClient;
944
+ }
945
+ if (!tonClientInstance) {
946
+ tonClientInstance = new TonClient({
947
+ endpoint: `${TON_CONFIG.apiUrl}/jsonRPC`,
948
+ apiKey
949
+ });
950
+ }
951
+ return tonClientInstance;
952
+ }
953
+ function getQuoteAmounts(quote, srcToken, dstToken) {
954
+ if (!quote || !srcToken || !dstToken) {
955
+ return {
956
+ inputHuman: 0,
957
+ outputHuman: 0,
958
+ outputHumanRounded: "0",
959
+ minReceivedHuman: 0
960
+ };
961
+ }
962
+ const inputHuman = fromLD(quote.srcAmount, srcToken.decimals);
963
+ const outputHuman = fromLD(quote.dstAmount, dstToken.decimals);
964
+ const outputHumanRounded = truncateToDecimals(outputHuman, 2);
965
+ const minReceivedHuman = fromLD(
966
+ quote.dstAmountMin || "0",
967
+ dstToken.decimals
968
+ );
969
+ return {
970
+ inputHuman,
971
+ outputHuman,
972
+ outputHumanRounded,
973
+ minReceivedHuman
974
+ };
975
+ }
976
+ function getQuoteFees(quote, tokens, chains, srcToken, dstToken) {
977
+ if (!quote || !tokens || !chains) {
978
+ return {
979
+ totalUsd: 0,
980
+ protocolFeeUsd: void 0,
981
+ messageFeeUsd: void 0,
982
+ serviceUsd: void 0,
983
+ blockchainUsd: void 0,
984
+ inSrcToken: void 0,
985
+ inDstToken: void 0
986
+ };
987
+ }
988
+ const feeData = computeFeesUsdFromArray(quote.fees, tokens, chains);
989
+ let inSrcToken = void 0;
990
+ let inDstToken = void 0;
991
+ if (srcToken && quote.srcChainKey) {
992
+ const feeInSrcLD = sumFeeByTokenLD(
993
+ quote.fees,
994
+ srcToken.address,
995
+ quote.srcChainKey
996
+ );
997
+ const feeInSrcHuman = fromLD(feeInSrcLD, srcToken.decimals);
998
+ if (feeInSrcHuman > 0) {
999
+ inSrcToken = Number(truncateToDecimals(feeInSrcHuman, 8));
1000
+ } else if (feeData.totalUsd > 0 && srcToken.price?.usd) {
1001
+ const feeInSrcApprox = feeData.totalUsd / srcToken.price.usd;
1002
+ inSrcToken = Number(truncateToDecimals(feeInSrcApprox, 8));
1003
+ }
1004
+ }
1005
+ if (dstToken && quote.dstChainKey) {
1006
+ const feeInDstLD = sumFeeByTokenLD(
1007
+ quote.fees,
1008
+ dstToken.address,
1009
+ quote.dstChainKey
1010
+ );
1011
+ const feeInDstHuman = fromLD(feeInDstLD, dstToken.decimals);
1012
+ if (feeInDstHuman > 0) {
1013
+ inDstToken = Number(truncateToDecimals(feeInDstHuman, 8));
1014
+ }
1015
+ }
1016
+ return {
1017
+ totalUsd: feeData.totalUsd,
1018
+ protocolFeeUsd: feeData.protocolFeeUsd,
1019
+ messageFeeUsd: feeData.messageFeeUsd,
1020
+ serviceUsd: feeData.serviceUsd,
1021
+ blockchainUsd: feeData.blockchainUsd,
1022
+ inSrcToken,
1023
+ inDstToken
1024
+ };
1025
+ }
1026
+ function calculateMinReceived(quote, slippageBps, dstToken) {
1027
+ if (!quote || !dstToken) return 0;
1028
+ const dstAmountLD = BigInt(quote.dstAmount);
1029
+ const minAmountLD = dstAmountLD * BigInt(1e4 - slippageBps) / BigInt(1e4);
1030
+ return fromLD(minAmountLD.toString(), dstToken.decimals);
1031
+ }
1032
+ function estimateTonNetworkFee(quote) {
1033
+ try {
1034
+ const tonStep = quote.steps?.find(
1035
+ (s) => s.chainKey.toLowerCase() === "ton" && s.type === "bridge"
1036
+ );
1037
+ if (!tonStep?.transaction?.messages) {
1038
+ console.warn("No TON messages found in quote, using fallback");
1039
+ return TON_CONFIG.estimatedNetworkFee;
1040
+ }
1041
+ const messages = tonStep.transaction.messages;
1042
+ const messageCount = messages.length;
1043
+ const baseFeePerMessage = 10000000n;
1044
+ let totalFee = 0n;
1045
+ for (const message of messages) {
1046
+ let messageFee = baseFeePerMessage;
1047
+ if (message.payload) {
1048
+ const payloadSize = message.payload.length;
1049
+ const sizeFee = BigInt(Math.floor(payloadSize / 100)) * 100000n;
1050
+ messageFee += sizeFee;
1051
+ }
1052
+ if (message.amount) {
1053
+ const amount = BigInt(message.amount);
1054
+ if (amount > 0n && message.payload) {
1055
+ messageFee += 5000000n;
1056
+ }
1057
+ }
1058
+ totalFee += messageFee;
1059
+ }
1060
+ const minBuffer = 50000000n;
1061
+ totalFee = totalFee > minBuffer ? totalFee : minBuffer;
1062
+ totalFee = totalFee * 120n / 100n;
1063
+ const maxFee = 1000000000n;
1064
+ if (totalFee > maxFee) {
1065
+ totalFee = maxFee;
1066
+ }
1067
+ console.log(
1068
+ `TON fee estimate: ${messageCount} messages = ${Number(totalFee) / 1e9} TON`
1069
+ );
1070
+ return totalFee.toString();
1071
+ } catch (error) {
1072
+ console.warn("Failed to estimate TON fee:", error);
1073
+ return TON_CONFIG.estimatedNetworkFee;
1074
+ }
1075
+ }
1076
+ function addTonNetworkFee(quote, chains, estimatedFee) {
1077
+ if (!quote || quote.srcChainKey.toLowerCase() !== "ton") {
1078
+ return quote;
1079
+ }
1080
+ const tonChain = chains?.find(
1081
+ (c) => c.chainKey.toLowerCase() === "ton"
1082
+ );
1083
+ if (!tonChain?.nativeCurrency?.address) {
1084
+ console.warn("Could not find TON native currency address");
1085
+ return quote;
1086
+ }
1087
+ const networkFee = {
1088
+ token: tonChain.nativeCurrency.address,
1089
+ chainKey: "ton",
1090
+ amount: estimatedFee || TON_CONFIG.estimatedNetworkFee,
1091
+ type: "network"
1092
+ };
1093
+ const hasNetworkFee = quote.fees?.some(
1094
+ (fee) => fee.type === "network" && fee.chainKey === "ton"
1095
+ );
1096
+ if (hasNetworkFee) {
1097
+ return quote;
1098
+ }
1099
+ return {
1100
+ ...quote,
1101
+ fees: [...quote.fees || [], networkFee]
1102
+ };
1103
+ }
1104
+ async function estimateTronNetworkFee(tronWeb, quote, userAddress) {
1105
+ try {
1106
+ const accountResources = await tronWeb.trx.getAccountResources(userAddress);
1107
+ const availableEnergy = accountResources.EnergyLimit || 0;
1108
+ const tronStep = quote.steps?.find(
1109
+ (s) => s.chainKey.toLowerCase() === "tron" && s.type === "bridge"
1110
+ );
1111
+ if (!tronStep?.transaction?.data || !tronStep.transaction.to) {
1112
+ console.warn("No TRON transaction data in quote, using fallback");
1113
+ return TRON_CONFIG.estimatedNetworkFee;
1114
+ }
1115
+ const contractAddress = tronWeb.address.fromHex(
1116
+ tronStep.transaction.to.startsWith("41") ? tronStep.transaction.to : "41" + tronStep.transaction.to.slice(2)
1117
+ );
1118
+ const simulation = await tronWeb.transactionBuilder.triggerSmartContract(
1119
+ contractAddress,
1120
+ "function signature doesn't matter for energy estimation",
1121
+ {
1122
+ feeLimit: TRON_CONFIG.feeLimit,
1123
+ callValue: tronStep.transaction.value ? Number(tronStep.transaction.value) : 0
1124
+ },
1125
+ [],
1126
+ userAddress
1127
+ );
1128
+ const requiredEnergy = simulation.energy_used || 0;
1129
+ const energyDeficit = Math.max(0, requiredEnergy - availableEnergy);
1130
+ if (energyDeficit === 0) {
1131
+ return "0";
1132
+ }
1133
+ const energyPriceInSun = 420;
1134
+ const feeSun = energyDeficit * energyPriceInSun;
1135
+ console.log(
1136
+ `TRON fee estimation: ${requiredEnergy} energy required, ${availableEnergy} available, ${feeSun / 1e6} TRX fee`
1137
+ );
1138
+ return feeSun.toString();
1139
+ } catch (error) {
1140
+ console.warn("Failed to estimate TRON network fee:", error);
1141
+ return TRON_CONFIG.estimatedNetworkFee;
1142
+ }
1143
+ }
1144
+ function addTronNetworkFee(quote, chains, estimatedFee) {
1145
+ if (!quote || quote.srcChainKey.toLowerCase() !== "tron") {
1146
+ return quote;
1147
+ }
1148
+ const tronChain = chains?.find(
1149
+ (c) => c.chainKey.toLowerCase() === "tron"
1150
+ );
1151
+ if (!tronChain?.nativeCurrency?.address) {
1152
+ console.warn("Could not find TRON native currency address");
1153
+ return quote;
1154
+ }
1155
+ const networkFee = {
1156
+ token: tronChain.nativeCurrency.address,
1157
+ chainKey: "tron",
1158
+ amount: estimatedFee || TRON_CONFIG.estimatedNetworkFee,
1159
+ type: "network"
1160
+ };
1161
+ const hasNetworkFee = quote.fees?.some(
1162
+ (fee) => fee.type === "network" && fee.chainKey === "tron"
1163
+ );
1164
+ if (hasNetworkFee) {
1165
+ return quote;
1166
+ }
1167
+ return {
1168
+ ...quote,
1169
+ fees: [...quote.fees || [], networkFee]
1170
+ };
1171
+ }
1172
+ async function estimateEvmNetworkFee(publicClient, quote, chainKey) {
1173
+ try {
1174
+ let totalGasLimit = 0n;
1175
+ for (const step of quote.steps || []) {
1176
+ if (step.chainKey.toLowerCase() !== chainKey.toLowerCase()) continue;
1177
+ if (step.type === "approve") {
1178
+ totalGasLimit += EVM_CONFIG.gasEstimates.approve;
1179
+ } else if (step.type === "bridge") {
1180
+ totalGasLimit += EVM_CONFIG.gasEstimates.bridge;
1181
+ }
1182
+ }
1183
+ if (totalGasLimit === 0n) {
1184
+ return "0";
1185
+ }
1186
+ totalGasLimit = totalGasLimit * BigInt(Math.floor(EVM_CONFIG.gasBuffer * 100)) / 100n;
1187
+ const gasPrice = await publicClient.getGasPrice();
1188
+ const totalCostWei = totalGasLimit * gasPrice;
1189
+ console.log(
1190
+ `EVM gas estimate: ${totalGasLimit} gas × ${gasPrice} Wei/gas = ${totalCostWei} Wei`
1191
+ );
1192
+ return totalCostWei.toString();
1193
+ } catch (error) {
1194
+ console.warn("Failed to estimate EVM gas fee:", error);
1195
+ return "10000000000000000";
1196
+ }
1197
+ }
1198
+ function addEvmNetworkFee(quote, chains, estimatedFee) {
1199
+ if (!quote) return quote;
1200
+ const srcChainKey = quote.srcChainKey.toLowerCase();
1201
+ if (srcChainKey === "ton" || srcChainKey === "tron") {
1202
+ return quote;
1203
+ }
1204
+ const srcChain = chains?.find(
1205
+ (c) => c.chainKey.toLowerCase() === srcChainKey
1206
+ );
1207
+ if (!srcChain?.nativeCurrency?.address) {
1208
+ return quote;
1209
+ }
1210
+ const hasNetworkFee = quote.fees?.some(
1211
+ (fee) => fee.type === "network" && fee.chainKey.toLowerCase() === srcChainKey
1212
+ );
1213
+ if (hasNetworkFee) {
1214
+ return quote;
1215
+ }
1216
+ const networkFee = {
1217
+ token: srcChain.nativeCurrency.address,
1218
+ chainKey: quote.srcChainKey,
1219
+ amount: estimatedFee || "10000000000000000",
1220
+ // 0.01 ETH fallback
1221
+ type: "network"
1222
+ };
1223
+ return {
1224
+ ...quote,
1225
+ fees: [...quote.fees || [], networkFee]
1226
+ };
1227
+ }
1228
+ function getQuoteDetails(quote, srcToken, dstToken, tokens, chains, slippageBps) {
1229
+ const amounts = getQuoteAmounts(quote, srcToken, dstToken);
1230
+ const fees = getQuoteFees(quote, tokens, chains, srcToken, dstToken);
1231
+ const minimumReceived = calculateMinReceived(quote, slippageBps, dstToken);
1232
+ return {
1233
+ inputAmount: amounts.inputHuman,
1234
+ outputAmount: amounts.outputHuman,
1235
+ outputAmountRounded: amounts.outputHumanRounded,
1236
+ minimumReceived,
1237
+ etaSeconds: quote?.duration?.estimated,
1238
+ fees
1239
+ };
1240
+ }
631
1241
  function useBridgeQuote() {
632
1242
  const { assetMatrix, selectedAssetSymbol } = useTokensStore();
633
- const { fromChain, toChain } = useChainsStore();
1243
+ const { fromChain, toChain, chains } = useChainsStore();
634
1244
  const { srcAddress, dstAddress } = useAddresses();
635
1245
  const { slippageBps, routePriority, getDstNativeAmount, getSlippageDecimal } = useSettingsStore();
636
1246
  const {
@@ -642,6 +1252,8 @@ function useBridgeQuote() {
642
1252
  setNoRoute,
643
1253
  resetWithIdle
644
1254
  } = useBridgeQuoteStore();
1255
+ const { chainRegistry } = useChainStrategies();
1256
+ const publicClient = usePublicClient();
645
1257
  const input = inputAmount;
646
1258
  const srcTokenOnFrom = useMemo(
647
1259
  () => resolveTokenOnChainFromMatrix$2(
@@ -730,7 +1342,85 @@ function useBridgeQuote() {
730
1342
  return;
731
1343
  }
732
1344
  if (cancelled) return;
733
- setQuote(quoteRoute);
1345
+ let quoteWithFees = quoteRoute;
1346
+ if (quoteRoute.srcChainKey.toLowerCase() === "ton") {
1347
+ try {
1348
+ const estimatedFee = estimateTonNetworkFee(quoteRoute);
1349
+ quoteWithFees = addTonNetworkFee(quoteRoute, chains, estimatedFee);
1350
+ console.log("TON network fee estimated for quote:", estimatedFee);
1351
+ } catch (error) {
1352
+ console.warn("Failed to estimate TON fee, using fallback:", error);
1353
+ quoteWithFees = addTonNetworkFee(quoteRoute, chains);
1354
+ }
1355
+ }
1356
+ if (quoteRoute.srcChainKey.toLowerCase() === "tron") {
1357
+ const tronStrategy = chainRegistry.getStrategy("tron");
1358
+ if (tronStrategy?.isConnected()) {
1359
+ const tronWeb = tronStrategy.getClient();
1360
+ const tronAddress = tronStrategy.getAccount();
1361
+ if (tronWeb && tronAddress) {
1362
+ try {
1363
+ const estimatedFee = await estimateTronNetworkFee(
1364
+ tronWeb,
1365
+ quoteRoute,
1366
+ tronAddress
1367
+ );
1368
+ quoteWithFees = addTronNetworkFee(
1369
+ quoteWithFees || quoteRoute,
1370
+ chains,
1371
+ estimatedFee
1372
+ );
1373
+ console.log("TRON network fee estimated for quote:", estimatedFee);
1374
+ } catch (error) {
1375
+ console.warn("Failed to estimate TRON fee, using fallback:", error);
1376
+ quoteWithFees = addTronNetworkFee(
1377
+ quoteWithFees || quoteRoute,
1378
+ chains
1379
+ );
1380
+ }
1381
+ } else {
1382
+ quoteWithFees = addTronNetworkFee(
1383
+ quoteWithFees || quoteRoute,
1384
+ chains
1385
+ );
1386
+ }
1387
+ } else {
1388
+ quoteWithFees = addTronNetworkFee(
1389
+ quoteWithFees || quoteRoute,
1390
+ chains
1391
+ );
1392
+ }
1393
+ }
1394
+ const srcChainKey = quoteRoute.srcChainKey.toLowerCase();
1395
+ if (srcChainKey !== "ton" && srcChainKey !== "tron") {
1396
+ if (publicClient) {
1397
+ try {
1398
+ const estimatedFee = await estimateEvmNetworkFee(
1399
+ publicClient,
1400
+ quoteRoute,
1401
+ quoteRoute.srcChainKey
1402
+ );
1403
+ quoteWithFees = addEvmNetworkFee(
1404
+ quoteWithFees || quoteRoute,
1405
+ chains,
1406
+ estimatedFee
1407
+ );
1408
+ console.log("EVM network fee estimated for quote:", estimatedFee);
1409
+ } catch (error) {
1410
+ console.warn("Failed to estimate EVM fee, using fallback:", error);
1411
+ quoteWithFees = addEvmNetworkFee(
1412
+ quoteWithFees || quoteRoute,
1413
+ chains
1414
+ );
1415
+ }
1416
+ } else {
1417
+ quoteWithFees = addEvmNetworkFee(
1418
+ quoteWithFees || quoteRoute,
1419
+ chains
1420
+ );
1421
+ }
1422
+ }
1423
+ setQuote(quoteWithFees || quoteRoute);
734
1424
  } catch {
735
1425
  if (!cancelled) {
736
1426
  resetUi();
@@ -756,7 +1446,10 @@ function useBridgeQuote() {
756
1446
  setQError,
757
1447
  slippageBps,
758
1448
  routePriority,
759
- refetchTrigger
1449
+ refetchTrigger,
1450
+ chainRegistry,
1451
+ chains,
1452
+ publicClient
760
1453
  ]);
761
1454
  return { loading };
762
1455
  }
@@ -778,7 +1471,8 @@ async function getTokens() {
778
1471
  throw new Error(`Failed to load chains: ${res.status}`);
779
1472
  }
780
1473
  const data = await res.json();
781
- return Array.isArray(data) ? data : data.tokens ?? [];
1474
+ const tokens = Array.isArray(data) ? data : data.tokens ?? [];
1475
+ return tokens.map(normalizeTokenSymbol);
782
1476
  }
783
1477
  async function getDestTokens(srcChainKey, srcTokenAddr) {
784
1478
  const url = new URL("https://stargate.finance/api/v1/tokens");
@@ -805,10 +1499,7 @@ async function getDestTokens(srcChainKey, srcTokenAddr) {
805
1499
  if (sa === sb) return (a.name ?? "").localeCompare(b.name ?? "");
806
1500
  return sa.localeCompare(sb);
807
1501
  });
808
- return unique;
809
- }
810
- function normalizeTickerSymbol$1(s) {
811
- return s.toUpperCase().replace(/₮/g, "T").replace(/[^A-Z0-9]/g, "");
1502
+ return unique.map(normalizeTokenSymbol);
812
1503
  }
813
1504
  function resolveTokenOnChainFromMatrix$1(assetMatrix, assetSymbol, chainKey) {
814
1505
  if (!assetMatrix || !assetSymbol || !chainKey) return void 0;
@@ -835,7 +1526,14 @@ const useChainDerivations = () => {
835
1526
  return [];
836
1527
  const byChain = assetMatrix[selectedAssetSymbol.toUpperCase()];
837
1528
  const keys = new Set(Object.keys(byChain ?? {}));
838
- return chains.filter((c) => keys.has(c.chainKey));
1529
+ const result = chains.filter((c) => keys.has(c.chainKey));
1530
+ console.log(
1531
+ `[DEBUG] supportedFrom for ${selectedAssetSymbol}:`,
1532
+ `${result.length} of ${chains.length} chains`,
1533
+ result.map((c) => c.chainKey),
1534
+ `| Has Arbitrum: ${result.some((c) => c.chainKey === "arbitrum")}`
1535
+ );
1536
+ return result;
839
1537
  }, [selectedAssetSymbol, assetMatrix, chains]);
840
1538
  useEffect(() => {
841
1539
  setAllowedFromChains(supportedFrom);
@@ -903,6 +1601,13 @@ const useChainDerivations = () => {
903
1601
  if (fromChain?.chainKey) {
904
1602
  list = list.filter((c) => c.chainKey !== fromChain.chainKey);
905
1603
  }
1604
+ console.log(
1605
+ `[DEBUG] allowedToChains from ${fromChain?.chainKey}:`,
1606
+ `${list.length} destinations`,
1607
+ list.map((c) => c.chainKey),
1608
+ `| Has Arbitrum: ${list.some((c) => c.chainKey === "arbitrum")}`,
1609
+ `| API returned ${dest.length} tokens, filtered to ${filteredDest.length}`
1610
+ );
906
1611
  setAllowedToChains(list);
907
1612
  if (!toChain || !list.some((c) => c.chainKey === toChain.chainKey)) {
908
1613
  setToChain(list[0]);
@@ -937,16 +1642,6 @@ const useChainDerivations = () => {
937
1642
  isLoadingToChains
938
1643
  };
939
1644
  };
940
- const ChainStrategyContext = createContext(void 0);
941
- function useChainStrategies() {
942
- const context = useContext(ChainStrategyContext);
943
- if (!context) {
944
- throw new Error(
945
- "useChainStrategies must be used within ChainStrategyProvider"
946
- );
947
- }
948
- return context;
949
- }
950
1645
  function useBalances(chainKey, address, priorityTokenSymbol) {
951
1646
  const { chainRegistry } = useChainStrategies();
952
1647
  const { assetMatrix } = useTokensStore();
@@ -1310,7 +2005,7 @@ const StargateIcon = (props) => {
1310
2005
  "path",
1311
2006
  {
1312
2007
  d: "M4.37473 7.21715L4.86639 7.00685C5.83193 6.59514 6.59903 5.82804 7.01074 4.86248L7.22104 4.37082C7.51425 3.68368 8.49163 3.68368 8.78489 4.37082L8.99514 4.86248C9.40685 5.82804 10.1739 6.59514 11.1395 7.00685L11.6312 7.21715C12.3183 7.51036 12.3183 8.4848 11.6312 8.78095L11.1395 8.99125C10.1739 9.40296 9.40685 10.1701 8.99514 11.1356L8.78489 11.6273C8.49163 12.3144 7.51425 12.3144 7.22104 11.6273L7.01074 11.1356C6.59903 10.1701 5.83193 9.40296 4.86639 8.99125L4.37473 8.78095C3.68759 8.48774 3.68759 7.5133 4.37473 7.21715Z",
1313
- fill: "#FAFAFA"
2008
+ fill: "currentColor"
1314
2009
  }
1315
2010
  )
1316
2011
  ] }),
@@ -1446,7 +2141,7 @@ const WalletConnectIcon = (props) => {
1446
2141
  }
1447
2142
  );
1448
2143
  };
1449
- const TonKeeperIcon = (props) => {
2144
+ const TonConnectIcon = (props) => {
1450
2145
  return /* @__PURE__ */ jsxs(
1451
2146
  "svg",
1452
2147
  {
@@ -1584,18 +2279,30 @@ const SwapButton = () => {
1584
2279
  ) });
1585
2280
  };
1586
2281
  const WalletBalance = (props) => {
1587
- const { value, isLoading = false } = props;
2282
+ const { value, isLoading = false, showMax = false, onMaxClick } = props;
2283
+ const { t } = useBridgeTranslation();
1588
2284
  const hasNoData = !value || value === "0" || value === "0.00" || value === "0.0";
1589
2285
  const shouldShowSkeleton = isLoading && hasNoData;
2286
+ const shouldShowMaxButton = showMax && !hasNoData && !isLoading;
1590
2287
  if (shouldShowSkeleton) {
1591
2288
  return /* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center", children: [
1592
2289
  /* @__PURE__ */ jsx(WalletIcon, { className: "text-muted-foreground" }),
1593
- /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-16 rounded-md" })
2290
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-12 rounded-md" })
1594
2291
  ] });
1595
2292
  }
1596
2293
  return /* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center", children: [
1597
2294
  /* @__PURE__ */ jsx(WalletIcon, { className: "text-muted-foreground" }),
1598
- /* @__PURE__ */ jsx("span", { className: "text-sm leading-5 font-medium text-muted-foreground", children: value })
2295
+ /* @__PURE__ */ jsx("span", { className: "text-sm leading-5 font-medium text-muted-foreground", children: value }),
2296
+ shouldShowMaxButton && /* @__PURE__ */ jsx(
2297
+ Button,
2298
+ {
2299
+ variant: "ghost",
2300
+ size: "sm",
2301
+ onClick: onMaxClick,
2302
+ className: "h-auto p-0 px-0 text-xs font-medium border-b border-foreground rounded-none",
2303
+ children: t("bridge.max")
2304
+ }
2305
+ )
1599
2306
  ] });
1600
2307
  };
1601
2308
  const BASE_URL$1 = "https://icons-ckg.pages.dev/stargate-light/networks";
@@ -1624,7 +2331,7 @@ const SelectNetworkButton = ({
1624
2331
  size: "sm",
1625
2332
  variant: "secondary",
1626
2333
  type: "button",
1627
- className: "shrink-0 gap-2 bg-card text-card-foreground hover:text-card-foreground h-9",
2334
+ className: "shrink-0 gap-2 h-9 !pl-2",
1628
2335
  "aria-label": label,
1629
2336
  children: [
1630
2337
  /* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center", children: [
@@ -1715,49 +2422,37 @@ const SearchInput = ({
1715
2422
  placeholder,
1716
2423
  value,
1717
2424
  onChange,
1718
- className,
1719
- containerClassName
2425
+ className
1720
2426
  }) => {
1721
- const [isFocused, setIsFocused] = useState(false);
1722
- return /* @__PURE__ */ jsxs(
1723
- "div",
1724
- {
1725
- className: cn(
1726
- "flex items-center gap-3 px-5 py-4 bg-input rounded-md h-13 transition-all duration-200",
1727
- isFocused ? "border border-ring" : "border border-transparent",
1728
- containerClassName
1729
- ),
1730
- children: [
1731
- /* @__PURE__ */ jsx(SearchIcon, { className: "w-6 h-6 text-input-icon" }),
1732
- /* @__PURE__ */ jsx(
1733
- Input,
1734
- {
1735
- placeholder,
1736
- className: cn(
1737
- "w-full outline-none bg-transparent border-none ring-0 leading-0 p-0 h-6 text-base text-input-text placeholder:text-input-placeholder bg-none dark:bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0",
1738
- className
1739
- ),
1740
- value,
1741
- onChange: (e) => onChange(e.target.value),
1742
- onFocus: () => setIsFocused(true),
1743
- onBlur: () => setIsFocused(false)
1744
- }
1745
- )
1746
- ]
1747
- }
1748
- );
2427
+ return /* @__PURE__ */ jsxs("div", { className: cn("rounded-xs relative"), children: [
2428
+ /* @__PURE__ */ jsx(SearchIcon, { className: "w-6 h-6 absolute left-5 top-0 bottom-0 my-auto" }),
2429
+ /* @__PURE__ */ jsx(
2430
+ Input,
2431
+ {
2432
+ placeholder,
2433
+ className: cn(
2434
+ "w-full outline-none px-5 py-4 relative z-10 pl-16 bg-input border-none rounded-xs ring-0 leading-0 h-13 text-base focus-visible:border focus-visible:border-ring",
2435
+ className
2436
+ ),
2437
+ value,
2438
+ onChange: (e) => onChange(e.target.value)
2439
+ }
2440
+ )
2441
+ ] });
1749
2442
  };
1750
2443
  const ChainSelectModal = ({
1751
2444
  isOpen,
1752
2445
  onClose,
1753
2446
  items,
1754
2447
  allowedItems,
1755
- onChangeChain
2448
+ onChangeChain,
2449
+ isSource
1756
2450
  }) => {
1757
2451
  const { t } = useBridgeTranslation();
1758
2452
  const [query, setQuery] = useState("");
1759
2453
  const { setFromChain, chains, fromChain, toChain } = useChainsStore();
1760
- const { assetMatrix, selectedAssetSymbol } = useTokensStore();
2454
+ const { assetMatrix, selectedAssetSymbol, setSelectedAssetSymbol } = useTokensStore();
2455
+ console.log("allowedItems", allowedItems);
1761
2456
  const handleClose = useCallback(() => {
1762
2457
  setQuery("");
1763
2458
  onClose();
@@ -1786,40 +2481,84 @@ const ChainSelectModal = ({
1786
2481
  },
1787
2482
  [chains, selectedAssetSymbol, assetMatrix]
1788
2483
  );
2484
+ const findCompatibleTokenAndSrcChain = useCallback(
2485
+ (dstChain) => {
2486
+ if (!chains || !assetMatrix) return void 0;
2487
+ const PRIORITY_CHAINS = [
2488
+ "ethereum",
2489
+ "arbitrum",
2490
+ "bsc",
2491
+ "polygon",
2492
+ "optimism",
2493
+ "base"
2494
+ ];
2495
+ for (const tokenSymbol of Object.keys(assetMatrix)) {
2496
+ const assetByChain = assetMatrix[tokenSymbol];
2497
+ if (!assetByChain[dstChain.chainKey]) continue;
2498
+ const availableChains = chains.filter(
2499
+ (c) => assetByChain[c.chainKey] && c.chainKey !== dstChain.chainKey
2500
+ );
2501
+ if (availableChains.length === 0) continue;
2502
+ let sourceChain;
2503
+ for (const chainKey of PRIORITY_CHAINS) {
2504
+ sourceChain = availableChains.find((c) => c.chainKey === chainKey);
2505
+ if (sourceChain) break;
2506
+ }
2507
+ if (!sourceChain) sourceChain = availableChains[0];
2508
+ return { tokenSymbol, sourceChain };
2509
+ }
2510
+ return void 0;
2511
+ },
2512
+ [chains, assetMatrix]
2513
+ );
1789
2514
  const groupedChains = useMemo(() => {
1790
2515
  const q = query.trim().toLowerCase();
1791
2516
  const filtered = q ? (items ?? []).filter(
1792
2517
  (c) => c.name.toLowerCase().includes(q) || c.chainKey.toLowerCase().includes(q)
1793
2518
  ) : items ?? [];
1794
- const groups = { available: [], willChangeSrc: [] };
2519
+ const groups = { available: [], willChangeSrc: [], willChangeTokenAndSrc: [] };
1795
2520
  for (const chain of filtered) {
1796
2521
  const isAllowed = allowedItems?.some((c) => c.chainKey === chain.chainKey) || false;
1797
2522
  if (isAllowed) {
1798
2523
  groups.available.push(chain);
1799
2524
  } else {
1800
2525
  const compatibleSrc = findCompatibleSrcChain(chain);
1801
- if (compatibleSrc) groups.willChangeSrc.push(chain);
2526
+ if (compatibleSrc) {
2527
+ groups.willChangeSrc.push(chain);
2528
+ } else {
2529
+ const compatibleTokenAndSrc = findCompatibleTokenAndSrcChain(chain);
2530
+ if (compatibleTokenAndSrc) {
2531
+ groups.willChangeTokenAndSrc.push(chain);
2532
+ }
2533
+ }
1802
2534
  }
1803
2535
  }
1804
2536
  groups.available.sort((a, b) => a.name.localeCompare(b.name));
1805
2537
  groups.willChangeSrc.sort((a, b) => a.name.localeCompare(b.name));
2538
+ groups.willChangeTokenAndSrc.sort((a, b) => a.name.localeCompare(b.name));
1806
2539
  return groups;
1807
- }, [items, query, allowedItems, findCompatibleSrcChain]);
1808
- const onChainPick = (chain, willChangeSrc = false) => {
1809
- if (willChangeSrc) {
2540
+ }, [items, query, allowedItems, findCompatibleSrcChain, findCompatibleTokenAndSrcChain]);
2541
+ const onChainPick = (chain, willChangeSrc = false, willChangeTokenAndSrc = false) => {
2542
+ if (willChangeTokenAndSrc) {
2543
+ const result = findCompatibleTokenAndSrcChain(chain);
2544
+ if (result) {
2545
+ setSelectedAssetSymbol(result.tokenSymbol);
2546
+ if (setFromChain) setFromChain(result.sourceChain);
2547
+ }
2548
+ } else if (willChangeSrc) {
1810
2549
  const newSrcChain = findCompatibleSrcChain(chain);
1811
2550
  if (newSrcChain && setFromChain) setFromChain(newSrcChain);
1812
2551
  }
1813
2552
  onChangeChain(chain);
1814
2553
  handleClose();
1815
2554
  };
1816
- const renderChainItem = (chain, willChangeSrc) => {
1817
- const isSelected = fromChain?.chainKey === chain.chainKey || toChain?.chainKey === chain.chainKey;
2555
+ const renderChainItem = (chain, willChangeSrc, willChangeTokenAndSrc = false) => {
2556
+ const isSelected = isSource ? fromChain?.chainKey === chain.chainKey : toChain?.chainKey === chain.chainKey;
1818
2557
  return /* @__PURE__ */ jsx(
1819
2558
  Button,
1820
2559
  {
1821
- onClick: () => onChainPick(chain, willChangeSrc),
1822
- className: `w-full cursor-pointer flex shadow-none rounded-sm items-center justify-between gap-3 px-5 py-4 h-13 font-semibold capitalize bg-transparent hover:scale-100 hover:bg-accent ${isSelected ? "border border-ring" : ""}`,
2560
+ onClick: () => onChainPick(chain, willChangeSrc, willChangeTokenAndSrc),
2561
+ className: `w-full cursor-pointer flex shadow-none rounded-xs items-center justify-between gap-3 px-5 py-4 h-13 font-semibold capitalize bg-transparent hover:scale-100 hover:bg-accent ${isSelected ? "border border-ring" : ""}`,
1823
2562
  children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1824
2563
  /* @__PURE__ */ jsx(
1825
2564
  NetworkSymbol,
@@ -1835,27 +2574,32 @@ const ChainSelectModal = ({
1835
2574
  chain.chainKey
1836
2575
  );
1837
2576
  };
1838
- 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") }) }),
2577
+ return /* @__PURE__ */ jsx(Dialog, { open: isOpen, onOpenChange: (open) => !open && handleClose(), children: /* @__PURE__ */ jsxs(DialogContent, { className: "md:max-h-[90dvh] md:h-[90dvh] overflow-hidden flex flex-col", children: [
2578
+ /* @__PURE__ */ jsx(DialogHeader, { className: "text-left", children: /* @__PURE__ */ jsx(DialogTitle, { children: t("bridge.selectNetwork") }) }),
1840
2579
  /* @__PURE__ */ jsx(
1841
2580
  SearchInput,
1842
2581
  {
1843
2582
  placeholder: t("bridge.searchDestinationChain"),
1844
2583
  value: query,
1845
2584
  onChange: setQuery,
1846
- containerClassName: "rounded-md",
1847
2585
  className: "text-foreground placeholder:text-muted-foreground"
1848
2586
  }
1849
2587
  ),
1850
2588
  /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto", children: [
1851
2589
  groupedChains.available.length > 0 && groupedChains.available.map((c) => renderChainItem(c, false)),
1852
- groupedChains.available.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
1853
- groupedChains.willChangeSrc.length > 0 && /* @__PURE__ */ jsx("p", { className: "px-5 py-2 leading-4 text-base font-semibold text-muted-foreground uppercase", children: t("bridge.willChangeSourceChain") }),
1854
- groupedChains.willChangeSrc.length > 0 && groupedChains.willChangeSrc.map(
1855
- (c) => renderChainItem(c, true)
2590
+ groupedChains.willChangeSrc.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
2591
+ /* @__PURE__ */ jsx("p", { className: `px-5 py-2 leading-4 text-base font-semibold text-muted-foreground uppercase ${groupedChains.available.length > 0 ? "mt-10" : ""}`, children: t("bridge.willChangeSourceChain") }),
2592
+ groupedChains.willChangeSrc.map(
2593
+ (c) => renderChainItem(c, true, false)
1856
2594
  )
1857
2595
  ] }),
1858
- groupedChains.available.length === 0 && groupedChains.willChangeSrc.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-5 py-4 text-sm text-muted-foreground", children: t("bridge.noResults") })
2596
+ groupedChains.willChangeTokenAndSrc.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
2597
+ /* @__PURE__ */ jsx("p", { className: `px-5 py-2 leading-4 text-base font-semibold text-muted-foreground uppercase ${groupedChains.available.length > 0 || groupedChains.willChangeSrc.length > 0 ? "mt-10" : ""}`, children: t("bridge.willChangeSourceNetworkAndToken") }),
2598
+ groupedChains.willChangeTokenAndSrc.map(
2599
+ (c) => renderChainItem(c, false, true)
2600
+ )
2601
+ ] }),
2602
+ groupedChains.available.length === 0 && groupedChains.willChangeSrc.length === 0 && groupedChains.willChangeTokenAndSrc.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-5 py-4 text-sm text-muted-foreground", children: t("bridge.noResults") })
1859
2603
  ] })
1860
2604
  ] }) });
1861
2605
  };
@@ -1865,52 +2609,10 @@ const useWalletSelectModal = create((set) => ({
1865
2609
  onOpen: (addressType) => set({ isOpen: true, addressType }),
1866
2610
  onClose: () => set({ isOpen: false, addressType: void 0 })
1867
2611
  }));
1868
- const truncateToDecimals = (num, decimals) => {
1869
- if (!isFinite(num) || isNaN(num)) return "0.00";
1870
- const multiplier = Math.pow(10, decimals);
1871
- const truncated = Math.floor(num * multiplier) / multiplier;
1872
- return truncated.toFixed(decimals);
1873
- };
1874
- const formatTokenAmount = (amount, symbol, options) => {
1875
- const normalizedSymbol = (symbol ?? "").toUpperCase();
1876
- if (["USDT", "USDC", "DAI", "BUSD"].includes(normalizedSymbol) && amount >= 1) {
1877
- return `${Math.floor(amount)} ${normalizedSymbol}`;
1878
- }
1879
- if (options?.decimals !== void 0) {
1880
- return `${amount.toFixed(options.decimals)} ${normalizedSymbol}`;
1881
- }
1882
- if (amount >= 1) {
1883
- return `${amount.toFixed(0)} ${normalizedSymbol}`;
1884
- } else if (amount >= 1e-3) {
1885
- return `${amount.toFixed(3)} ${normalizedSymbol}`;
1886
- } else {
1887
- return `${amount.toFixed(6)} ${normalizedSymbol}`;
1888
- }
1889
- };
1890
- const formatUsd = (value) => {
1891
- if (!value || !isFinite(value)) return "$0";
1892
- if (value >= 1) return `$${value.toFixed(2)}`;
1893
- return `$${value.toFixed(6).replace(/0+$/, "").replace(/\.$/, "")}`;
1894
- };
1895
- const formatPercentage = (bps, decimals = 2) => {
1896
- return `${(bps / 100).toFixed(decimals)}%`;
1897
- };
1898
- const formatBalance = (amount, decimals = 2) => {
1899
- if (!isFinite(amount) || isNaN(amount) || amount <= 0) {
1900
- return "0.00";
1901
- }
1902
- return amount.toFixed(decimals);
1903
- };
1904
- const formatHash = (hash, startChars = 4, endChars = 4) => {
1905
- if (!hash) return "";
1906
- if (hash.length <= startChars + endChars) return hash;
1907
- return `${hash.slice(0, startChars)}...${hash.slice(-endChars)}`;
1908
- };
1909
- const formatAddress = formatHash;
1910
2612
  const prefixIcons = {
1911
2613
  tronlink: /* @__PURE__ */ jsx(TronLinkIcon, { className: "w-5 h-5" }),
1912
2614
  metamask: /* @__PURE__ */ jsx(MetaMaskIcon, { className: "w-5 h-5" }),
1913
- ton: /* @__PURE__ */ jsx(TonKeeperIcon, { className: "w-5 h-5" })
2615
+ ton: /* @__PURE__ */ jsx(TonConnectIcon, { className: "w-5 h-5" })
1914
2616
  };
1915
2617
  const mapWalletToType = (wallet) => {
1916
2618
  switch (wallet) {
@@ -1961,7 +2663,7 @@ const WalletInlineButton = ({
1961
2663
  disabled: isButtonDisabled,
1962
2664
  variant: "ghost",
1963
2665
  size: "sm",
1964
- className: "flex gap-1 cursor-pointer px-0 pr-1 h-5",
2666
+ className: "flex gap-1 cursor-pointer hover:opacity-60 hover:bg-transparent !px-0 pr-1 h-5",
1965
2667
  children: [
1966
2668
  /* @__PURE__ */ jsx("span", { children: isConnected ? prefixIcons[wallet] : null }),
1967
2669
  /* @__PURE__ */ jsx("span", { className: "leading-3 text-sm border-b border-dotted border-link text-link", children: buttonText })
@@ -2026,6 +2728,11 @@ const SwapSection = ({
2026
2728
  },
2027
2729
  [onSelect, onClose]
2028
2730
  );
2731
+ const handleMaxClick = useCallback(() => {
2732
+ if (balance.balance && onAmountChange) {
2733
+ onAmountChange(balance.balance.toString());
2734
+ }
2735
+ }, [balance.balance, onAmountChange]);
2029
2736
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2030
2737
  /* @__PURE__ */ jsxs(
2031
2738
  "div",
@@ -2042,7 +2749,9 @@ const SwapSection = ({
2042
2749
  WalletBalance,
2043
2750
  {
2044
2751
  value: truncateToDecimals(balance.balance, 2),
2045
- isLoading: balance.isLoading
2752
+ isLoading: balance.isLoading,
2753
+ showMax: isSource,
2754
+ onMaxClick: handleMaxClick
2046
2755
  }
2047
2756
  )
2048
2757
  ] }),
@@ -2090,7 +2799,8 @@ const SwapSection = ({
2090
2799
  onClose,
2091
2800
  items: chains,
2092
2801
  allowedItems: allowedChains,
2093
- onChangeChain
2802
+ onChangeChain,
2803
+ isSource
2094
2804
  }
2095
2805
  )
2096
2806
  ] });
@@ -2144,7 +2854,6 @@ const AnotherAddress = () => {
2144
2854
  /* @__PURE__ */ jsx(
2145
2855
  Switch,
2146
2856
  {
2147
- className: "data-[state=unchecked]:bg-switch-inactive data-[state=checked]:bg-switch-active",
2148
2857
  "aria-pressed": enabled,
2149
2858
  checked: enabled,
2150
2859
  onClick: () => setEnabled((v) => !v)
@@ -2163,7 +2872,7 @@ const AnotherAddress = () => {
2163
2872
  "div",
2164
2873
  {
2165
2874
  className: cn(
2166
- "bg-input py-2 px-4 mt-2 w-full flex items-center gap-4 rounded-md justify-between border border-transparent transition-all",
2875
+ "bg-input px-4 py-2 pr-2 mt-4 w-full flex items-center gap-3 rounded-xs justify-between border border-transparent transition-all",
2167
2876
  {
2168
2877
  "py-4": value,
2169
2878
  "border border-ring": isFocused,
@@ -2173,13 +2882,13 @@ const AnotherAddress = () => {
2173
2882
  children: [
2174
2883
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 w-full", children: [
2175
2884
  /* @__PURE__ */ jsx(
2176
- Input,
2885
+ "textarea",
2177
2886
  {
2178
2887
  className: cn(
2179
- "p-0 h-auto text-base leading-5 font-semibold w-full bg-transparent dark:bg-transparent placeholder:text-muted-foreground/50 border-none focus:outline-none focus:ring-0"
2888
+ "h-auto text-base leading-5 font-semibold w-full bg-transparent dark:bg-transparent placeholder:text-muted-foreground/50 border-none focus:outline-none focus:ring-0 resize-none transition-all"
2180
2889
  ),
2181
2890
  placeholder: t("bridge.anotherAddressPlaceholder"),
2182
- type: "text",
2891
+ rows: value.length >= 32 ? 2 : 1,
2183
2892
  value,
2184
2893
  onFocus: () => setIsFocused(true),
2185
2894
  onBlur: () => setIsFocused(false),
@@ -2196,7 +2905,7 @@ const AnotherAddress = () => {
2196
2905
  Button,
2197
2906
  {
2198
2907
  variant: "secondary",
2199
- className: "bg-card text-card-foreground",
2908
+ className: "bg-accent text-card-foreground uppercase text-xs",
2200
2909
  size: "sm",
2201
2910
  onClick: onPaste,
2202
2911
  children: t("common.paste")
@@ -2219,8 +2928,8 @@ const AnotherAddress = () => {
2219
2928
  ) })
2220
2929
  ] });
2221
2930
  };
2222
- const TipIcon = (props) => {
2223
- return /* @__PURE__ */ jsxs(
2931
+ const InfoIcon = (props) => {
2932
+ return /* @__PURE__ */ jsx(
2224
2933
  "svg",
2225
2934
  {
2226
2935
  width: "16",
@@ -2229,56 +2938,22 @@ const TipIcon = (props) => {
2229
2938
  fill: "none",
2230
2939
  xmlns: "http://www.w3.org/2000/svg",
2231
2940
  ...props,
2232
- children: [
2233
- /* @__PURE__ */ jsx("foreignObject", { x: "-4.4893", y: "-4.4893", width: "24.9786", height: "24.9786", children: /* @__PURE__ */ jsx(
2234
- "div",
2235
- {
2236
- style: {
2237
- backdropFilter: "blur(2.24px)",
2238
- WebkitBackdropFilter: "blur(2.24px)",
2239
- clipPath: "url(#bgblur_0_13066_11660_clip_path)",
2240
- height: "100%",
2241
- width: "100%"
2242
- }
2243
- }
2244
- ) }),
2245
- /* @__PURE__ */ jsx(
2246
- "circle",
2247
- {
2248
- "data-figma-bg-blur-radius": "4.4893",
2249
- cx: "8",
2250
- cy: "8",
2251
- r: "8",
2252
- fill: "currentColor",
2253
- fillOpacity: "0.2"
2254
- }
2255
- ),
2256
- /* @__PURE__ */ jsx(
2257
- "path",
2258
- {
2259
- d: "M6.57031 5.85386C6.57031 5.063 7.21143 4.42188 8.00229 4.42188C8.79316 4.42188 9.43428 5.063 9.43428 5.85386C9.43428 6.13893 9.35098 6.40455 9.20739 6.62769C8.77944 7.29276 8.00229 7.92696 8.00229 8.71782V9.07582M8.00229 11.3178V11.5818",
2260
- stroke: "currentColor",
2261
- strokeOpacity: "0.5",
2262
- strokeWidth: "1.68349",
2263
- strokeLinecap: "round"
2264
- }
2265
- ),
2266
- /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx(
2267
- "clipPath",
2268
- {
2269
- id: "bgblur_0_13066_11660_clip_path",
2270
- transform: "translate(4.4893 4.4893)",
2271
- children: /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "8" })
2272
- }
2273
- ) })
2274
- ]
2941
+ children: /* @__PURE__ */ jsx(
2942
+ "path",
2943
+ {
2944
+ d: "M6.56836 5.8519C6.56836 5.06104 7.20948 4.41992 8.00034 4.41992C8.7912 4.41992 9.43232 5.06104 9.43232 5.8519C9.43232 6.13698 9.34902 6.40259 9.20543 6.62574C8.77748 7.29081 8.00034 7.92501 8.00034 8.71587V9.07386M8.00034 11.3159V11.5798",
2945
+ stroke: "currentColor",
2946
+ "stroke-width": "1.68349",
2947
+ "stroke-linecap": "round"
2948
+ }
2949
+ )
2275
2950
  }
2276
2951
  );
2277
2952
  };
2278
2953
  const Tip = (props) => {
2279
2954
  const { children, text } = props;
2280
2955
  return /* @__PURE__ */ jsxs(Tooltip, { children: [
2281
- /* @__PURE__ */ jsx(TooltipTrigger, { children }),
2956
+ /* @__PURE__ */ jsx(TooltipTrigger, { className: "w-4 h-4 rounded-full bg-muted text-muted-foreground", children }),
2282
2957
  /* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { children: text }) })
2283
2958
  ] });
2284
2959
  };
@@ -2292,97 +2967,84 @@ const TokenSymbol = ({
2292
2967
  const src = `${BASE_URL}/${normalizedSymbol}.svg`;
2293
2968
  return /* @__PURE__ */ jsx("img", { src, alt: alt ?? symbol, className });
2294
2969
  };
2295
- function getQuoteAmounts(quote, srcToken, dstToken) {
2296
- if (!quote || !srcToken || !dstToken) {
2297
- return {
2298
- inputHuman: 0,
2299
- outputHuman: 0,
2300
- outputHumanRounded: "0",
2301
- minReceivedHuman: 0
2302
- };
2303
- }
2304
- const inputHuman = fromLD(quote.srcAmount, srcToken.decimals);
2305
- const outputHuman = fromLD(quote.dstAmount, dstToken.decimals);
2306
- const outputHumanRounded = truncateToDecimals(outputHuman, 2);
2307
- const minReceivedHuman = fromLD(
2308
- quote.dstAmountMin || "0",
2309
- dstToken.decimals
2310
- );
2311
- return {
2312
- inputHuman,
2313
- outputHuman,
2314
- outputHumanRounded,
2315
- minReceivedHuman
2316
- };
2317
- }
2318
- function getQuoteFees(quote, tokens, chains, srcToken, dstToken) {
2319
- if (!quote || !tokens || !chains) {
2320
- return {
2321
- totalUsd: 0,
2322
- protocolFeeUsd: void 0,
2323
- messageFeeUsd: void 0,
2324
- serviceUsd: void 0,
2325
- blockchainUsd: void 0,
2326
- inSrcToken: void 0,
2327
- inDstToken: void 0
2328
- };
2329
- }
2330
- const feeData = computeFeesUsdFromArray(quote.fees, tokens, chains);
2331
- let inSrcToken = void 0;
2332
- let inDstToken = void 0;
2333
- if (srcToken && quote.srcChainKey) {
2334
- const feeInSrcLD = sumFeeByTokenLD(
2335
- quote.fees,
2336
- srcToken.address,
2337
- quote.srcChainKey
2338
- );
2339
- const feeInSrcHuman = fromLD(feeInSrcLD, srcToken.decimals);
2340
- if (feeInSrcHuman > 0) {
2341
- inSrcToken = Number(truncateToDecimals(feeInSrcHuman, 8));
2342
- } else if (feeData.totalUsd > 0 && srcToken.price?.usd) {
2343
- const feeInSrcApprox = feeData.totalUsd / srcToken.price.usd;
2344
- inSrcToken = Number(truncateToDecimals(feeInSrcApprox, 8));
2345
- }
2346
- }
2347
- if (dstToken && quote.dstChainKey) {
2348
- const feeInDstLD = sumFeeByTokenLD(
2349
- quote.fees,
2350
- dstToken.address,
2351
- quote.dstChainKey
2352
- );
2353
- const feeInDstHuman = fromLD(feeInDstLD, dstToken.decimals);
2354
- if (feeInDstHuman > 0) {
2355
- inDstToken = Number(truncateToDecimals(feeInDstHuman, 8));
2356
- }
2357
- }
2358
- return {
2359
- totalUsd: feeData.totalUsd,
2360
- protocolFeeUsd: feeData.protocolFeeUsd,
2361
- messageFeeUsd: feeData.messageFeeUsd,
2362
- serviceUsd: feeData.serviceUsd,
2363
- blockchainUsd: feeData.blockchainUsd,
2364
- inSrcToken,
2365
- inDstToken
2366
- };
2367
- }
2368
- function calculateMinReceived(quote, slippageBps, dstToken) {
2369
- if (!quote || !dstToken) return 0;
2370
- const dstAmountLD = BigInt(quote.dstAmount);
2371
- const minAmountLD = dstAmountLD * BigInt(1e4 - slippageBps) / BigInt(1e4);
2372
- return fromLD(minAmountLD.toString(), dstToken.decimals);
2970
+ function getSimpleFallback(chainKey) {
2971
+ const key = chainKey.toLowerCase();
2972
+ if (key === "ton") return 0.15;
2973
+ if (key === "tron") return 10;
2974
+ return 0.01;
2373
2975
  }
2374
- function getQuoteDetails(quote, srcToken, dstToken, tokens, chains, slippageBps) {
2375
- const amounts = getQuoteAmounts(quote, srcToken, dstToken);
2376
- const fees = getQuoteFees(quote, tokens, chains, srcToken, dstToken);
2377
- const minimumReceived = calculateMinReceived(quote, slippageBps, dstToken);
2378
- return {
2379
- inputAmount: amounts.inputHuman,
2380
- outputAmount: amounts.outputHuman,
2381
- outputAmountRounded: amounts.outputHumanRounded,
2382
- minimumReceived,
2383
- etaSeconds: quote?.duration?.estimated,
2384
- fees
2385
- };
2976
+ function useGasEstimate(amountNum) {
2977
+ const { fromChain } = useChainsStore();
2978
+ const { selectedAssetSymbol } = useTokensStore();
2979
+ const { srcAddress } = useAddresses();
2980
+ const { balances, isLoading: balancesLoading } = useBalances(
2981
+ fromChain?.chainKey,
2982
+ srcAddress
2983
+ );
2984
+ const { quote } = useBridgeQuoteStore();
2985
+ const balancesKnown = !balancesLoading;
2986
+ const chainKey = fromChain?.chainKey;
2987
+ const nativeCurrencySymbol = fromChain?.nativeCurrency?.symbol;
2988
+ const nativeCurrencyAddress = fromChain?.nativeCurrency?.address;
2989
+ const nativeCurrencyDecimals = fromChain?.nativeCurrency?.decimals;
2990
+ const quoteFees = quote?.fees ? JSON.stringify(quote.fees) : null;
2991
+ const quoteSrcChainKey = quote?.srcChainKey;
2992
+ const nativeBalanceValue = nativeCurrencySymbol ? Number(balances[nativeCurrencySymbol.toUpperCase()]?.balance ?? 0) : 0;
2993
+ const result = useMemo(() => {
2994
+ if (!chainKey || !nativeCurrencySymbol) {
2995
+ return {
2996
+ nativeSym: "",
2997
+ nativeBalance: 0,
2998
+ requiredNative: 0,
2999
+ balancesKnown,
3000
+ isNativeSelected: false,
3001
+ hasEnoughGas: true
3002
+ };
3003
+ }
3004
+ const nativeSym = nativeCurrencySymbol.toUpperCase();
3005
+ const nativeBalance = nativeBalanceValue;
3006
+ const isNativeSelected = nativeSym === (selectedAssetSymbol || "").toUpperCase();
3007
+ let requiredNative = 0;
3008
+ if (quoteFees && quoteSrcChainKey === chainKey) {
3009
+ const fees = JSON.parse(quoteFees);
3010
+ const feesInNative = fees.filter(
3011
+ (f) => f.chainKey === chainKey && f.token === nativeCurrencyAddress
3012
+ ).reduce(
3013
+ (sum, f) => sum + BigInt(f.amount || "0"),
3014
+ 0n
3015
+ );
3016
+ const decimals = nativeCurrencyDecimals || 18;
3017
+ requiredNative = Number(feesInNative) / Math.pow(10, decimals);
3018
+ } else {
3019
+ requiredNative = getSimpleFallback(chainKey);
3020
+ }
3021
+ let hasEnoughGas = true;
3022
+ if (isNativeSelected) {
3023
+ hasEnoughGas = nativeBalance - (amountNum ?? 0) >= requiredNative;
3024
+ } else {
3025
+ hasEnoughGas = nativeBalance >= requiredNative;
3026
+ }
3027
+ return {
3028
+ nativeSym,
3029
+ nativeBalance,
3030
+ requiredNative,
3031
+ balancesKnown,
3032
+ isNativeSelected,
3033
+ hasEnoughGas: balancesKnown ? hasEnoughGas : true
3034
+ };
3035
+ }, [
3036
+ chainKey,
3037
+ nativeCurrencySymbol,
3038
+ nativeCurrencyAddress,
3039
+ nativeCurrencyDecimals,
3040
+ selectedAssetSymbol,
3041
+ quoteFees,
3042
+ quoteSrcChainKey,
3043
+ amountNum,
3044
+ balancesKnown,
3045
+ nativeBalanceValue
3046
+ ]);
3047
+ return result;
2386
3048
  }
2387
3049
  function getRouteDisplayName(route) {
2388
3050
  if (!route) return "Stargate Bridge";
@@ -2399,6 +3061,7 @@ const Details = () => {
2399
3061
  const { toChain, fromChain, chains } = useChainsStore();
2400
3062
  const { quote, status } = useBridgeQuoteStore();
2401
3063
  const { slippageBps, routePriority } = useSettingsStore();
3064
+ const gas = useGasEstimate();
2402
3065
  const dstToken = resolveTokenOnChainFromMatrix$2(
2403
3066
  assetMatrix,
2404
3067
  selectedAssetSymbol,
@@ -2421,24 +3084,10 @@ const Details = () => {
2421
3084
  const isLoading = status === "loading";
2422
3085
  const receiveText = quoteDetails.outputAmount != null ? Number(quoteDetails.outputAmount).toFixed(6) : "0.00";
2423
3086
  const etaText = quoteDetails.etaSeconds != null ? `≈ ${Math.max(1, Math.round(quoteDetails.etaSeconds / 60))}m` : "—";
2424
- const feeInSrc = quoteDetails.fees?.inSrcToken;
2425
- const feeInDst = quoteDetails.fees?.inDstToken;
2426
- const totalFeeUsd = quoteDetails.fees?.totalUsd;
2427
- const totalFeeDisplay = (() => {
2428
- if (feeInSrc != null && srcToken?.symbol) {
2429
- return `${Number(feeInSrc).toFixed(6)} ${srcToken.symbol.toUpperCase()}`;
2430
- }
2431
- if (feeInDst != null && dstToken?.symbol) {
2432
- return `${Number(feeInDst).toFixed(6)} ${dstToken.symbol.toUpperCase()}`;
2433
- }
2434
- if (totalFeeUsd != null) {
2435
- return formatUsd(totalFeeUsd);
2436
- }
2437
- return "—";
2438
- })();
3087
+ const totalFeeDisplay = gas.requiredNative > 0 ? `${gas.requiredNative.toFixed(6)} ${gas.nativeSym}` : "—";
2439
3088
  const currentSlippageText = formatPercentage(slippageBps);
2440
3089
  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: [
3090
+ 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
3091
  /* @__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
3092
  /* @__PURE__ */ jsx("p", { className: "text-sm font-normal text-priority leading-4", children: t("bridge.youWillReceive", { defaultValue: "You will receive" }) }),
2444
3093
  /* @__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: [
@@ -2455,7 +3104,7 @@ const Details = () => {
2455
3104
  DetailsRow,
2456
3105
  {
2457
3106
  label: t("transaction.route"),
2458
- value: /* @__PURE__ */ jsxs("strong", { className: "flex items-center font-semibold gap-2", children: [
3107
+ value: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
2459
3108
  /* @__PURE__ */ jsx(StargateIcon, { className: "w-4 h-4" }),
2460
3109
  /* @__PURE__ */ jsx("p", { className: "", children: routeText })
2461
3110
  ] })
@@ -2482,7 +3131,14 @@ const Details = () => {
2482
3131
  label: t("transaction.totalFee"),
2483
3132
  value: /* @__PURE__ */ jsxs("strong", { className: "inline-flex items-center gap-1", children: [
2484
3133
  /* @__PURE__ */ jsx("span", { children: totalFeeDisplay }),
2485
- /* @__PURE__ */ jsx(TokenSymbol, { symbol, className: "w-4 h-4", alt: "token" })
3134
+ /* @__PURE__ */ jsx(
3135
+ TokenSymbol,
3136
+ {
3137
+ symbol: gas.nativeSym,
3138
+ className: "w-4 h-4",
3139
+ alt: "token"
3140
+ }
3141
+ )
2486
3142
  ] }),
2487
3143
  isLoading
2488
3144
  }
@@ -2497,7 +3153,7 @@ const DetailsRow = ({
2497
3153
  }) => /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
2498
3154
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2499
3155
  /* @__PURE__ */ jsx("p", { className: "text-sm text-priority font-normal", children: label }),
2500
- /* @__PURE__ */ jsx(Tip, { text: label, children: /* @__PURE__ */ jsx(TipIcon, { className: "w-4 h-4 text-receive-icon" }) })
3156
+ /* @__PURE__ */ jsx(Tip, { text: label, children: /* @__PURE__ */ jsx(InfoIcon, {}) })
2501
3157
  ] }),
2502
3158
  isLoading ? /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-16 rounded-md" }) : /* @__PURE__ */ jsx("div", { className: "text-foreground text-sm", children: value ?? "—" })
2503
3159
  ] });
@@ -2550,6 +3206,20 @@ const useTransactionStore = create((set, get) => ({
2550
3206
  };
2551
3207
  set({ current: next });
2552
3208
  },
3209
+ updateActualFee: (feeValue, feeSymbol) => {
3210
+ const cur = get().current;
3211
+ if (!cur) return;
3212
+ const next = {
3213
+ ...cur,
3214
+ metadata: {
3215
+ ...cur.metadata,
3216
+ actualFeeValue: feeValue,
3217
+ actualFeeSymbol: feeSymbol
3218
+ },
3219
+ updatedAt: Date.now()
3220
+ };
3221
+ set({ current: next });
3222
+ },
2553
3223
  reset: () => {
2554
3224
  set({ current: void 0 });
2555
3225
  }
@@ -2695,9 +3365,13 @@ function isUserRejection(error) {
2695
3365
  if (error instanceof TransactionRejectedError) {
2696
3366
  return true;
2697
3367
  }
3368
+ if (typeof error === "string") {
3369
+ const message = error.toLowerCase();
3370
+ return message.includes("rejected") || message.includes("denied") || message.includes("declined") || message.includes("not sent") || message.includes("user denied") || message.includes("user rejected") || message.includes("user reject") || message.includes("user cancelled") || message.includes("user canceled") || message.includes("action_rejected") || message.includes("ethers-user-denied") || message.includes("denied transaction") || message.includes("reject request") || message.includes("declined by user");
3371
+ }
2698
3372
  if (error instanceof Error) {
2699
3373
  const message = error.message.toLowerCase();
2700
- return message.includes("rejected") || message.includes("denied") || message.includes("user denied") || message.includes("user rejected") || message.includes("user reject") || message.includes("user cancelled") || message.includes("user canceled") || message.includes("action_rejected") || message.includes("ethers-user-denied") || message.includes("denied transaction") || message.includes("reject request");
3374
+ return message.includes("rejected") || message.includes("denied") || message.includes("declined") || message.includes("not sent") || message.includes("user denied") || message.includes("user rejected") || message.includes("user reject") || message.includes("user cancelled") || message.includes("user canceled") || message.includes("action_rejected") || message.includes("ethers-user-denied") || message.includes("denied transaction") || message.includes("reject request") || message.includes("declined by user");
2701
3375
  }
2702
3376
  return false;
2703
3377
  }
@@ -2716,17 +3390,13 @@ function toChainStrategyError(error, chainKey, context) {
2716
3390
  error
2717
3391
  );
2718
3392
  }
2719
- return new ChainStrategyError(
2720
- String(error),
2721
- "UNKNOWN_ERROR",
2722
- chainKey
2723
- );
3393
+ return new ChainStrategyError(String(error), "UNKNOWN_ERROR", chainKey);
2724
3394
  }
2725
3395
  function useBridgeTransaction() {
2726
3396
  const { quote } = useBridgeQuoteStore();
2727
3397
  const { chainRegistry } = useChainStrategies();
2728
3398
  const { srcAddress, dstAddress } = useAddresses();
2729
- const { assetMatrix, tokens, selectedAssetSymbol } = useTokensStore();
3399
+ const { assetMatrix, selectedAssetSymbol } = useTokensStore();
2730
3400
  const { chains } = useChainsStore();
2731
3401
  const txStore = useTransactionStore();
2732
3402
  const [isProcessing, setIsProcessing] = useState(false);
@@ -2756,29 +3426,32 @@ function useBridgeTransaction() {
2756
3426
  const srcChain = chains?.find((c) => c.chainKey === quote.srcChainKey);
2757
3427
  const dstChain = chains?.find((c) => c.chainKey === quote.dstChainKey);
2758
3428
  const amounts = getQuoteAmounts(quote, srcToken, dstToken);
2759
- const fees = getQuoteFees(quote, tokens, chains, srcToken, dstToken);
2760
3429
  const metadata = {
2761
3430
  srcChainName: srcChain?.name || quote.srcChainKey,
2762
3431
  dstChainName: dstChain?.name || quote.dstChainKey,
2763
3432
  srcTokenSymbol: srcToken?.symbol || quote.srcToken,
2764
3433
  dstTokenSymbol: dstToken?.symbol || quote.dstToken,
2765
3434
  srcAmountHuman: amounts.inputHuman,
2766
- dstAmountHuman: amounts.outputHuman,
2767
- // TODO: IT'S NOT TRUE DAMN :)
2768
- totalFeeValue: fees.inSrcToken ?? fees.inDstToken ?? void 0,
2769
- totalFeeSymbol: fees.inSrcToken ? srcToken?.symbol : fees.inDstToken ? dstToken?.symbol : void 0
3435
+ dstAmountHuman: amounts.outputHuman
3436
+ // Actual fee will be updated when transaction completes
2770
3437
  };
2771
3438
  txStore.setTransaction(quote, "executing", metadata);
2772
3439
  setIsProcessing(true);
2773
3440
  try {
2774
3441
  const steps = quote.steps || [];
2775
3442
  if (steps.length === 0) {
2776
- throw new InvalidStepsError(quote.srcChainKey, "No transaction steps found in quote");
3443
+ throw new InvalidStepsError(
3444
+ quote.srcChainKey,
3445
+ "No transaction steps found in quote"
3446
+ );
2777
3447
  }
2778
3448
  const chainKey = steps[0].chainKey;
2779
3449
  const strategy = chainRegistry.getStrategy(chainKey);
2780
3450
  if (!strategy) {
2781
- throw new InvalidStepsError(chainKey, `No strategy available for chain: ${chainKey}`);
3451
+ throw new InvalidStepsError(
3452
+ chainKey,
3453
+ `No strategy available for chain: ${chainKey}`
3454
+ );
2782
3455
  }
2783
3456
  if (!strategy.isConnected()) {
2784
3457
  throw new WalletNotConnectedError(chainKey);
@@ -2790,7 +3463,6 @@ function useBridgeTransaction() {
2790
3463
  srcChainKey: quote.srcChainKey,
2791
3464
  dstChainKey: quote.dstChainKey
2792
3465
  };
2793
- console.log(steps);
2794
3466
  const txResult = await strategy.executeSteps(steps, context, (hash) => {
2795
3467
  txStore.setSrcHash(hash);
2796
3468
  txStore.updateStatus("processing");
@@ -2801,6 +3473,21 @@ function useBridgeTransaction() {
2801
3473
  if (result.dstTxHash) {
2802
3474
  txStore.setDstHash(result.dstTxHash);
2803
3475
  }
3476
+ if (result.actualFeeValue) {
3477
+ let feeSymbol = result.actualFeeSymbol;
3478
+ if (!feeSymbol) {
3479
+ const srcChain2 = chains?.find(
3480
+ (c) => c.chainKey === quote.srcChainKey
3481
+ );
3482
+ feeSymbol = srcChain2?.nativeCurrency?.symbol || "";
3483
+ }
3484
+ if (feeSymbol) {
3485
+ const feeValue = parseFloat(result.actualFeeValue);
3486
+ if (!isNaN(feeValue)) {
3487
+ txStore.updateActualFee(feeValue, feeSymbol);
3488
+ }
3489
+ }
3490
+ }
2804
3491
  txStore.updateStatus("completed");
2805
3492
  console.log("Transaction completed successfully");
2806
3493
  } else {
@@ -2818,19 +3505,26 @@ function useBridgeTransaction() {
2818
3505
  console.error("Error tracking completion:", err);
2819
3506
  });
2820
3507
  } else {
2821
- throw new TransactionFailedError(chainKey, "Transaction hash not received from wallet");
3508
+ throw new TransactionFailedError(
3509
+ chainKey,
3510
+ "Transaction hash not received from wallet"
3511
+ );
2822
3512
  }
2823
3513
  return txResult;
2824
3514
  } catch (err) {
2825
3515
  if (isUserRejection(err)) {
2826
3516
  txStore.setError("TRANSACTION_REJECTED");
2827
- throw new TransactionFailedError(quote.srcChainKey, "Transaction rejected by user");
3517
+ throw new TransactionFailedError(
3518
+ quote.srcChainKey,
3519
+ "Transaction rejected by user"
3520
+ );
2828
3521
  }
2829
3522
  if (ChainStrategyError.isChainStrategyError(err)) {
2830
3523
  txStore.setError(err.code, { chainKey: err.chainKey });
2831
3524
  console.error("Chain strategy error:", err.toJSON());
2832
3525
  throw err;
2833
3526
  }
3527
+ console.log(err);
2834
3528
  txStore.setError("UNKNOWN_ERROR");
2835
3529
  throw err;
2836
3530
  } finally {
@@ -2843,66 +3537,6 @@ function useBridgeTransaction() {
2843
3537
  hasQuote: !!quote
2844
3538
  };
2845
3539
  }
2846
- function useGasEstimate(amountNum) {
2847
- const { fromChain } = useChainsStore();
2848
- const { selectedAssetSymbol, tokens } = useTokensStore();
2849
- const getSourceGasReserveHuman = useSettingsStore(
2850
- (state) => state.getSourceGasReserveHuman
2851
- );
2852
- const { srcAddress } = useAddresses();
2853
- const { balances, isLoading: balancesLoading } = useBalances(
2854
- fromChain?.chainKey,
2855
- srcAddress
2856
- );
2857
- const { chainRegistry } = useChainStrategies();
2858
- const balancesKnown = !balancesLoading;
2859
- const strategy = useMemo(() => {
2860
- if (!fromChain) return null;
2861
- return chainRegistry.getStrategy(fromChain.chainKey);
2862
- }, [fromChain, chainRegistry]);
2863
- const { data: gasRequirement } = useQuery({
2864
- queryKey: [
2865
- "gas-estimate",
2866
- fromChain?.chainKey,
2867
- selectedAssetSymbol,
2868
- amountNum,
2869
- balances
2870
- ],
2871
- queryFn: async () => {
2872
- if (!fromChain || !strategy) {
2873
- return null;
2874
- }
2875
- const selectedToken = tokens?.find(
2876
- (t) => t.symbol.toUpperCase() === selectedAssetSymbol?.toUpperCase()
2877
- ) || null;
2878
- const nativeTokenSymbol = fromChain.nativeCurrency?.symbol ?? "";
2879
- const nativeDecimals = fromChain.nativeCurrency?.decimals || 18;
2880
- const reserveFallback = getSourceGasReserveHuman(fromChain.chainKey);
2881
- const result = await strategy.estimateGasRequirement({
2882
- selectedToken,
2883
- nativeTokenSymbol,
2884
- amount: amountNum,
2885
- balances,
2886
- nativeDecimals,
2887
- reserveFallback
2888
- });
2889
- return result;
2890
- },
2891
- enabled: !!fromChain && !!strategy,
2892
- staleTime: 3e4,
2893
- gcTime: 5 * 6e4,
2894
- refetchOnWindowFocus: false,
2895
- retry: 1
2896
- });
2897
- return {
2898
- nativeSym: gasRequirement?.nativeSym || "",
2899
- nativeBalance: gasRequirement?.nativeBalance || 0,
2900
- requiredNative: gasRequirement?.requiredNative || 0,
2901
- balancesKnown,
2902
- isNativeSelected: gasRequirement?.isNativeSelected || false,
2903
- hasEnoughGas: balancesKnown ? gasRequirement?.hasEnoughGas ?? true : true
2904
- };
2905
- }
2906
3540
  function useBalanceCheck(amountNum, gas) {
2907
3541
  const { fromChain } = useChainsStore();
2908
3542
  const { selectedAssetSymbol } = useTokensStore();
@@ -3155,6 +3789,7 @@ const WalletModalButton = (props) => {
3155
3789
  const { icon: IconComponent, name, onClose } = props;
3156
3790
  const { chainRegistry } = useChainStrategies();
3157
3791
  const { connect, isPending } = useConnect();
3792
+ const [isConnecting, setIsConnecting] = useState(false);
3158
3793
  if (props.variant === "connected") {
3159
3794
  const { address, onDisconnect } = props;
3160
3795
  return /* @__PURE__ */ jsx("div", { className: "-mx-3", children: /* @__PURE__ */ jsxs("div", { className: buttonBaseClasses, children: [
@@ -3178,6 +3813,7 @@ const WalletModalButton = (props) => {
3178
3813
  }
3179
3814
  const { walletId, connector } = props;
3180
3815
  const handleConnect = async () => {
3816
+ setIsConnecting(true);
3181
3817
  try {
3182
3818
  if (connector) {
3183
3819
  connect({ connector });
@@ -3190,9 +3826,13 @@ const WalletModalButton = (props) => {
3190
3826
  onClose?.();
3191
3827
  } catch (error) {
3192
3828
  console.error("Failed to connect wallet:", error);
3829
+ const errorMessage = error instanceof Error ? error.message : "Failed to connect wallet. Please try again.";
3830
+ toast.error(errorMessage);
3831
+ } finally {
3832
+ setIsConnecting(false);
3193
3833
  }
3194
3834
  };
3195
- const isDisabled = connector ? isPending : false;
3835
+ const isDisabled = connector ? isPending : isConnecting;
3196
3836
  return /* @__PURE__ */ jsxs(
3197
3837
  Button,
3198
3838
  {
@@ -3227,8 +3867,8 @@ const WalletSelectModal = () => {
3227
3867
  {
3228
3868
  strategy: tonWallet,
3229
3869
  walletId: "ton",
3230
- name: t("wallets.tonKeeper"),
3231
- icon: TonKeeperIcon
3870
+ name: t("wallets.tonconnect"),
3871
+ icon: TonConnectIcon
3232
3872
  },
3233
3873
  {
3234
3874
  strategy: metaMaskWallet,
@@ -3266,8 +3906,8 @@ const WalletSelectModal = () => {
3266
3906
  const tonWallets = [
3267
3907
  {
3268
3908
  id: "ton",
3269
- name: t("wallets.tonKeeper"),
3270
- icon: TonKeeperIcon,
3909
+ name: t("wallets.tonconnect"),
3910
+ icon: TonConnectIcon,
3271
3911
  enabled: true
3272
3912
  }
3273
3913
  ];
@@ -3303,7 +3943,7 @@ const WalletSelectModal = () => {
3303
3943
  }
3304
3944
  ].filter((category) => category.wallets.length > 0);
3305
3945
  return /* @__PURE__ */ jsx(Dialog, { open: isOpen, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs(DialogContent, { children: [
3306
- /* @__PURE__ */ jsxs(DialogHeader, { children: [
3946
+ /* @__PURE__ */ jsxs(DialogHeader, { className: "text-left", children: [
3307
3947
  /* @__PURE__ */ jsx(DialogTitle, { children: t("wallets.chooseWallet") }),
3308
3948
  /* @__PURE__ */ jsx(DialogDescription, { children: t("wallets.oneWalletPerEnv") })
3309
3949
  ] }),
@@ -3346,7 +3986,7 @@ const WalletSelectModal = () => {
3346
3986
  ] }) });
3347
3987
  };
3348
3988
  const ProgressStep = ({
3349
- icon = /* @__PURE__ */ jsx(Loader2, { className: "w-12 h-12 animate-spin" })
3989
+ icon = /* @__PURE__ */ jsx(Loader2, { className: "w-16 h-16 animate-spin" })
3350
3990
  }) => {
3351
3991
  const { t } = useBridgeTranslation();
3352
3992
  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,40 +3996,130 @@ const ProgressStep = ({
3356
3996
  ] }) });
3357
3997
  };
3358
3998
  const FailedStep = ({
3359
- icon = /* @__PURE__ */ jsx(AlertCircleIcon, { className: "w-12 h-12" })
3999
+ icon = /* @__PURE__ */ jsx(AlertCircleIcon, { className: "w-16 h-16" })
3360
4000
  }) => {
3361
4001
  const { current, reset } = useTransactionStore();
3362
4002
  const { t } = useBridgeTranslation();
3363
4003
  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: [
4004
+ /* @__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
4005
  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
- ) }) })
4006
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
4007
+ /* @__PURE__ */ jsx(DialogTitle, { children: t("transaction.failed") }),
4008
+ current?.errorCode && /* @__PURE__ */ jsx(DialogDescription$1, { children: t(
4009
+ `errors.${current.errorCode}`,
4010
+ current.errorParams || {}
4011
+ ) })
4012
+ ] })
3371
4013
  ] }),
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") }) })
4014
+ /* @__PURE__ */ jsx(DialogFooter, { children: /* @__PURE__ */ jsx(Button, { variant: "outline", className: "w-full min-w-40", onClick: reset, children: t("common.close") }) })
3373
4015
  ] });
3374
4016
  };
4017
+ const EXPLORER_CONFIGS = {
4018
+ // TON
4019
+ ton: {
4020
+ baseUrl: "https://tonscan.org",
4021
+ txPath: "/tx/"
4022
+ },
4023
+ // TRON
4024
+ tron: {
4025
+ baseUrl: "https://tronscan.org",
4026
+ txPath: "/#/transaction/"
4027
+ },
4028
+ // Ethereum & EVM chains
4029
+ ethereum: {
4030
+ baseUrl: "https://etherscan.io",
4031
+ txPath: "/tx/"
4032
+ },
4033
+ eth: {
4034
+ baseUrl: "https://etherscan.io",
4035
+ txPath: "/tx/"
4036
+ },
4037
+ // BSC (Binance Smart Chain)
4038
+ bsc: {
4039
+ baseUrl: "https://bscscan.com",
4040
+ txPath: "/tx/"
4041
+ },
4042
+ "binance-smart-chain": {
4043
+ baseUrl: "https://bscscan.com",
4044
+ txPath: "/tx/"
4045
+ },
4046
+ // Polygon
4047
+ polygon: {
4048
+ baseUrl: "https://polygonscan.com",
4049
+ txPath: "/tx/"
4050
+ },
4051
+ matic: {
4052
+ baseUrl: "https://polygonscan.com",
4053
+ txPath: "/tx/"
4054
+ },
4055
+ // Avalanche
4056
+ avalanche: {
4057
+ baseUrl: "https://snowtrace.io",
4058
+ txPath: "/tx/"
4059
+ },
4060
+ avax: {
4061
+ baseUrl: "https://snowtrace.io",
4062
+ txPath: "/tx/"
4063
+ },
4064
+ // Arbitrum
4065
+ arbitrum: {
4066
+ baseUrl: "https://arbiscan.io",
4067
+ txPath: "/tx/"
4068
+ },
4069
+ // Optimism
4070
+ optimism: {
4071
+ baseUrl: "https://optimistic.etherscan.io",
4072
+ txPath: "/tx/"
4073
+ },
4074
+ // Base
4075
+ base: {
4076
+ baseUrl: "https://basescan.org",
4077
+ txPath: "/tx/"
4078
+ },
4079
+ // Fantom
4080
+ fantom: {
4081
+ baseUrl: "https://ftmscan.com",
4082
+ txPath: "/tx/"
4083
+ }
4084
+ };
4085
+ function getExplorerTxUrl(chainKey, txHash) {
4086
+ if (!chainKey || !txHash) {
4087
+ return null;
4088
+ }
4089
+ const normalizedChainKey = chainKey.toLowerCase();
4090
+ const config = EXPLORER_CONFIGS[normalizedChainKey];
4091
+ if (!config) {
4092
+ console.warn(
4093
+ `No explorer config found for chain: ${chainKey}. Please add it to EXPLORER_CONFIGS.`
4094
+ );
4095
+ return null;
4096
+ }
4097
+ return `${config.baseUrl}${config.txPath}${txHash}`;
4098
+ }
4099
+ function openTransactionInExplorer(chainKey, txHash) {
4100
+ const url = getExplorerTxUrl(chainKey, txHash);
4101
+ if (url && typeof window !== "undefined") {
4102
+ window.open(url, "_blank", "noopener,noreferrer");
4103
+ }
4104
+ }
3375
4105
  const SuccessStep = ({
3376
- icon = /* @__PURE__ */ jsx(CheckCircle2, { className: "w-12 h-12" })
4106
+ icon = /* @__PURE__ */ jsx(CheckCircle2, { className: "w-16 h-16" })
3377
4107
  }) => {
3378
4108
  const { current, reset } = useTransactionStore();
3379
4109
  const { t } = useBridgeTranslation();
3380
4110
  const metadata = current?.metadata;
3381
4111
  const srcTxHash = current?.srcTxHash;
3382
- const handleCopyHash = () => {
3383
- if (srcTxHash) {
3384
- navigator.clipboard.writeText(srcTxHash);
3385
- toast.success(t("transaction.hashCopied"));
4112
+ const srcChainKey = current?.quote?.srcChainKey;
4113
+ const handleOpenExplorer = () => {
4114
+ if (srcTxHash && srcChainKey) {
4115
+ openTransactionInExplorer(srcChainKey, srcTxHash);
3386
4116
  }
3387
4117
  };
3388
4118
  return /* @__PURE__ */ jsxs(DialogContent, { showCloseButton: false, children: [
3389
4119
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 pt-10 px-8 flex-1 justify-start items-center text-center noise bg-background", children: [
3390
4120
  icon,
3391
4121
  /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: t("transaction.success") }) }),
3392
- /* @__PURE__ */ jsxs("div", { className: "w-full space-y-3 mt-4 relative z-10 text-sm", children: [
4122
+ /* @__PURE__ */ jsxs("div", { className: "w-full space-y-2 mt-4 relative z-10 pb-14", children: [
3393
4123
  metadata?.srcAmountHuman && /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
3394
4124
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("transaction.bridged") }),
3395
4125
  /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 font-medium", children: [
@@ -3401,15 +4131,13 @@ const SuccessStep = ({
3401
4131
  ] }),
3402
4132
  /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
3403
4133
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("transaction.transferTitle") }),
3404
- /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
4134
+ /* @__PURE__ */ jsxs("span", { className: "font-medium space-x-1", children: [
3405
4135
  /* @__PURE__ */ jsxs("span", { className: "inline-flex gap-1 items-center", children: [
3406
4136
  metadata?.srcChainName,
3407
4137
  " ",
3408
4138
  /* @__PURE__ */ jsx(NetworkSymbol, { chainKey: metadata.srcChainName })
3409
4139
  ] }),
3410
- " ",
3411
- "→",
3412
- " ",
4140
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "→" }),
3413
4141
  /* @__PURE__ */ jsxs("span", { className: "inline-flex gap-1 items-center", children: [
3414
4142
  metadata?.dstChainName,
3415
4143
  " ",
@@ -3422,18 +4150,18 @@ const SuccessStep = ({
3422
4150
  /* @__PURE__ */ jsx(
3423
4151
  "button",
3424
4152
  {
3425
- onClick: handleCopyHash,
3426
- className: "font-medium hover:underline cursor-pointer",
4153
+ onClick: handleOpenExplorer,
4154
+ className: "font-medium cursor-pointer inline-flex items-center gap-1 underline hover:no-underline",
3427
4155
  children: formatHash(srcTxHash)
3428
4156
  }
3429
4157
  )
3430
4158
  ] }),
3431
- metadata?.totalFeeValue && /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
4159
+ metadata?.actualFeeValue && /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
3432
4160
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("transaction.finalFee") }),
3433
4161
  /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
3434
- metadata.totalFeeValue,
4162
+ metadata.actualFeeValue,
3435
4163
  " ",
3436
- metadata.totalFeeSymbol
4164
+ metadata.actualFeeSymbol
3437
4165
  ] })
3438
4166
  ] })
3439
4167
  ] })
@@ -3459,7 +4187,7 @@ const useCountdown = (initialSeconds) => {
3459
4187
  };
3460
4188
  };
3461
4189
  const ConfirmStep = ({
3462
- icon = /* @__PURE__ */ jsx(Clock, { className: "w-12 h-12" })
4190
+ icon = /* @__PURE__ */ jsx(Clock, { className: "w-16 h-16" })
3463
4191
  }) => {
3464
4192
  const { t } = useBridgeTranslation();
3465
4193
  const { formatTime } = useCountdown(90);
@@ -3530,7 +4258,7 @@ const useTokens = () => {
3530
4258
  return { ...query };
3531
4259
  };
3532
4260
  const useChains = () => {
3533
- const { setChains, setFromChain } = useChainsStore();
4261
+ const { setChains, setFromChain, fromChain } = useChainsStore();
3534
4262
  const query = useQuery({
3535
4263
  queryKey: ["chains"],
3536
4264
  queryFn: () => getChains(),
@@ -3544,10 +4272,16 @@ const useChains = () => {
3544
4272
  });
3545
4273
  useEffect(() => {
3546
4274
  if (query.isSuccess && query.data?.length) {
4275
+ console.log(
4276
+ `[DEBUG] Loaded ${query.data.length} chains from API:`,
4277
+ query.data.map((c) => c.chainKey)
4278
+ );
3547
4279
  setChains(query.data);
3548
- setFromChain(query.data[0]);
4280
+ if (!fromChain) {
4281
+ setFromChain(query.data[0]);
4282
+ }
3549
4283
  }
3550
- }, [query.isSuccess, query.data, setChains, setFromChain]);
4284
+ }, [query.isSuccess, query.data]);
3551
4285
  useEffect(() => {
3552
4286
  if (query.isError && query.error) {
3553
4287
  console.error(query.error.name, query.error.message);
@@ -3613,49 +4347,6 @@ class ChainStrategyRegistry {
3613
4347
  await strategy.disconnect();
3614
4348
  }
3615
4349
  }
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
4350
  function isNativeAddress(addr) {
3660
4351
  if (!addr) return false;
3661
4352
  const a = addr.toLowerCase();
@@ -3748,7 +4439,9 @@ async function getEvmBalances(publicClient, address, tokens, priorityToken) {
3748
4439
  }
3749
4440
  } catch (error) {
3750
4441
  const errorMessage = error instanceof Error ? error.message : String(error);
3751
- const isZeroDataError = errorMessage.includes('returned no data ("0x")');
4442
+ const isZeroDataError = errorMessage.includes(
4443
+ 'returned no data ("0x")'
4444
+ );
3752
4445
  if (!isZeroDataError) {
3753
4446
  console.debug(
3754
4447
  `Failed to get priority token balance for ${priorityToken.symbol}:`,
@@ -3859,12 +4552,9 @@ async function getTonBalances(address, tokens, customTonClient, tonApiKey) {
3859
4552
  }
3860
4553
  const client = getTonClient(customTonClient, tonApiKey);
3861
4554
  const accountAddress = Address$1.parse(address);
3862
- console.log(address);
3863
- console.log(tokens);
3864
4555
  try {
3865
4556
  const balance = await client.getBalance(accountAddress);
3866
4557
  const tonBalance = Number(balance) / 1e9;
3867
- console.log("tonBalance", tonBalance);
3868
4558
  if (tonBalance > 0) {
3869
4559
  balances.TON = { balance: tonBalance, address: "ton-native" };
3870
4560
  }
@@ -3921,8 +4611,23 @@ async function getTronBalances(tronWeb, address, tokens) {
3921
4611
  try {
3922
4612
  if (!tronWeb) throw new Error("TronWeb not available");
3923
4613
  const ownerB58 = toTronBase58(address, tronWeb);
4614
+ try {
4615
+ const trxBalanceSun = await tronWeb.trx.getBalance(ownerB58);
4616
+ const sunNum = Number(trxBalanceSun);
4617
+ const trxVal = tronWeb.fromSun(sunNum);
4618
+ const trxStr = typeof trxVal === "string" ? trxVal : trxVal.toString();
4619
+ const trxBalance = parseFloat(trxStr);
4620
+ if (trxBalance > 0) {
4621
+ balances.TRX = { balance: trxBalance, address: ownerB58 };
4622
+ }
4623
+ } catch (error) {
4624
+ console.warn("Failed to get native TRX balance:", error);
4625
+ }
3924
4626
  for (const token of tokens) {
3925
4627
  try {
4628
+ if (isNativeAddress(token.address) && token.symbol.toUpperCase() === "TRX") {
4629
+ continue;
4630
+ }
3926
4631
  let balance = 0;
3927
4632
  if (isNativeAddress(token.address)) {
3928
4633
  const trxBalanceSun = await tronWeb.trx.getBalance(ownerB58);
@@ -4009,6 +4714,9 @@ class EvmChainStrategy {
4009
4714
  getConnectLabel(t) {
4010
4715
  return t("wallets.connectEvmWallet");
4011
4716
  }
4717
+ getClient() {
4718
+ return this.provider;
4719
+ }
4012
4720
  async getBalances(address, tokens, priorityToken) {
4013
4721
  if (!this.publicClient) {
4014
4722
  console.warn("No publicClient available for balance query");
@@ -4025,52 +4733,6 @@ class EvmChainStrategy {
4025
4733
  if (!address) return false;
4026
4734
  return /^0x[0-9a-fA-F]{40}$/.test(address);
4027
4735
  }
4028
- async estimateGasRequirement(params) {
4029
- const provider = this.provider;
4030
- const {
4031
- selectedToken,
4032
- nativeTokenSymbol,
4033
- amount,
4034
- balances,
4035
- nativeDecimals,
4036
- reserveFallback
4037
- } = params;
4038
- const nativeSym = nativeTokenSymbol.toUpperCase();
4039
- const isNativeSelected = nativeSym === (selectedToken?.symbol ?? "").toUpperCase();
4040
- const nativeBalance = Number(balances[nativeSym]?.balance ?? 0);
4041
- let estimatedGas = null;
4042
- if (provider) {
4043
- try {
4044
- const gasPriceHex = await provider.send("eth_gasPrice", []);
4045
- const gasPrice = BigInt(gasPriceHex);
4046
- const approveGas = isNativeSelected ? 0n : EVM_CONFIG.gasEstimates.approve;
4047
- const bridgeGas = EVM_CONFIG.gasEstimates.bridge;
4048
- const totalGas = approveGas + bridgeGas;
4049
- const bufferMultiplier = BigInt(Math.floor(EVM_CONFIG.gasBuffer * 100));
4050
- const requiredWei = gasPrice * totalGas * bufferMultiplier / 100n;
4051
- const { formatUnits: formatUnits2 } = await import("ethers");
4052
- estimatedGas = Number(formatUnits2(requiredWei, nativeDecimals));
4053
- } catch {
4054
- estimatedGas = null;
4055
- }
4056
- }
4057
- const requiredNative = estimatedGas ?? reserveFallback;
4058
- const amountNum = amount ?? 0;
4059
- let hasEnoughGas = true;
4060
- if (isNativeSelected) {
4061
- hasEnoughGas = nativeBalance - amountNum >= requiredNative;
4062
- } else {
4063
- hasEnoughGas = nativeBalance >= requiredNative;
4064
- }
4065
- return {
4066
- estimatedGas,
4067
- nativeBalance,
4068
- requiredNative,
4069
- hasEnoughGas,
4070
- nativeSym,
4071
- isNativeSelected
4072
- };
4073
- }
4074
4736
  validateSteps(steps) {
4075
4737
  if (!steps || steps.length === 0) {
4076
4738
  throw new InvalidStepsError("evm", "No transaction steps provided");
@@ -4163,8 +4825,24 @@ class EvmChainStrategy {
4163
4825
  console.log(
4164
4826
  `EVM transaction confirmed in block ${receipt.blockNumber} with ${EVM_CONFIG.requiredConfirmations} confirmations`
4165
4827
  );
4828
+ let actualFeeValue;
4829
+ try {
4830
+ const gasUsed = receipt.gasUsed;
4831
+ const effectiveGasPrice = receipt.gasPrice;
4832
+ if (gasUsed && effectiveGasPrice) {
4833
+ const feeWei = gasUsed * effectiveGasPrice;
4834
+ const { formatUnits: formatUnits2 } = await import("ethers");
4835
+ const feeInNative = formatUnits2(feeWei, 18);
4836
+ actualFeeValue = feeInNative;
4837
+ console.log(`EVM transaction fee: ${feeInNative} (native token)`);
4838
+ }
4839
+ } catch (error) {
4840
+ console.warn("Failed to calculate actual fee:", error);
4841
+ }
4166
4842
  return {
4167
- completed: true
4843
+ completed: true,
4844
+ actualFeeValue
4845
+ // Symbol will be determined by the caller based on chain info
4168
4846
  };
4169
4847
  } catch (error) {
4170
4848
  if (error && typeof error === "object" && "code" in error && error.code === "TRANSACTION_REPLACED") {
@@ -4177,8 +4855,26 @@ class EvmChainStrategy {
4177
4855
  console.log(
4178
4856
  `Replacement transaction succeeded in block ${replacementReceipt.blockNumber}`
4179
4857
  );
4858
+ let actualFeeValue;
4859
+ try {
4860
+ const receipt = error.receipt;
4861
+ const gasUsed = receipt.gasUsed;
4862
+ const effectiveGasPrice = receipt.gasPrice || receipt.effectiveGasPrice;
4863
+ if (gasUsed && effectiveGasPrice) {
4864
+ const feeWei = gasUsed * effectiveGasPrice;
4865
+ const { formatUnits: formatUnits2 } = await import("ethers");
4866
+ const feeInNative = formatUnits2(feeWei, 18);
4867
+ actualFeeValue = feeInNative;
4868
+ console.log(
4869
+ `Replacement transaction fee: ${feeInNative} (native token)`
4870
+ );
4871
+ }
4872
+ } catch (feeError) {
4873
+ console.warn("Failed to calculate replacement transaction fee:", feeError);
4874
+ }
4180
4875
  return {
4181
- completed: true
4876
+ completed: true,
4877
+ actualFeeValue
4182
4878
  };
4183
4879
  } else {
4184
4880
  const chainError2 = new TransactionRevertedError("evm", txHash);
@@ -4381,6 +5077,9 @@ class TonChainStrategy {
4381
5077
  getConnectLabel(t) {
4382
5078
  return t("wallets.connectTonWallet");
4383
5079
  }
5080
+ getClient() {
5081
+ return getTonClient(this.config.tonClient, this.config.tonApiKey);
5082
+ }
4384
5083
  async getBalances(address, tokens) {
4385
5084
  return await getTonBalances(
4386
5085
  address,
@@ -4393,35 +5092,6 @@ class TonChainStrategy {
4393
5092
  if (!address) return false;
4394
5093
  return true;
4395
5094
  }
4396
- async estimateGasRequirement(params) {
4397
- const {
4398
- selectedToken,
4399
- nativeTokenSymbol,
4400
- amount,
4401
- balances,
4402
- reserveFallback
4403
- } = params;
4404
- const nativeSym = nativeTokenSymbol.toUpperCase();
4405
- const isNativeSelected = nativeSym === (selectedToken?.symbol ?? "").toUpperCase();
4406
- const nativeBalance = Number(balances[nativeSym]?.balance ?? 0);
4407
- const estimatedGas = null;
4408
- const requiredNative = reserveFallback;
4409
- const amountNum = amount ?? 0;
4410
- let hasEnoughGas = true;
4411
- if (isNativeSelected) {
4412
- hasEnoughGas = nativeBalance - amountNum >= requiredNative;
4413
- } else {
4414
- hasEnoughGas = nativeBalance >= requiredNative;
4415
- }
4416
- return {
4417
- estimatedGas,
4418
- nativeBalance,
4419
- requiredNative,
4420
- hasEnoughGas,
4421
- nativeSym,
4422
- isNativeSelected
4423
- };
4424
- }
4425
5095
  validateSteps(steps) {
4426
5096
  if (!steps || steps.length === 0) {
4427
5097
  throw new InvalidStepsError("ton", "No transaction steps provided");
@@ -4474,23 +5144,35 @@ class TonChainStrategy {
4474
5144
  const result = await this.config.tonConnectUI.sendTransaction(
4475
5145
  transaction2
4476
5146
  );
4477
- const hash = result.boc;
4478
- onFirstHash?.(hash);
4479
- return {
4480
- chainKey: "ton",
4481
- hash
4482
- };
5147
+ const bocBase64 = result.boc;
5148
+ try {
5149
+ const inMessage = loadMessage(Cell.fromBase64(bocBase64).beginParse());
5150
+ const messageHash = this.getNormalizedExtMessageHash(inMessage);
5151
+ const hexHash = messageHash.toString("hex");
5152
+ onFirstHash?.(hexHash);
5153
+ return {
5154
+ chainKey: "ton",
5155
+ hash: hexHash
5156
+ };
5157
+ } catch (error) {
5158
+ console.error("Error parsing BOC to hex hash:", error);
5159
+ onFirstHash?.(bocBase64);
5160
+ return {
5161
+ chainKey: "ton",
5162
+ hash: bocBase64
5163
+ };
5164
+ }
4483
5165
  } catch (error) {
4484
5166
  throw toChainStrategyError(error, "ton", "transaction");
4485
5167
  }
4486
5168
  }
4487
5169
  async waitForCompletion(txHash) {
4488
5170
  try {
4489
- const confirmed = await this.checkTonTransaction(
5171
+ const result = await this.checkTonTransaction(
4490
5172
  txHash,
4491
5173
  TON_CONFIG.timeout
4492
5174
  );
4493
- if (!confirmed) {
5175
+ if (!result.confirmed) {
4494
5176
  const error = new TransactionFailedError(
4495
5177
  "ton",
4496
5178
  "Transaction not confirmed on-chain",
@@ -4502,7 +5184,9 @@ class TonChainStrategy {
4502
5184
  };
4503
5185
  }
4504
5186
  return {
4505
- completed: true
5187
+ completed: true,
5188
+ actualFeeValue: result.fee,
5189
+ actualFeeSymbol: "TON"
4506
5190
  };
4507
5191
  } catch (error) {
4508
5192
  const chainError = toChainStrategyError(
@@ -4532,20 +5216,31 @@ class TonChainStrategy {
4532
5216
  };
4533
5217
  return beginCell$1().store(storeMessage(normalizedMessage, { forceRef: true })).endCell().hash();
4534
5218
  }
4535
- async checkTonTransaction(bocBase64, timeoutMs = 36e4) {
5219
+ async checkTonTransaction(hashOrBoc, timeoutMs = 36e4) {
4536
5220
  const deadline = Date.now() + timeoutMs;
4537
5221
  const client = getTonClient(this.config.tonClient, this.config.tonApiKey);
4538
5222
  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;
5223
+ let targetMessageHash;
5224
+ let accountAddress;
5225
+ try {
5226
+ const inMessage = loadMessage(Cell.fromBase64(hashOrBoc).beginParse());
5227
+ if (inMessage.info.type !== "external-in") {
5228
+ console.debug(
5229
+ "Expected external-in message, got:",
5230
+ inMessage.info.type
5231
+ );
5232
+ return { confirmed: false };
5233
+ }
5234
+ accountAddress = inMessage.info.dest;
5235
+ targetMessageHash = this.getNormalizedExtMessageHash(inMessage);
5236
+ } catch {
5237
+ targetMessageHash = Buffer.from(hashOrBoc, "hex");
5238
+ if (!this.config.tonAddress) {
5239
+ console.debug("No wallet address available for hex hash lookup");
5240
+ return { confirmed: false };
5241
+ }
5242
+ accountAddress = Address.parse(this.config.tonAddress);
4546
5243
  }
4547
- const accountAddress = inMessage.info.dest;
4548
- const targetMessageHash = this.getNormalizedExtMessageHash(inMessage);
4549
5244
  let lt = void 0;
4550
5245
  let hash = void 0;
4551
5246
  while (Date.now() < deadline) {
@@ -4557,7 +5252,7 @@ class TonChainStrategy {
4557
5252
  archival: true
4558
5253
  });
4559
5254
  if (transactions.length === 0) {
4560
- await new Promise((r) => setTimeout(r, 3e3));
5255
+ await new Promise((r) => setTimeout(r, TON_CONFIG.pollingInterval));
4561
5256
  lt = void 0;
4562
5257
  hash = void 0;
4563
5258
  continue;
@@ -4569,24 +5264,31 @@ class TonChainStrategy {
4569
5264
  );
4570
5265
  if (txInMessageHash.equals(targetMessageHash)) {
4571
5266
  console.debug("Transaction found by in-message hash");
4572
- return true;
5267
+ const totalFees = tx.totalFees;
5268
+ const feeInTon = Number(totalFees) / 1e9;
5269
+ console.log(`TON transaction fee: ${feeInTon} TON`);
5270
+ return {
5271
+ confirmed: true,
5272
+ fee: feeInTon.toString()
5273
+ };
4573
5274
  }
4574
5275
  }
4575
5276
  }
4576
5277
  const lastTx = transactions[transactions.length - 1];
4577
5278
  lt = lastTx.lt.toString();
4578
5279
  hash = lastTx.hash().toString("base64");
5280
+ await new Promise((r) => setTimeout(r, TON_CONFIG.pollingInterval));
4579
5281
  } catch (error) {
4580
5282
  console.debug("Error fetching transactions:", error);
4581
- await new Promise((r) => setTimeout(r, 3e3));
5283
+ await new Promise((r) => setTimeout(r, TON_CONFIG.pollingInterval));
4582
5284
  lt = void 0;
4583
5285
  hash = void 0;
4584
5286
  }
4585
5287
  }
4586
- return false;
5288
+ return { confirmed: false };
4587
5289
  } catch (error) {
4588
5290
  console.debug("Error parsing BOC or checking transaction:", error);
4589
- return false;
5291
+ return { confirmed: false };
4590
5292
  }
4591
5293
  }
4592
5294
  }
@@ -4605,9 +5307,14 @@ class TronChainStrategy {
4605
5307
  return "TRON Chain Strategy";
4606
5308
  }
4607
5309
  async connect() {
4608
- const tronWeb = this.getTronWeb();
4609
- if (!tronWeb && (typeof window === "undefined" || !window.tronLink)) {
4610
- throw new WalletNotFoundError("tron", "TronLink");
5310
+ if (!this.isTronLinkInstalled()) {
5311
+ if (typeof window !== "undefined") {
5312
+ window.open("https://www.tronlink.org/", "_blank");
5313
+ }
5314
+ throw new WalletNotFoundError(
5315
+ "tron",
5316
+ "TronLink wallet is not installed. Please install TronLink extension and try again."
5317
+ );
4611
5318
  }
4612
5319
  this.config.tronSelect(TronLinkAdapterName);
4613
5320
  await new Promise((resolve) => setTimeout(resolve, 100));
@@ -4635,7 +5342,7 @@ class TronChainStrategy {
4635
5342
  return t("wallets.connectTronWallet");
4636
5343
  }
4637
5344
  async getBalances(address, tokens) {
4638
- const tronWeb = this.getTronWeb();
5345
+ const tronWeb = this.getClient();
4639
5346
  if (!tronWeb) return {};
4640
5347
  return await getTronBalances(tronWeb, address, tokens);
4641
5348
  }
@@ -4643,35 +5350,6 @@ class TronChainStrategy {
4643
5350
  if (!address) return false;
4644
5351
  return /^T[1-9A-HJ-NP-Za-km-z]{33}$/.test(address);
4645
5352
  }
4646
- async estimateGasRequirement(params) {
4647
- const {
4648
- selectedToken,
4649
- nativeTokenSymbol,
4650
- amount,
4651
- balances,
4652
- reserveFallback
4653
- } = params;
4654
- const nativeSym = nativeTokenSymbol.toUpperCase();
4655
- const isNativeSelected = nativeSym === (selectedToken?.symbol ?? "").toUpperCase();
4656
- const nativeBalance = Number(balances[nativeSym]?.balance ?? 0);
4657
- const estimatedGas = null;
4658
- const requiredNative = reserveFallback;
4659
- const amountNum = amount ?? 0;
4660
- let hasEnoughGas = true;
4661
- if (isNativeSelected) {
4662
- hasEnoughGas = nativeBalance - amountNum >= requiredNative;
4663
- } else {
4664
- hasEnoughGas = nativeBalance >= requiredNative;
4665
- }
4666
- return {
4667
- estimatedGas,
4668
- nativeBalance,
4669
- requiredNative,
4670
- hasEnoughGas,
4671
- nativeSym,
4672
- isNativeSelected
4673
- };
4674
- }
4675
5353
  validateSteps(steps) {
4676
5354
  console.log("validateSteps");
4677
5355
  if (!steps?.length) {
@@ -4688,8 +5366,7 @@ class TronChainStrategy {
4688
5366
  }
4689
5367
  }
4690
5368
  async executeSteps(steps, _context, onFirstHash) {
4691
- console.log("executeSteps");
4692
- const tronWeb = this.getTronWeb();
5369
+ const tronWeb = this.getClient();
4693
5370
  if (!tronWeb) {
4694
5371
  throw new ProviderNotAvailableError("tron");
4695
5372
  }
@@ -4783,7 +5460,7 @@ class TronChainStrategy {
4783
5460
  }
4784
5461
  async waitForCompletion(txHash) {
4785
5462
  try {
4786
- const tronWeb = this.getTronWeb();
5463
+ const tronWeb = this.getClient();
4787
5464
  if (!tronWeb) {
4788
5465
  throw new ProviderNotAvailableError("tron");
4789
5466
  }
@@ -4792,6 +5469,7 @@ class TronChainStrategy {
4792
5469
  );
4793
5470
  const deadline = Date.now() + TRON_CONFIG.timeout;
4794
5471
  let txBlockNumber = null;
5472
+ let actualFeeTrx = null;
4795
5473
  while (Date.now() < deadline && !txBlockNumber) {
4796
5474
  try {
4797
5475
  const info = await tronWeb.trx.getTransactionInfo(txHash);
@@ -4826,7 +5504,9 @@ class TronChainStrategy {
4826
5504
  };
4827
5505
  }
4828
5506
  txBlockNumber = info.blockNumber;
4829
- console.log(`TRON transaction found in block ${txBlockNumber}`);
5507
+ const feeSun = info.fee || 0;
5508
+ actualFeeTrx = feeSun / 1e6;
5509
+ console.log(`TRON transaction found in block ${txBlockNumber}, fee: ${actualFeeTrx} TRX`);
4830
5510
  }
4831
5511
  } catch (e) {
4832
5512
  console.debug("TRON getTransactionInfo error:", e);
@@ -4854,7 +5534,9 @@ class TronChainStrategy {
4854
5534
  `TRON transaction confirmed in block ${txBlockNumber} with ${confirmations} confirmations`
4855
5535
  );
4856
5536
  return {
4857
- completed: true
5537
+ completed: true,
5538
+ actualFeeValue: actualFeeTrx?.toString(),
5539
+ actualFeeSymbol: "TRX"
4858
5540
  };
4859
5541
  }
4860
5542
  console.log(
@@ -4883,9 +5565,26 @@ class TronChainStrategy {
4883
5565
  };
4884
5566
  }
4885
5567
  }
4886
- getTronWeb() {
5568
+ getClient() {
4887
5569
  return typeof window !== "undefined" ? window.tronWeb : void 0;
4888
5570
  }
5571
+ /**
5572
+ * Check if TronLink wallet is actually installed
5573
+ * This excludes Bybit Wallet which also injects tronLink for compatibility
5574
+ */
5575
+ isTronLinkInstalled() {
5576
+ if (typeof window === "undefined") {
5577
+ return false;
5578
+ }
5579
+ const hasBybitWallet = typeof window.bybitWallet !== "undefined" && typeof window.bybitWallet.tronLink !== "undefined";
5580
+ if (hasBybitWallet && !window.tronLink) {
5581
+ return false;
5582
+ }
5583
+ if (!window.tronLink) {
5584
+ return false;
5585
+ }
5586
+ return true;
5587
+ }
4889
5588
  hexToAscii(h) {
4890
5589
  if (!h) return null;
4891
5590
  const clean = h.replace(/^0x/, "");
@@ -5183,7 +5882,7 @@ class TronChainStrategy {
5183
5882
  */
5184
5883
  async checkSolidified(txHash) {
5185
5884
  try {
5186
- const tronWeb = this.getTronWeb();
5885
+ const tronWeb = this.getClient();
5187
5886
  if (!tronWeb) {
5188
5887
  return false;
5189
5888
  }
@@ -5289,7 +5988,7 @@ const routePresets = [
5289
5988
  RoutePriority.CHEAPEST,
5290
5989
  RoutePriority.RECOMMENDED
5291
5990
  ];
5292
- const SettingModal = ({ isOpen, onClose }) => {
5991
+ const SettingsModal = ({ isOpen, onClose }) => {
5293
5992
  const { t } = useBridgeTranslation();
5294
5993
  const { toChain } = useChainsStore();
5295
5994
  const { tokens } = useTokensStore();
@@ -5322,14 +6021,14 @@ const SettingModal = ({ isOpen, onClose }) => {
5322
6021
  );
5323
6022
  const activeBtn = "bg-primary hover:bg-primary/80 text-primary-foreground transition-colors";
5324
6023
  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") }) }),
6024
+ return /* @__PURE__ */ jsx(Dialog, { open: isOpen, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs(DialogContent, { onOpenAutoFocus: (e) => e.preventDefault(), children: [
6025
+ /* @__PURE__ */ jsx(DialogHeader, { className: "text-left", children: /* @__PURE__ */ jsx(DialogTitle, { children: t("settings.title") }) }),
5327
6026
  /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
5328
6027
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-5", children: [
5329
6028
  /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
5330
6029
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
5331
6030
  /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm font-medium leading-4", children: t("settings.gasOnDestination") }),
5332
- /* @__PURE__ */ jsx(Tip, { text: t("settings.gasOnDestination"), children: /* @__PURE__ */ jsx(TipIcon, { className: "w-4 h-4 text-muted-foreground" }) })
6031
+ /* @__PURE__ */ jsx(Tip, { text: t("settings.gasOnDestination"), children: /* @__PURE__ */ jsx(InfoIcon, { className: "w-4 h-4 text-muted-foreground" }) })
5333
6032
  ] }),
5334
6033
  /* @__PURE__ */ jsx("p", { className: "text-foreground text-sm font-medium leading-4", children: formatUsd(gasUsdValue) })
5335
6034
  ] }),
@@ -5351,6 +6050,7 @@ const SettingModal = ({ isOpen, onClose }) => {
5351
6050
  Badge,
5352
6051
  {
5353
6052
  onClick: () => setGasPreset(g),
6053
+ size: "lg",
5354
6054
  className: cn(
5355
6055
  "cursor-pointer",
5356
6056
  gasPreset === g ? activeBtn : notActiveBtn
@@ -5366,7 +6066,7 @@ const SettingModal = ({ isOpen, onClose }) => {
5366
6066
  /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
5367
6067
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
5368
6068
  /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm font-medium leading-4", children: t("settings.slippageTolerance") }),
5369
- /* @__PURE__ */ jsx(Tip, { text: t("settings.slippageTolerance"), children: /* @__PURE__ */ jsx(TipIcon, { className: "w-4 h-4 text-muted-foreground" }) })
6069
+ /* @__PURE__ */ jsx(Tip, { text: t("settings.slippageTolerance"), children: /* @__PURE__ */ jsx(InfoIcon, { className: "w-4 h-4 text-muted-foreground" }) })
5370
6070
  ] }),
5371
6071
  slippageBps >= 500 && /* @__PURE__ */ jsx("p", { className: "text-destructive text-xs font-medium", children: t("settings.highSlippageWarning", {
5372
6072
  defaultValue: "High slippage warning"
@@ -5377,6 +6077,7 @@ const SettingModal = ({ isOpen, onClose }) => {
5377
6077
  /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: slippagePresets.map((p) => /* @__PURE__ */ jsx(
5378
6078
  Badge,
5379
6079
  {
6080
+ size: "lg",
5380
6081
  onClick: () => {
5381
6082
  const bps = parseFloat(p.replace("%", "")) * 100;
5382
6083
  setSlippageBps(bps);
@@ -5395,11 +6096,12 @@ const SettingModal = ({ isOpen, onClose }) => {
5395
6096
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-5", children: [
5396
6097
  /* @__PURE__ */ jsx("div", { className: "flex justify-between items-center", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
5397
6098
  /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm font-medium leading-4", children: t("settings.routePriority") }),
5398
- /* @__PURE__ */ jsx(Tip, { text: t("settings.routePriority"), children: /* @__PURE__ */ jsx(TipIcon, { className: "w-4 h-4 text-muted-foreground" }) })
6099
+ /* @__PURE__ */ jsx(Tip, { text: t("settings.routePriority"), children: /* @__PURE__ */ jsx(InfoIcon, { className: "w-4 h-4 text-muted-foreground" }) })
5399
6100
  ] }) }),
5400
6101
  /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end gap-2", children: routePresets.map((r) => /* @__PURE__ */ jsx(
5401
6102
  Badge,
5402
6103
  {
6104
+ size: "lg",
5403
6105
  onClick: () => setRoutePriority(r),
5404
6106
  className: cn(
5405
6107
  "cursor-pointer",
@@ -5427,7 +6129,7 @@ const TokenRow = ({
5427
6129
  Button,
5428
6130
  {
5429
6131
  onClick: onPick,
5430
- className: `w-full rounded-sm bg-transparent flex items-center justify-between gap-3 px-5 hover:bg-accent hover:scale-100 ${isSelected ? "border border-ring" : ""}`,
6132
+ className: `w-full rounded-xs bg-transparent flex items-center justify-between gap-3 px-3 hover:bg-accent hover:scale-100 ${isSelected ? "bg-accent hover:bg-accent/50" : ""}`,
5431
6133
  children: [
5432
6134
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
5433
6135
  /* @__PURE__ */ jsx(
@@ -5448,7 +6150,7 @@ const TokenRow = ({
5448
6150
  /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-12 rounded-md" })
5449
6151
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
5450
6152
  /* @__PURE__ */ jsx("div", { className: "font-semibold text-foreground text-lg leading-4 truncate", children: hasAnyWallet ? formatBalance(balance) : "—" }),
5451
- /* @__PURE__ */ jsx("div", { className: "text-xs leading-3 text-muted-foreground", children: hasAnyWallet && balance > 0 && usdValue > 0 ? formatUsd(usdValue) : "—" })
6153
+ /* @__PURE__ */ jsx("div", { className: "text-xs leading-3 text-muted-foreground/50", children: hasAnyWallet && balance > 0 && usdValue > 0 ? formatUsd(usdValue) : "—" })
5452
6154
  ] }) })
5453
6155
  ]
5454
6156
  }
@@ -5591,8 +6293,8 @@ const TokenSelectModal = ({
5591
6293
  [groupedTokens.willChangeSrcChain]
5592
6294
  );
5593
6295
  const hasNoResults = tokensToRender.length === 0 && willChangeSrcTokens.length === 0;
5594
- 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") }) }),
6296
+ return /* @__PURE__ */ jsx(Dialog, { open: isOpen, onOpenChange: (open) => !open && handleClose(), children: /* @__PURE__ */ jsxs(DialogContent, { className: "md:max-h-[90dvh] md:h-[90dvh] overflow-hidden flex flex-col", children: [
6297
+ /* @__PURE__ */ jsx(DialogHeader, { className: "text-left", children: /* @__PURE__ */ jsx(DialogTitle, { children: t("bridge.selectToken") }) }),
5596
6298
  /* @__PURE__ */ jsx(
5597
6299
  SearchInput,
5598
6300
  {
@@ -5605,24 +6307,26 @@ const TokenSelectModal = ({
5605
6307
  /* @__PURE__ */ jsx(
5606
6308
  Button,
5607
6309
  {
5608
- variant: tab === "my" ? "default" : "outline",
6310
+ variant: tab === "my" ? "default" : "ghost",
5609
6311
  onClick: () => setTab("my"),
5610
6312
  size: "sm",
6313
+ className: cn(tab !== "my" && "bg-muted hover:bg-accent"),
5611
6314
  children: t("bridge.myTokens")
5612
6315
  }
5613
6316
  ),
5614
6317
  /* @__PURE__ */ jsx(
5615
6318
  Button,
5616
6319
  {
5617
- variant: tab === "all" ? "default" : "outline",
6320
+ variant: tab === "all" ? "default" : "ghost",
5618
6321
  onClick: () => setTab("all"),
5619
6322
  size: "sm",
6323
+ className: cn(tab !== "all" && "bg-muted hover:bg-accent"),
5620
6324
  children: t("bridge.allTokens")
5621
6325
  }
5622
6326
  )
5623
6327
  ] }),
5624
- /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto -mx-3", children: hasNoResults ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground px-5 py-4", children: t("bridge.noResults") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
5625
- effectiveTab === "my" && myTokens.length === 0 && /* @__PURE__ */ jsx("p", { className: "leading-4 text-base font-semibold text-muted-foreground uppercase px-5 py-2", children: t("bridge.noBalancesFound") }),
6328
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto px-1", children: hasNoResults ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground px-5 py-4", children: t("bridge.noResults") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
6329
+ effectiveTab === "my" && myTokens.length === 0 && /* @__PURE__ */ jsx("p", { className: "leading-4 text-base font-semibold text-muted-foreground uppercase py-2", children: t("bridge.noBalancesFound") }),
5626
6330
  tokensToRender.map(({ token, willChangeSrc }) => {
5627
6331
  const bal = getBalance(token.symbol);
5628
6332
  const usd = getTokenUsdValue(token.symbol, token.price?.usd);
@@ -5673,6 +6377,7 @@ const TokenSelectModal = ({
5673
6377
  function useBridgeRefresh() {
5674
6378
  const qc = useQueryClient();
5675
6379
  const { srcAddress, dstAddress } = useAddresses();
6380
+ const { fromChain, toChain } = useChainsStore();
5676
6381
  const { setLoading: setQLoading, triggerRefetch } = useBridgeQuoteStore();
5677
6382
  const { hasAnyWallet } = useConnectedWalletsStore();
5678
6383
  const [spinning, setSpinning] = useState(false);
@@ -5685,14 +6390,30 @@ function useBridgeRefresh() {
5685
6390
  qc.invalidateQueries({ queryKey: ["chains"] })
5686
6391
  ];
5687
6392
  if (hasAnyWallet()) {
5688
- if (srcAddress)
6393
+ if (srcAddress) {
5689
6394
  queries.push(
5690
6395
  qc.invalidateQueries({ queryKey: ["srcTokens", srcAddress] })
5691
6396
  );
5692
- if (dstAddress)
6397
+ if (fromChain?.chainKey) {
6398
+ queries.push(
6399
+ qc.invalidateQueries({
6400
+ queryKey: ["balances", fromChain.chainKey, srcAddress]
6401
+ })
6402
+ );
6403
+ }
6404
+ }
6405
+ if (dstAddress) {
5693
6406
  queries.push(
5694
6407
  qc.invalidateQueries({ queryKey: ["dstTokens", dstAddress] })
5695
6408
  );
6409
+ if (toChain?.chainKey) {
6410
+ queries.push(
6411
+ qc.invalidateQueries({
6412
+ queryKey: ["balances", toChain.chainKey, dstAddress]
6413
+ })
6414
+ );
6415
+ }
6416
+ }
5696
6417
  }
5697
6418
  await Promise.all(queries);
5698
6419
  triggerRefetch();
@@ -5703,6 +6424,8 @@ function useBridgeRefresh() {
5703
6424
  qc,
5704
6425
  srcAddress,
5705
6426
  dstAddress,
6427
+ fromChain?.chainKey,
6428
+ toChain?.chainKey,
5706
6429
  hasAnyWallet,
5707
6430
  setQLoading,
5708
6431
  triggerRefetch
@@ -5722,7 +6445,7 @@ const RefreshButton = () => {
5722
6445
  onClick: handleRefresh,
5723
6446
  disabled: spinning,
5724
6447
  variant: "secondary",
5725
- className: "bg-card text-card-foreground hover:text-card-foreground h-9",
6448
+ className: "h-9 w-11",
5726
6449
  size: "sm",
5727
6450
  children: /* @__PURE__ */ jsx(
5728
6451
  ReloadIcon,
@@ -5750,7 +6473,7 @@ const SelectTokenButton = ({
5750
6473
  onClick,
5751
6474
  size: "sm",
5752
6475
  variant: "secondary",
5753
- className: "shrink-0 gap-2 bg-card text-card-foreground hover:text-card-foreground h-9",
6476
+ className: "shrink-0 gap-2 h-9 !pl-2",
5754
6477
  type: "button",
5755
6478
  "aria-label": label,
5756
6479
  children: [
@@ -5798,7 +6521,7 @@ const Toolbar = () => {
5798
6521
  {
5799
6522
  onClick: onOpenSettings,
5800
6523
  size: "sm",
5801
- className: "bg-card text-card-foreground hover:text-card-foreground h-9",
6524
+ className: "h-9 w-11",
5802
6525
  variant: "secondary",
5803
6526
  children: /* @__PURE__ */ jsx(BoltIcon, { stroke: "currentColor" })
5804
6527
  }
@@ -5816,7 +6539,7 @@ const Toolbar = () => {
5816
6539
  }
5817
6540
  }
5818
6541
  ),
5819
- /* @__PURE__ */ jsx(SettingModal, { isOpen: isOpenSettings, onClose: onCloseSettings })
6542
+ /* @__PURE__ */ jsx(SettingsModal, { isOpen: isOpenSettings, onClose: onCloseSettings })
5820
6543
  ] });
5821
6544
  };
5822
6545
  const EvaaBridgeWithProviders = (props) => {
@@ -6079,10 +6802,16 @@ export {
6079
6802
  EvaaBridge,
6080
6803
  RoutePriority,
6081
6804
  RouteType,
6805
+ addEvmNetworkFee,
6806
+ addTonNetworkFee,
6807
+ addTronNetworkFee,
6082
6808
  addrForApi,
6083
6809
  buildAssetMatrix,
6084
6810
  calculateMinReceived,
6085
6811
  computeFeesUsdFromArray,
6812
+ estimateEvmNetworkFee,
6813
+ estimateTonNetworkFee,
6814
+ estimateTronNetworkFee,
6086
6815
  findNativeMeta,
6087
6816
  formatAddress,
6088
6817
  formatBalance,