@rash2x/bridge-widget 0.2.11 → 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.
- package/LICENSE +2 -0
- package/README.md +103 -138
- package/dist/evaa-bridge.cjs +1378 -853
- package/dist/evaa-bridge.cjs.map +1 -1
- package/dist/evaa-bridge.mjs +1380 -855
- package/dist/evaa-bridge.mjs.map +1 -1
- package/dist/index.d.ts +58 -4
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/dist/assets/fonts/Gilroy-Black.ttf +0 -0
- package/dist/assets/fonts/Gilroy-Bold.ttf +0 -0
- package/dist/assets/fonts/Gilroy-ExtraBold.ttf +0 -0
- package/dist/assets/fonts/Gilroy-Heavy.ttf +0 -0
- package/dist/assets/fonts/Gilroy-Light.ttf +0 -0
- package/dist/assets/fonts/Gilroy-Medium.ttf +0 -0
- package/dist/assets/fonts/Gilroy-Regular.ttf +0 -0
- package/dist/assets/fonts/Gilroy-SemiBold.ttf +0 -0
- package/dist/assets/fonts/Gilroy-Thin.ttf +0 -0
- package/dist/assets/fonts/Gilroy-UltraLight.ttf +0 -0
- package/dist/assets/fonts/fonts.css +0 -81
- package/dist/assets/fonts/hanson-bold.woff +0 -0
- package/dist/assets/fonts/hanson-bold.woff2 +0 -0
package/dist/evaa-bridge.mjs
CHANGED
|
@@ -2,15 +2,16 @@ 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,
|
|
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
|
|
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
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";
|
|
@@ -21,7 +22,6 @@ 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
|
-
import { TonClient, Address as Address$1, beginCell } from "@ton/ton";
|
|
25
25
|
import { toast, Toaster } from "sonner";
|
|
26
26
|
import { DialogDescription as DialogDescription$1 } from "@radix-ui/react-dialog";
|
|
27
27
|
import { BrowserProvider, Contract, parseUnits } from "ethers";
|
|
@@ -30,8 +30,8 @@ import { TronLinkAdapterName } from "@tronweb3/tronwallet-adapters";
|
|
|
30
30
|
import { CardHeader, CardTitle, CardAction, Card, CardContent, CardFooter } from "@/components/ui/card";
|
|
31
31
|
import { Badge } from "@/components/ui/badge";
|
|
32
32
|
const common$1 = { "connecting": "Connecting…", "initializing": "Initializing...", "loading": "Loading...", "paste": "paste", "close": "Close", "zeroPlaceholder": "0", "nativeToken": "Native Token" };
|
|
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", "
|
|
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
|
|
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" };
|
|
35
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" };
|
|
36
36
|
const app$1 = { "stargateWidgetName": "Stargate Bridge Widget", "liveWidget": "Live Widget", "getStarted": "Get Started" };
|
|
37
37
|
const settings$1 = { "title": "Settings", "gasOnDestination": "Gas on destination", "slippageTolerance": "Slippage tolerance", "routePriority": "Route Priority", "highSlippageWarning": "High slippage warning", "gasPresets": { "auto": "Auto", "none": "None", "medium": "Medium", "max": "Max" }, "routePresets": { "fastest": "Fastest", "cheapest": "Cheapest", "recommended": "Recommended" } };
|
|
@@ -46,8 +46,8 @@ const en = {
|
|
|
46
46
|
errors: errors$1
|
|
47
47
|
};
|
|
48
48
|
const common = { "connecting": "Подключение…", "initializing": "Инициализация...", "loading": "Загрузка...", "paste": "вставить", "close": "Закрыть", "zeroPlaceholder": "0", "nativeToken": "Нативный токен" };
|
|
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", "
|
|
50
|
-
const bridge = { "max": "Макс", "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": "Проверьте корректность перед переводом" };
|
|
51
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": "Ошибка котировки" };
|
|
52
52
|
const app = { "stargateWidgetName": "Виджет Stargate Bridge", "liveWidget": "Живой виджет", "getStarted": "Начало работы" };
|
|
53
53
|
const settings = { "title": "Настройки", "gasOnDestination": "Газ на назначении", "slippageTolerance": "Толерантность к проскальзыванию", "routePriority": "Приоритет маршрута", "highSlippageWarning": "Высокое проскальзывание", "gasPresets": { "auto": "Авто", "none": "Нет", "medium": "Средний", "max": "Макс" }, "routePresets": { "fastest": "Быстрейший", "cheapest": "Дешевейший", "recommended": "Рекомендуемый" } };
|
|
@@ -98,72 +98,282 @@ function BridgeI18nProvider({
|
|
|
98
98
|
function useBridgeTranslation() {
|
|
99
99
|
return useTranslation("evaa-bridge", { i18n: bridgeI18n });
|
|
100
100
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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);
|
|
116
120
|
}
|
|
117
|
-
return
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (!prev || !data) return false;
|
|
137
|
-
if (prev.length !== data.length) return false;
|
|
138
|
-
for (let i = 0; i < prev.length; i++) {
|
|
139
|
-
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;
|
|
140
140
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (!prev && !data) return true;
|
|
150
|
-
if (!prev || !data) return false;
|
|
151
|
-
if (prev.length !== data.length) return false;
|
|
152
|
-
for (let i = 0; i < prev.length; i++) {
|
|
153
|
-
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);
|
|
154
149
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
+
}
|
|
167
377
|
const POPULAR_ORDER = [
|
|
168
378
|
"USDT",
|
|
169
379
|
"USDC",
|
|
@@ -199,6 +409,16 @@ function buildAssetMatrix(tokens) {
|
|
|
199
409
|
if (!symbol) continue;
|
|
200
410
|
(m[symbol] || (m[symbol] = {}))[t.chainKey] = t;
|
|
201
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
|
+
);
|
|
202
422
|
return m;
|
|
203
423
|
}
|
|
204
424
|
function listAssetsForSelect(tokens) {
|
|
@@ -208,11 +428,11 @@ function listAssetsForSelect(tokens) {
|
|
|
208
428
|
const preferred = pickOrder.map((k) => byChain[k]).find(Boolean) ?? Object.values(byChain)[0];
|
|
209
429
|
return preferred;
|
|
210
430
|
});
|
|
211
|
-
const byNorm = new Map(base.map((e) => [
|
|
431
|
+
const byNorm = new Map(base.map((e) => [normalizeTickerSymbol$1(e.symbol), e]));
|
|
212
432
|
const used = /* @__PURE__ */ new Set();
|
|
213
433
|
const popular = [];
|
|
214
434
|
for (const s of POPULAR_ORDER) {
|
|
215
|
-
const key =
|
|
435
|
+
const key = normalizeTickerSymbol$1(s);
|
|
216
436
|
const item = byNorm.get(key);
|
|
217
437
|
if (item && !used.has(key)) {
|
|
218
438
|
popular.push(item);
|
|
@@ -220,11 +440,11 @@ function listAssetsForSelect(tokens) {
|
|
|
220
440
|
}
|
|
221
441
|
}
|
|
222
442
|
const stable = base.filter((e) => {
|
|
223
|
-
const k =
|
|
443
|
+
const k = normalizeTickerSymbol$1(e.symbol);
|
|
224
444
|
return !used.has(k) && STABLES.has(k);
|
|
225
445
|
}).sort((a, b) => a.symbol.localeCompare(b.symbol));
|
|
226
|
-
for (const e of stable) used.add(
|
|
227
|
-
const other = base.filter((e) => !used.has(
|
|
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));
|
|
228
448
|
return [...popular, ...stable, ...other];
|
|
229
449
|
}
|
|
230
450
|
function resolveTokenOnChain(tokens, assetSymbol, chainKey) {
|
|
@@ -629,163 +849,636 @@ function isAddressValidForChain(chainKey, addr) {
|
|
|
629
849
|
if (chainKey === "tron") return isTronAddress(addr);
|
|
630
850
|
return isEvmAddress(addr);
|
|
631
851
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
refetchTrigger,
|
|
640
|
-
setLoading: setQuoteLoading,
|
|
641
|
-
setQuote,
|
|
642
|
-
setError: setQError,
|
|
643
|
-
setNoRoute,
|
|
644
|
-
resetWithIdle
|
|
645
|
-
} = useBridgeQuoteStore();
|
|
646
|
-
const input = inputAmount;
|
|
647
|
-
const srcTokenOnFrom = useMemo(
|
|
648
|
-
() => resolveTokenOnChainFromMatrix$2(
|
|
649
|
-
assetMatrix,
|
|
650
|
-
selectedAssetSymbol,
|
|
651
|
-
fromChain?.chainKey
|
|
652
|
-
),
|
|
653
|
-
[assetMatrix, selectedAssetSymbol, fromChain?.chainKey]
|
|
654
|
-
);
|
|
655
|
-
const dstTokenOnTo = useMemo(
|
|
656
|
-
() => resolveTokenOnChainFromMatrix$2(
|
|
657
|
-
assetMatrix,
|
|
658
|
-
selectedAssetSymbol,
|
|
659
|
-
toChain?.chainKey
|
|
660
|
-
),
|
|
661
|
-
[assetMatrix, selectedAssetSymbol, toChain?.chainKey]
|
|
662
|
-
);
|
|
663
|
-
const [loading, setLoading] = useState(false);
|
|
664
|
-
useEffect(() => {
|
|
665
|
-
if (!input || input === "") {
|
|
666
|
-
setLoading(false);
|
|
667
|
-
resetWithIdle();
|
|
668
|
-
}
|
|
669
|
-
}, [input, resetWithIdle]);
|
|
670
|
-
useEffect(() => {
|
|
671
|
-
const resetUi = (withError) => {
|
|
672
|
-
setLoading(false);
|
|
673
|
-
{
|
|
674
|
-
resetWithIdle();
|
|
675
|
-
}
|
|
676
|
-
};
|
|
677
|
-
if (!input || Number(input) <= 0 || !fromChain?.chainKey || !toChain?.chainKey || !srcTokenOnFrom || !dstTokenOnTo || !srcAddress || !dstAddress) {
|
|
678
|
-
resetUi();
|
|
679
|
-
return;
|
|
680
|
-
}
|
|
681
|
-
if (!isAddressValidForChain(fromChain.chainKey, srcAddress) || !isAddressValidForChain(toChain.chainKey, dstAddress)) {
|
|
682
|
-
resetUi();
|
|
683
|
-
return;
|
|
684
|
-
}
|
|
685
|
-
if (fromChain.chainKey === toChain.chainKey) {
|
|
686
|
-
resetUi();
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
|
-
let cancelled = false;
|
|
690
|
-
const run = async () => {
|
|
691
|
-
try {
|
|
692
|
-
setLoading(true);
|
|
693
|
-
setQuoteLoading?.();
|
|
694
|
-
if (assetMatrix && selectedAssetSymbol) {
|
|
695
|
-
const tokenMatrix = assetMatrix[selectedAssetSymbol.toUpperCase()];
|
|
696
|
-
if (!tokenMatrix || !tokenMatrix[fromChain.chainKey] || !tokenMatrix[toChain.chainKey]) {
|
|
697
|
-
if (!cancelled) {
|
|
698
|
-
resetUi();
|
|
699
|
-
}
|
|
700
|
-
return;
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
const srcAmountLD = toLD(input, srcTokenOnFrom.decimals);
|
|
704
|
-
const srcAddrApi = addrForApi(fromChain.chainKey, srcAddress);
|
|
705
|
-
const dstAddrApi = addrForApi(toChain.chainKey, dstAddress);
|
|
706
|
-
const dstNativeAmount = getDstNativeAmount(toChain.chainKey);
|
|
707
|
-
const slippageDecimal = getSlippageDecimal();
|
|
708
|
-
const approximateMinLD = BigInt(srcAmountLD) * BigInt(9500) / BigInt(1e4);
|
|
709
|
-
const quoteRoute = await getQuotesByPriority({
|
|
710
|
-
srcChainKey: fromChain.chainKey,
|
|
711
|
-
dstChainKey: toChain.chainKey,
|
|
712
|
-
srcToken: srcTokenOnFrom.address,
|
|
713
|
-
dstToken: dstTokenOnTo.address,
|
|
714
|
-
srcAmountLD,
|
|
715
|
-
dstAmountMinLD: approximateMinLD.toString(),
|
|
716
|
-
srcAddress: srcAddrApi,
|
|
717
|
-
dstAddress: dstAddrApi,
|
|
718
|
-
dstNativeAmount,
|
|
719
|
-
slippage: slippageDecimal,
|
|
720
|
-
routePriority
|
|
721
|
-
});
|
|
722
|
-
if (quoteRoute?.route === null) {
|
|
723
|
-
setNoRoute(true);
|
|
724
|
-
if (!cancelled) resetUi();
|
|
725
|
-
return;
|
|
726
|
-
} else {
|
|
727
|
-
setNoRoute(false);
|
|
728
|
-
}
|
|
729
|
-
if (!quoteRoute || !quoteRoute.dstAmount || BigInt(quoteRoute.dstAmount) === BigInt(0)) {
|
|
730
|
-
if (!cancelled) resetUi();
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
if (cancelled) return;
|
|
734
|
-
setQuote(quoteRoute);
|
|
735
|
-
} catch {
|
|
736
|
-
if (!cancelled) {
|
|
737
|
-
resetUi();
|
|
738
|
-
}
|
|
739
|
-
} finally {
|
|
740
|
-
if (!cancelled) setLoading(false);
|
|
741
|
-
}
|
|
742
|
-
};
|
|
743
|
-
run();
|
|
744
|
-
return () => {
|
|
745
|
-
cancelled = true;
|
|
746
|
-
};
|
|
747
|
-
}, [
|
|
748
|
-
input,
|
|
749
|
-
fromChain?.chainKey,
|
|
750
|
-
toChain?.chainKey,
|
|
751
|
-
srcTokenOnFrom,
|
|
752
|
-
dstTokenOnTo,
|
|
753
|
-
srcAddress,
|
|
754
|
-
dstAddress,
|
|
755
|
-
setQuoteLoading,
|
|
756
|
-
setQuote,
|
|
757
|
-
setQError,
|
|
758
|
-
slippageBps,
|
|
759
|
-
routePriority,
|
|
760
|
-
refetchTrigger
|
|
761
|
-
]);
|
|
762
|
-
return { loading };
|
|
763
|
-
}
|
|
764
|
-
async function getChains() {
|
|
765
|
-
const res = await fetch("https://stargate.finance/api/v1/chains", {
|
|
766
|
-
credentials: "same-origin"
|
|
767
|
-
});
|
|
768
|
-
if (!res.ok) {
|
|
769
|
-
throw new Error(`Failed to load chains: ${res.status}`);
|
|
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
|
+
);
|
|
770
859
|
}
|
|
771
|
-
|
|
772
|
-
return Array.isArray(data) ? data : data.chains ?? [];
|
|
860
|
+
return context;
|
|
773
861
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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}`;
|
|
780
872
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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
|
+
}
|
|
1241
|
+
function useBridgeQuote() {
|
|
1242
|
+
const { assetMatrix, selectedAssetSymbol } = useTokensStore();
|
|
1243
|
+
const { fromChain, toChain, chains } = useChainsStore();
|
|
1244
|
+
const { srcAddress, dstAddress } = useAddresses();
|
|
1245
|
+
const { slippageBps, routePriority, getDstNativeAmount, getSlippageDecimal } = useSettingsStore();
|
|
1246
|
+
const {
|
|
1247
|
+
inputAmount,
|
|
1248
|
+
refetchTrigger,
|
|
1249
|
+
setLoading: setQuoteLoading,
|
|
1250
|
+
setQuote,
|
|
1251
|
+
setError: setQError,
|
|
1252
|
+
setNoRoute,
|
|
1253
|
+
resetWithIdle
|
|
1254
|
+
} = useBridgeQuoteStore();
|
|
1255
|
+
const { chainRegistry } = useChainStrategies();
|
|
1256
|
+
const publicClient = usePublicClient();
|
|
1257
|
+
const input = inputAmount;
|
|
1258
|
+
const srcTokenOnFrom = useMemo(
|
|
1259
|
+
() => resolveTokenOnChainFromMatrix$2(
|
|
1260
|
+
assetMatrix,
|
|
1261
|
+
selectedAssetSymbol,
|
|
1262
|
+
fromChain?.chainKey
|
|
1263
|
+
),
|
|
1264
|
+
[assetMatrix, selectedAssetSymbol, fromChain?.chainKey]
|
|
1265
|
+
);
|
|
1266
|
+
const dstTokenOnTo = useMemo(
|
|
1267
|
+
() => resolveTokenOnChainFromMatrix$2(
|
|
1268
|
+
assetMatrix,
|
|
1269
|
+
selectedAssetSymbol,
|
|
1270
|
+
toChain?.chainKey
|
|
1271
|
+
),
|
|
1272
|
+
[assetMatrix, selectedAssetSymbol, toChain?.chainKey]
|
|
1273
|
+
);
|
|
1274
|
+
const [loading, setLoading] = useState(false);
|
|
1275
|
+
useEffect(() => {
|
|
1276
|
+
if (!input || input === "") {
|
|
1277
|
+
setLoading(false);
|
|
1278
|
+
resetWithIdle();
|
|
1279
|
+
}
|
|
1280
|
+
}, [input, resetWithIdle]);
|
|
1281
|
+
useEffect(() => {
|
|
1282
|
+
const resetUi = (withError) => {
|
|
1283
|
+
setLoading(false);
|
|
1284
|
+
{
|
|
1285
|
+
resetWithIdle();
|
|
1286
|
+
}
|
|
1287
|
+
};
|
|
1288
|
+
if (!input || Number(input) <= 0 || !fromChain?.chainKey || !toChain?.chainKey || !srcTokenOnFrom || !dstTokenOnTo || !srcAddress || !dstAddress) {
|
|
1289
|
+
resetUi();
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
if (!isAddressValidForChain(fromChain.chainKey, srcAddress) || !isAddressValidForChain(toChain.chainKey, dstAddress)) {
|
|
1293
|
+
resetUi();
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
if (fromChain.chainKey === toChain.chainKey) {
|
|
1297
|
+
resetUi();
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
let cancelled = false;
|
|
1301
|
+
const run = async () => {
|
|
1302
|
+
try {
|
|
1303
|
+
setLoading(true);
|
|
1304
|
+
setQuoteLoading?.();
|
|
1305
|
+
if (assetMatrix && selectedAssetSymbol) {
|
|
1306
|
+
const tokenMatrix = assetMatrix[selectedAssetSymbol.toUpperCase()];
|
|
1307
|
+
if (!tokenMatrix || !tokenMatrix[fromChain.chainKey] || !tokenMatrix[toChain.chainKey]) {
|
|
1308
|
+
if (!cancelled) {
|
|
1309
|
+
resetUi();
|
|
1310
|
+
}
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
const srcAmountLD = toLD(input, srcTokenOnFrom.decimals);
|
|
1315
|
+
const srcAddrApi = addrForApi(fromChain.chainKey, srcAddress);
|
|
1316
|
+
const dstAddrApi = addrForApi(toChain.chainKey, dstAddress);
|
|
1317
|
+
const dstNativeAmount = getDstNativeAmount(toChain.chainKey);
|
|
1318
|
+
const slippageDecimal = getSlippageDecimal();
|
|
1319
|
+
const approximateMinLD = BigInt(srcAmountLD) * BigInt(9500) / BigInt(1e4);
|
|
1320
|
+
const quoteRoute = await getQuotesByPriority({
|
|
1321
|
+
srcChainKey: fromChain.chainKey,
|
|
1322
|
+
dstChainKey: toChain.chainKey,
|
|
1323
|
+
srcToken: srcTokenOnFrom.address,
|
|
1324
|
+
dstToken: dstTokenOnTo.address,
|
|
1325
|
+
srcAmountLD,
|
|
1326
|
+
dstAmountMinLD: approximateMinLD.toString(),
|
|
1327
|
+
srcAddress: srcAddrApi,
|
|
1328
|
+
dstAddress: dstAddrApi,
|
|
1329
|
+
dstNativeAmount,
|
|
1330
|
+
slippage: slippageDecimal,
|
|
1331
|
+
routePriority
|
|
1332
|
+
});
|
|
1333
|
+
if (quoteRoute?.route === null) {
|
|
1334
|
+
setNoRoute(true);
|
|
1335
|
+
if (!cancelled) resetUi();
|
|
1336
|
+
return;
|
|
1337
|
+
} else {
|
|
1338
|
+
setNoRoute(false);
|
|
1339
|
+
}
|
|
1340
|
+
if (!quoteRoute || !quoteRoute.dstAmount || BigInt(quoteRoute.dstAmount) === BigInt(0)) {
|
|
1341
|
+
if (!cancelled) resetUi();
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
if (cancelled) return;
|
|
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);
|
|
1424
|
+
} catch {
|
|
1425
|
+
if (!cancelled) {
|
|
1426
|
+
resetUi();
|
|
1427
|
+
}
|
|
1428
|
+
} finally {
|
|
1429
|
+
if (!cancelled) setLoading(false);
|
|
1430
|
+
}
|
|
1431
|
+
};
|
|
1432
|
+
run();
|
|
1433
|
+
return () => {
|
|
1434
|
+
cancelled = true;
|
|
1435
|
+
};
|
|
1436
|
+
}, [
|
|
1437
|
+
input,
|
|
1438
|
+
fromChain?.chainKey,
|
|
1439
|
+
toChain?.chainKey,
|
|
1440
|
+
srcTokenOnFrom,
|
|
1441
|
+
dstTokenOnTo,
|
|
1442
|
+
srcAddress,
|
|
1443
|
+
dstAddress,
|
|
1444
|
+
setQuoteLoading,
|
|
1445
|
+
setQuote,
|
|
1446
|
+
setQError,
|
|
1447
|
+
slippageBps,
|
|
1448
|
+
routePriority,
|
|
1449
|
+
refetchTrigger,
|
|
1450
|
+
chainRegistry,
|
|
1451
|
+
chains,
|
|
1452
|
+
publicClient
|
|
1453
|
+
]);
|
|
1454
|
+
return { loading };
|
|
1455
|
+
}
|
|
1456
|
+
async function getChains() {
|
|
1457
|
+
const res = await fetch("https://stargate.finance/api/v1/chains", {
|
|
1458
|
+
credentials: "same-origin"
|
|
1459
|
+
});
|
|
1460
|
+
if (!res.ok) {
|
|
1461
|
+
throw new Error(`Failed to load chains: ${res.status}`);
|
|
1462
|
+
}
|
|
1463
|
+
const data = await res.json();
|
|
1464
|
+
return Array.isArray(data) ? data : data.chains ?? [];
|
|
1465
|
+
}
|
|
1466
|
+
async function getTokens() {
|
|
1467
|
+
const res = await fetch("https://stargate.finance/api/v1/tokens", {
|
|
1468
|
+
credentials: "same-origin"
|
|
1469
|
+
});
|
|
1470
|
+
if (!res.ok) {
|
|
1471
|
+
throw new Error(`Failed to load chains: ${res.status}`);
|
|
1472
|
+
}
|
|
1473
|
+
const data = await res.json();
|
|
1474
|
+
const tokens = Array.isArray(data) ? data : data.tokens ?? [];
|
|
1475
|
+
return tokens.map(normalizeTokenSymbol);
|
|
1476
|
+
}
|
|
1477
|
+
async function getDestTokens(srcChainKey, srcTokenAddr) {
|
|
1478
|
+
const url = new URL("https://stargate.finance/api/v1/tokens");
|
|
1479
|
+
url.searchParams.set("srcChainKey", srcChainKey);
|
|
1480
|
+
url.searchParams.set("srcToken", srcTokenAddr);
|
|
1481
|
+
const res = await fetch(url.toString(), { credentials: "omit" });
|
|
789
1482
|
if (!res.ok) throw new Error(`Failed to load dest tokens: ${res.status}`);
|
|
790
1483
|
const data = await res.json();
|
|
791
1484
|
const raw = data.tokens ?? [];
|
|
@@ -806,10 +1499,7 @@ async function getDestTokens(srcChainKey, srcTokenAddr) {
|
|
|
806
1499
|
if (sa === sb) return (a.name ?? "").localeCompare(b.name ?? "");
|
|
807
1500
|
return sa.localeCompare(sb);
|
|
808
1501
|
});
|
|
809
|
-
return unique;
|
|
810
|
-
}
|
|
811
|
-
function normalizeTickerSymbol$1(s) {
|
|
812
|
-
return s.toUpperCase().replace(/₮/g, "T").replace(/[^A-Z0-9]/g, "");
|
|
1502
|
+
return unique.map(normalizeTokenSymbol);
|
|
813
1503
|
}
|
|
814
1504
|
function resolveTokenOnChainFromMatrix$1(assetMatrix, assetSymbol, chainKey) {
|
|
815
1505
|
if (!assetMatrix || !assetSymbol || !chainKey) return void 0;
|
|
@@ -836,7 +1526,14 @@ const useChainDerivations = () => {
|
|
|
836
1526
|
return [];
|
|
837
1527
|
const byChain = assetMatrix[selectedAssetSymbol.toUpperCase()];
|
|
838
1528
|
const keys = new Set(Object.keys(byChain ?? {}));
|
|
839
|
-
|
|
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;
|
|
840
1537
|
}, [selectedAssetSymbol, assetMatrix, chains]);
|
|
841
1538
|
useEffect(() => {
|
|
842
1539
|
setAllowedFromChains(supportedFrom);
|
|
@@ -904,6 +1601,13 @@ const useChainDerivations = () => {
|
|
|
904
1601
|
if (fromChain?.chainKey) {
|
|
905
1602
|
list = list.filter((c) => c.chainKey !== fromChain.chainKey);
|
|
906
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
|
+
);
|
|
907
1611
|
setAllowedToChains(list);
|
|
908
1612
|
if (!toChain || !list.some((c) => c.chainKey === toChain.chainKey)) {
|
|
909
1613
|
setToChain(list[0]);
|
|
@@ -938,16 +1642,6 @@ const useChainDerivations = () => {
|
|
|
938
1642
|
isLoadingToChains
|
|
939
1643
|
};
|
|
940
1644
|
};
|
|
941
|
-
const ChainStrategyContext = createContext(void 0);
|
|
942
|
-
function useChainStrategies() {
|
|
943
|
-
const context = useContext(ChainStrategyContext);
|
|
944
|
-
if (!context) {
|
|
945
|
-
throw new Error(
|
|
946
|
-
"useChainStrategies must be used within ChainStrategyProvider"
|
|
947
|
-
);
|
|
948
|
-
}
|
|
949
|
-
return context;
|
|
950
|
-
}
|
|
951
1645
|
function useBalances(chainKey, address, priorityTokenSymbol) {
|
|
952
1646
|
const { chainRegistry } = useChainStrategies();
|
|
953
1647
|
const { assetMatrix } = useTokensStore();
|
|
@@ -1311,7 +2005,7 @@ const StargateIcon = (props) => {
|
|
|
1311
2005
|
"path",
|
|
1312
2006
|
{
|
|
1313
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",
|
|
1314
|
-
fill: "
|
|
2008
|
+
fill: "currentColor"
|
|
1315
2009
|
}
|
|
1316
2010
|
)
|
|
1317
2011
|
] }),
|
|
@@ -1447,7 +2141,7 @@ const WalletConnectIcon = (props) => {
|
|
|
1447
2141
|
}
|
|
1448
2142
|
);
|
|
1449
2143
|
};
|
|
1450
|
-
const
|
|
2144
|
+
const TonConnectIcon = (props) => {
|
|
1451
2145
|
return /* @__PURE__ */ jsxs(
|
|
1452
2146
|
"svg",
|
|
1453
2147
|
{
|
|
@@ -1637,7 +2331,7 @@ const SelectNetworkButton = ({
|
|
|
1637
2331
|
size: "sm",
|
|
1638
2332
|
variant: "secondary",
|
|
1639
2333
|
type: "button",
|
|
1640
|
-
className: "shrink-0 gap-2
|
|
2334
|
+
className: "shrink-0 gap-2 h-9 !pl-2",
|
|
1641
2335
|
"aria-label": label,
|
|
1642
2336
|
children: [
|
|
1643
2337
|
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center", children: [
|
|
@@ -1728,49 +2422,37 @@ const SearchInput = ({
|
|
|
1728
2422
|
placeholder,
|
|
1729
2423
|
value,
|
|
1730
2424
|
onChange,
|
|
1731
|
-
className
|
|
1732
|
-
containerClassName
|
|
2425
|
+
className
|
|
1733
2426
|
}) => {
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
className: cn(
|
|
1750
|
-
"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",
|
|
1751
|
-
className
|
|
1752
|
-
),
|
|
1753
|
-
value,
|
|
1754
|
-
onChange: (e) => onChange(e.target.value),
|
|
1755
|
-
onFocus: () => setIsFocused(true),
|
|
1756
|
-
onBlur: () => setIsFocused(false)
|
|
1757
|
-
}
|
|
1758
|
-
)
|
|
1759
|
-
]
|
|
1760
|
-
}
|
|
1761
|
-
);
|
|
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
|
+
] });
|
|
1762
2442
|
};
|
|
1763
2443
|
const ChainSelectModal = ({
|
|
1764
2444
|
isOpen,
|
|
1765
2445
|
onClose,
|
|
1766
2446
|
items,
|
|
1767
2447
|
allowedItems,
|
|
1768
|
-
onChangeChain
|
|
2448
|
+
onChangeChain,
|
|
2449
|
+
isSource
|
|
1769
2450
|
}) => {
|
|
1770
2451
|
const { t } = useBridgeTranslation();
|
|
1771
2452
|
const [query, setQuery] = useState("");
|
|
1772
2453
|
const { setFromChain, chains, fromChain, toChain } = useChainsStore();
|
|
1773
|
-
const { assetMatrix, selectedAssetSymbol } = useTokensStore();
|
|
2454
|
+
const { assetMatrix, selectedAssetSymbol, setSelectedAssetSymbol } = useTokensStore();
|
|
2455
|
+
console.log("allowedItems", allowedItems);
|
|
1774
2456
|
const handleClose = useCallback(() => {
|
|
1775
2457
|
setQuery("");
|
|
1776
2458
|
onClose();
|
|
@@ -1799,40 +2481,84 @@ const ChainSelectModal = ({
|
|
|
1799
2481
|
},
|
|
1800
2482
|
[chains, selectedAssetSymbol, assetMatrix]
|
|
1801
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
|
+
);
|
|
1802
2514
|
const groupedChains = useMemo(() => {
|
|
1803
2515
|
const q = query.trim().toLowerCase();
|
|
1804
2516
|
const filtered = q ? (items ?? []).filter(
|
|
1805
2517
|
(c) => c.name.toLowerCase().includes(q) || c.chainKey.toLowerCase().includes(q)
|
|
1806
2518
|
) : items ?? [];
|
|
1807
|
-
const groups = { available: [], willChangeSrc: [] };
|
|
2519
|
+
const groups = { available: [], willChangeSrc: [], willChangeTokenAndSrc: [] };
|
|
1808
2520
|
for (const chain of filtered) {
|
|
1809
2521
|
const isAllowed = allowedItems?.some((c) => c.chainKey === chain.chainKey) || false;
|
|
1810
2522
|
if (isAllowed) {
|
|
1811
2523
|
groups.available.push(chain);
|
|
1812
2524
|
} else {
|
|
1813
2525
|
const compatibleSrc = findCompatibleSrcChain(chain);
|
|
1814
|
-
if (compatibleSrc)
|
|
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
|
+
}
|
|
1815
2534
|
}
|
|
1816
2535
|
}
|
|
1817
2536
|
groups.available.sort((a, b) => a.name.localeCompare(b.name));
|
|
1818
2537
|
groups.willChangeSrc.sort((a, b) => a.name.localeCompare(b.name));
|
|
2538
|
+
groups.willChangeTokenAndSrc.sort((a, b) => a.name.localeCompare(b.name));
|
|
1819
2539
|
return groups;
|
|
1820
|
-
}, [items, query, allowedItems, findCompatibleSrcChain]);
|
|
1821
|
-
const onChainPick = (chain, willChangeSrc = false) => {
|
|
1822
|
-
if (
|
|
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) {
|
|
1823
2549
|
const newSrcChain = findCompatibleSrcChain(chain);
|
|
1824
2550
|
if (newSrcChain && setFromChain) setFromChain(newSrcChain);
|
|
1825
2551
|
}
|
|
1826
2552
|
onChangeChain(chain);
|
|
1827
2553
|
handleClose();
|
|
1828
2554
|
};
|
|
1829
|
-
const renderChainItem = (chain, willChangeSrc) => {
|
|
1830
|
-
const isSelected = fromChain?.chainKey === chain.chainKey
|
|
2555
|
+
const renderChainItem = (chain, willChangeSrc, willChangeTokenAndSrc = false) => {
|
|
2556
|
+
const isSelected = isSource ? fromChain?.chainKey === chain.chainKey : toChain?.chainKey === chain.chainKey;
|
|
1831
2557
|
return /* @__PURE__ */ jsx(
|
|
1832
2558
|
Button,
|
|
1833
2559
|
{
|
|
1834
|
-
onClick: () => onChainPick(chain, willChangeSrc),
|
|
1835
|
-
className: `w-full cursor-pointer flex shadow-none rounded-
|
|
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" : ""}`,
|
|
1836
2562
|
children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1837
2563
|
/* @__PURE__ */ jsx(
|
|
1838
2564
|
NetworkSymbol,
|
|
@@ -1848,7 +2574,7 @@ const ChainSelectModal = ({
|
|
|
1848
2574
|
chain.chainKey
|
|
1849
2575
|
);
|
|
1850
2576
|
};
|
|
1851
|
-
return /* @__PURE__ */ jsx(Dialog, { open: isOpen, onOpenChange: (open) => !open && handleClose(), children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-h-[90dvh] h-[90dvh] overflow-hidden flex flex-col", children: [
|
|
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: [
|
|
1852
2578
|
/* @__PURE__ */ jsx(DialogHeader, { className: "text-left", children: /* @__PURE__ */ jsx(DialogTitle, { children: t("bridge.selectNetwork") }) }),
|
|
1853
2579
|
/* @__PURE__ */ jsx(
|
|
1854
2580
|
SearchInput,
|
|
@@ -1856,19 +2582,24 @@ const ChainSelectModal = ({
|
|
|
1856
2582
|
placeholder: t("bridge.searchDestinationChain"),
|
|
1857
2583
|
value: query,
|
|
1858
2584
|
onChange: setQuery,
|
|
1859
|
-
containerClassName: "rounded-md",
|
|
1860
2585
|
className: "text-foreground placeholder:text-muted-foreground"
|
|
1861
2586
|
}
|
|
1862
2587
|
),
|
|
1863
2588
|
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto", children: [
|
|
1864
2589
|
groupedChains.available.length > 0 && groupedChains.available.map((c) => renderChainItem(c, false)),
|
|
1865
|
-
groupedChains.
|
|
1866
|
-
|
|
1867
|
-
groupedChains.willChangeSrc.
|
|
1868
|
-
(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)
|
|
1869
2594
|
)
|
|
1870
2595
|
] }),
|
|
1871
|
-
groupedChains.
|
|
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") })
|
|
1872
2603
|
] })
|
|
1873
2604
|
] }) });
|
|
1874
2605
|
};
|
|
@@ -1878,52 +2609,10 @@ const useWalletSelectModal = create((set) => ({
|
|
|
1878
2609
|
onOpen: (addressType) => set({ isOpen: true, addressType }),
|
|
1879
2610
|
onClose: () => set({ isOpen: false, addressType: void 0 })
|
|
1880
2611
|
}));
|
|
1881
|
-
const truncateToDecimals = (num, decimals) => {
|
|
1882
|
-
if (!isFinite(num) || isNaN(num)) return "0.00";
|
|
1883
|
-
const multiplier = Math.pow(10, decimals);
|
|
1884
|
-
const truncated = Math.floor(num * multiplier) / multiplier;
|
|
1885
|
-
return truncated.toFixed(decimals);
|
|
1886
|
-
};
|
|
1887
|
-
const formatTokenAmount = (amount, symbol, options) => {
|
|
1888
|
-
const normalizedSymbol = (symbol ?? "").toUpperCase();
|
|
1889
|
-
if (["USDT", "USDC", "DAI", "BUSD"].includes(normalizedSymbol) && amount >= 1) {
|
|
1890
|
-
return `${Math.floor(amount)} ${normalizedSymbol}`;
|
|
1891
|
-
}
|
|
1892
|
-
if (options?.decimals !== void 0) {
|
|
1893
|
-
return `${amount.toFixed(options.decimals)} ${normalizedSymbol}`;
|
|
1894
|
-
}
|
|
1895
|
-
if (amount >= 1) {
|
|
1896
|
-
return `${amount.toFixed(0)} ${normalizedSymbol}`;
|
|
1897
|
-
} else if (amount >= 1e-3) {
|
|
1898
|
-
return `${amount.toFixed(3)} ${normalizedSymbol}`;
|
|
1899
|
-
} else {
|
|
1900
|
-
return `${amount.toFixed(6)} ${normalizedSymbol}`;
|
|
1901
|
-
}
|
|
1902
|
-
};
|
|
1903
|
-
const formatUsd = (value) => {
|
|
1904
|
-
if (!value || !isFinite(value)) return "$0";
|
|
1905
|
-
if (value >= 1) return `$${value.toFixed(2)}`;
|
|
1906
|
-
return `$${value.toFixed(6).replace(/0+$/, "").replace(/\.$/, "")}`;
|
|
1907
|
-
};
|
|
1908
|
-
const formatPercentage = (bps, decimals = 2) => {
|
|
1909
|
-
return `${(bps / 100).toFixed(decimals)}%`;
|
|
1910
|
-
};
|
|
1911
|
-
const formatBalance = (amount, decimals = 2) => {
|
|
1912
|
-
if (!isFinite(amount) || isNaN(amount) || amount <= 0) {
|
|
1913
|
-
return "0.00";
|
|
1914
|
-
}
|
|
1915
|
-
return amount.toFixed(decimals);
|
|
1916
|
-
};
|
|
1917
|
-
const formatHash = (hash, startChars = 4, endChars = 4) => {
|
|
1918
|
-
if (!hash) return "";
|
|
1919
|
-
if (hash.length <= startChars + endChars) return hash;
|
|
1920
|
-
return `${hash.slice(0, startChars)}...${hash.slice(-endChars)}`;
|
|
1921
|
-
};
|
|
1922
|
-
const formatAddress = formatHash;
|
|
1923
2612
|
const prefixIcons = {
|
|
1924
2613
|
tronlink: /* @__PURE__ */ jsx(TronLinkIcon, { className: "w-5 h-5" }),
|
|
1925
2614
|
metamask: /* @__PURE__ */ jsx(MetaMaskIcon, { className: "w-5 h-5" }),
|
|
1926
|
-
ton: /* @__PURE__ */ jsx(
|
|
2615
|
+
ton: /* @__PURE__ */ jsx(TonConnectIcon, { className: "w-5 h-5" })
|
|
1927
2616
|
};
|
|
1928
2617
|
const mapWalletToType = (wallet) => {
|
|
1929
2618
|
switch (wallet) {
|
|
@@ -1974,7 +2663,7 @@ const WalletInlineButton = ({
|
|
|
1974
2663
|
disabled: isButtonDisabled,
|
|
1975
2664
|
variant: "ghost",
|
|
1976
2665
|
size: "sm",
|
|
1977
|
-
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",
|
|
1978
2667
|
children: [
|
|
1979
2668
|
/* @__PURE__ */ jsx("span", { children: isConnected ? prefixIcons[wallet] : null }),
|
|
1980
2669
|
/* @__PURE__ */ jsx("span", { className: "leading-3 text-sm border-b border-dotted border-link text-link", children: buttonText })
|
|
@@ -2110,7 +2799,8 @@ const SwapSection = ({
|
|
|
2110
2799
|
onClose,
|
|
2111
2800
|
items: chains,
|
|
2112
2801
|
allowedItems: allowedChains,
|
|
2113
|
-
onChangeChain
|
|
2802
|
+
onChangeChain,
|
|
2803
|
+
isSource
|
|
2114
2804
|
}
|
|
2115
2805
|
)
|
|
2116
2806
|
] });
|
|
@@ -2164,7 +2854,6 @@ const AnotherAddress = () => {
|
|
|
2164
2854
|
/* @__PURE__ */ jsx(
|
|
2165
2855
|
Switch,
|
|
2166
2856
|
{
|
|
2167
|
-
className: "data-[state=unchecked]:bg-switch-inactive data-[state=checked]:bg-switch-active",
|
|
2168
2857
|
"aria-pressed": enabled,
|
|
2169
2858
|
checked: enabled,
|
|
2170
2859
|
onClick: () => setEnabled((v) => !v)
|
|
@@ -2183,7 +2872,7 @@ const AnotherAddress = () => {
|
|
|
2183
2872
|
"div",
|
|
2184
2873
|
{
|
|
2185
2874
|
className: cn(
|
|
2186
|
-
"bg-input py-2
|
|
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",
|
|
2187
2876
|
{
|
|
2188
2877
|
"py-4": value,
|
|
2189
2878
|
"border border-ring": isFocused,
|
|
@@ -2193,13 +2882,13 @@ const AnotherAddress = () => {
|
|
|
2193
2882
|
children: [
|
|
2194
2883
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 w-full", children: [
|
|
2195
2884
|
/* @__PURE__ */ jsx(
|
|
2196
|
-
|
|
2885
|
+
"textarea",
|
|
2197
2886
|
{
|
|
2198
2887
|
className: cn(
|
|
2199
|
-
"
|
|
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"
|
|
2200
2889
|
),
|
|
2201
2890
|
placeholder: t("bridge.anotherAddressPlaceholder"),
|
|
2202
|
-
|
|
2891
|
+
rows: value.length >= 32 ? 2 : 1,
|
|
2203
2892
|
value,
|
|
2204
2893
|
onFocus: () => setIsFocused(true),
|
|
2205
2894
|
onBlur: () => setIsFocused(false),
|
|
@@ -2216,7 +2905,7 @@ const AnotherAddress = () => {
|
|
|
2216
2905
|
Button,
|
|
2217
2906
|
{
|
|
2218
2907
|
variant: "secondary",
|
|
2219
|
-
className: "bg-
|
|
2908
|
+
className: "bg-accent text-card-foreground uppercase text-xs",
|
|
2220
2909
|
size: "sm",
|
|
2221
2910
|
onClick: onPaste,
|
|
2222
2911
|
children: t("common.paste")
|
|
@@ -2226,258 +2915,136 @@ const AnotherAddress = () => {
|
|
|
2226
2915
|
{
|
|
2227
2916
|
variant: "ghost",
|
|
2228
2917
|
size: "sm",
|
|
2229
|
-
className: "rounded-full p-0 w-5 h-5 self-start",
|
|
2230
|
-
onClick: () => setValue(""),
|
|
2231
|
-
children: /* @__PURE__ */ jsx(X, { className: "w-4 h-4" })
|
|
2232
|
-
}
|
|
2233
|
-
)
|
|
2234
|
-
]
|
|
2235
|
-
}
|
|
2236
|
-
)
|
|
2237
|
-
},
|
|
2238
|
-
"custom-address-block"
|
|
2239
|
-
) })
|
|
2240
|
-
] });
|
|
2241
|
-
};
|
|
2242
|
-
const
|
|
2243
|
-
return /* @__PURE__ */
|
|
2244
|
-
"svg",
|
|
2245
|
-
{
|
|
2246
|
-
width: "16",
|
|
2247
|
-
height: "16",
|
|
2248
|
-
viewBox: "0 0 16 16",
|
|
2249
|
-
fill: "none",
|
|
2250
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
2251
|
-
...props,
|
|
2252
|
-
children:
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
width: "100%"
|
|
2262
|
-
}
|
|
2263
|
-
}
|
|
2264
|
-
) }),
|
|
2265
|
-
/* @__PURE__ */ jsx(
|
|
2266
|
-
"circle",
|
|
2267
|
-
{
|
|
2268
|
-
"data-figma-bg-blur-radius": "4.4893",
|
|
2269
|
-
cx: "8",
|
|
2270
|
-
cy: "8",
|
|
2271
|
-
r: "8",
|
|
2272
|
-
fill: "currentColor",
|
|
2273
|
-
fillOpacity: "0.2"
|
|
2274
|
-
}
|
|
2275
|
-
),
|
|
2276
|
-
/* @__PURE__ */ jsx(
|
|
2277
|
-
"path",
|
|
2278
|
-
{
|
|
2279
|
-
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",
|
|
2280
|
-
stroke: "currentColor",
|
|
2281
|
-
strokeOpacity: "0.5",
|
|
2282
|
-
strokeWidth: "1.68349",
|
|
2283
|
-
strokeLinecap: "round"
|
|
2284
|
-
}
|
|
2285
|
-
),
|
|
2286
|
-
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx(
|
|
2287
|
-
"clipPath",
|
|
2288
|
-
{
|
|
2289
|
-
id: "bgblur_0_13066_11660_clip_path",
|
|
2290
|
-
transform: "translate(4.4893 4.4893)",
|
|
2291
|
-
children: /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "8" })
|
|
2292
|
-
}
|
|
2293
|
-
) })
|
|
2294
|
-
]
|
|
2295
|
-
}
|
|
2296
|
-
);
|
|
2297
|
-
};
|
|
2298
|
-
const Tip = (props) => {
|
|
2299
|
-
const { children, text } = props;
|
|
2300
|
-
return /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
2301
|
-
/* @__PURE__ */ jsx(TooltipTrigger, { children }),
|
|
2302
|
-
/* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { children: text }) })
|
|
2303
|
-
] });
|
|
2304
|
-
};
|
|
2305
|
-
const BASE_URL = "https://icons-ckg.pages.dev/stargate-light/tokens";
|
|
2306
|
-
const TokenSymbol = ({
|
|
2307
|
-
symbol,
|
|
2308
|
-
className = "w-4 h-4",
|
|
2309
|
-
alt
|
|
2310
|
-
}) => {
|
|
2311
|
-
const normalizedSymbol = normalizeTickerSymbol$1(symbol).toLowerCase();
|
|
2312
|
-
const src = `${BASE_URL}/${normalizedSymbol}.svg`;
|
|
2313
|
-
return /* @__PURE__ */ jsx("img", { src, alt: alt ?? symbol, className });
|
|
2314
|
-
};
|
|
2315
|
-
const EVM_CONFIG = {
|
|
2316
|
-
usdtAddress: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
|
2317
|
-
gasEstimates: {
|
|
2318
|
-
approve: 65000n,
|
|
2319
|
-
bridge: 300000n
|
|
2320
|
-
},
|
|
2321
|
-
gasBuffer: 1.2,
|
|
2322
|
-
// 20% buffer
|
|
2323
|
-
timeout: 3e5,
|
|
2324
|
-
// 5 minutes (increased for slower networks)
|
|
2325
|
-
requiredConfirmations: 3
|
|
2326
|
-
// Wait for 3 confirmations for reorg protection
|
|
2327
|
-
};
|
|
2328
|
-
const TON_CONFIG = {
|
|
2329
|
-
apiUrl: "https://toncenter.com/api/v2",
|
|
2330
|
-
timeout: 36e4,
|
|
2331
|
-
// 6 minutes
|
|
2332
|
-
validUntil: 600,
|
|
2333
|
-
// 10 minutes
|
|
2334
|
-
pollingInterval: 5e3,
|
|
2335
|
-
// 5 seconds between transaction status checks
|
|
2336
|
-
estimatedNetworkFee: "100000000"
|
|
2337
|
-
// 0.1 TON in nanoton (conservative estimate)
|
|
2338
|
-
};
|
|
2339
|
-
const TRON_CONFIG = {
|
|
2340
|
-
timeout: 12e4,
|
|
2341
|
-
// 2 minutes (for 19 confirmations)
|
|
2342
|
-
feeLimit: 1e8,
|
|
2343
|
-
// 100 TRX in sun
|
|
2344
|
-
requiredConfirmations: 19,
|
|
2345
|
-
// TRON standard: 19 blocks for confirmation
|
|
2346
|
-
pollingInterval: 3e3
|
|
2347
|
-
// 3 seconds between checks
|
|
2348
|
-
};
|
|
2349
|
-
let tonClientInstance = null;
|
|
2350
|
-
function getTonClient(customClient, apiKey) {
|
|
2351
|
-
if (customClient) {
|
|
2352
|
-
return customClient;
|
|
2353
|
-
}
|
|
2354
|
-
if (!tonClientInstance) {
|
|
2355
|
-
tonClientInstance = new TonClient({
|
|
2356
|
-
endpoint: `${TON_CONFIG.apiUrl}/jsonRPC`,
|
|
2357
|
-
apiKey
|
|
2358
|
-
});
|
|
2359
|
-
}
|
|
2360
|
-
return tonClientInstance;
|
|
2361
|
-
}
|
|
2362
|
-
function getQuoteAmounts(quote, srcToken, dstToken) {
|
|
2363
|
-
if (!quote || !srcToken || !dstToken) {
|
|
2364
|
-
return {
|
|
2365
|
-
inputHuman: 0,
|
|
2366
|
-
outputHuman: 0,
|
|
2367
|
-
outputHumanRounded: "0",
|
|
2368
|
-
minReceivedHuman: 0
|
|
2369
|
-
};
|
|
2370
|
-
}
|
|
2371
|
-
const inputHuman = fromLD(quote.srcAmount, srcToken.decimals);
|
|
2372
|
-
const outputHuman = fromLD(quote.dstAmount, dstToken.decimals);
|
|
2373
|
-
const outputHumanRounded = truncateToDecimals(outputHuman, 2);
|
|
2374
|
-
const minReceivedHuman = fromLD(
|
|
2375
|
-
quote.dstAmountMin || "0",
|
|
2376
|
-
dstToken.decimals
|
|
2377
|
-
);
|
|
2378
|
-
return {
|
|
2379
|
-
inputHuman,
|
|
2380
|
-
outputHuman,
|
|
2381
|
-
outputHumanRounded,
|
|
2382
|
-
minReceivedHuman
|
|
2383
|
-
};
|
|
2384
|
-
}
|
|
2385
|
-
function getQuoteFees(quote, tokens, chains, srcToken, dstToken) {
|
|
2386
|
-
if (!quote || !tokens || !chains) {
|
|
2387
|
-
return {
|
|
2388
|
-
totalUsd: 0,
|
|
2389
|
-
protocolFeeUsd: void 0,
|
|
2390
|
-
messageFeeUsd: void 0,
|
|
2391
|
-
serviceUsd: void 0,
|
|
2392
|
-
blockchainUsd: void 0,
|
|
2393
|
-
inSrcToken: void 0,
|
|
2394
|
-
inDstToken: void 0
|
|
2395
|
-
};
|
|
2396
|
-
}
|
|
2397
|
-
const feeData = computeFeesUsdFromArray(quote.fees, tokens, chains);
|
|
2398
|
-
let inSrcToken = void 0;
|
|
2399
|
-
let inDstToken = void 0;
|
|
2400
|
-
if (srcToken && quote.srcChainKey) {
|
|
2401
|
-
const feeInSrcLD = sumFeeByTokenLD(
|
|
2402
|
-
quote.fees,
|
|
2403
|
-
srcToken.address,
|
|
2404
|
-
quote.srcChainKey
|
|
2405
|
-
);
|
|
2406
|
-
const feeInSrcHuman = fromLD(feeInSrcLD, srcToken.decimals);
|
|
2407
|
-
if (feeInSrcHuman > 0) {
|
|
2408
|
-
inSrcToken = Number(truncateToDecimals(feeInSrcHuman, 8));
|
|
2409
|
-
} else if (feeData.totalUsd > 0 && srcToken.price?.usd) {
|
|
2410
|
-
const feeInSrcApprox = feeData.totalUsd / srcToken.price.usd;
|
|
2411
|
-
inSrcToken = Number(truncateToDecimals(feeInSrcApprox, 8));
|
|
2412
|
-
}
|
|
2413
|
-
}
|
|
2414
|
-
if (dstToken && quote.dstChainKey) {
|
|
2415
|
-
const feeInDstLD = sumFeeByTokenLD(
|
|
2416
|
-
quote.fees,
|
|
2417
|
-
dstToken.address,
|
|
2418
|
-
quote.dstChainKey
|
|
2419
|
-
);
|
|
2420
|
-
const feeInDstHuman = fromLD(feeInDstLD, dstToken.decimals);
|
|
2421
|
-
if (feeInDstHuman > 0) {
|
|
2422
|
-
inDstToken = Number(truncateToDecimals(feeInDstHuman, 8));
|
|
2918
|
+
className: "rounded-full p-0 w-5 h-5 self-start",
|
|
2919
|
+
onClick: () => setValue(""),
|
|
2920
|
+
children: /* @__PURE__ */ jsx(X, { className: "w-4 h-4" })
|
|
2921
|
+
}
|
|
2922
|
+
)
|
|
2923
|
+
]
|
|
2924
|
+
}
|
|
2925
|
+
)
|
|
2926
|
+
},
|
|
2927
|
+
"custom-address-block"
|
|
2928
|
+
) })
|
|
2929
|
+
] });
|
|
2930
|
+
};
|
|
2931
|
+
const InfoIcon = (props) => {
|
|
2932
|
+
return /* @__PURE__ */ jsx(
|
|
2933
|
+
"svg",
|
|
2934
|
+
{
|
|
2935
|
+
width: "16",
|
|
2936
|
+
height: "16",
|
|
2937
|
+
viewBox: "0 0 16 16",
|
|
2938
|
+
fill: "none",
|
|
2939
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
2940
|
+
...props,
|
|
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
|
+
)
|
|
2423
2950
|
}
|
|
2424
|
-
}
|
|
2425
|
-
return {
|
|
2426
|
-
totalUsd: feeData.totalUsd,
|
|
2427
|
-
protocolFeeUsd: feeData.protocolFeeUsd,
|
|
2428
|
-
messageFeeUsd: feeData.messageFeeUsd,
|
|
2429
|
-
serviceUsd: feeData.serviceUsd,
|
|
2430
|
-
blockchainUsd: feeData.blockchainUsd,
|
|
2431
|
-
inSrcToken,
|
|
2432
|
-
inDstToken
|
|
2433
|
-
};
|
|
2434
|
-
}
|
|
2435
|
-
function calculateMinReceived(quote, slippageBps, dstToken) {
|
|
2436
|
-
if (!quote || !dstToken) return 0;
|
|
2437
|
-
const dstAmountLD = BigInt(quote.dstAmount);
|
|
2438
|
-
const minAmountLD = dstAmountLD * BigInt(1e4 - slippageBps) / BigInt(1e4);
|
|
2439
|
-
return fromLD(minAmountLD.toString(), dstToken.decimals);
|
|
2440
|
-
}
|
|
2441
|
-
function addTonNetworkFee(quote, chains) {
|
|
2442
|
-
if (!quote || quote.srcChainKey.toLowerCase() !== "ton") {
|
|
2443
|
-
return quote;
|
|
2444
|
-
}
|
|
2445
|
-
const tonChain = chains?.find(
|
|
2446
|
-
(c) => c.chainKey.toLowerCase() === "ton"
|
|
2447
|
-
);
|
|
2448
|
-
if (!tonChain?.nativeCurrency?.address) {
|
|
2449
|
-
console.warn("Could not find TON native currency address");
|
|
2450
|
-
return quote;
|
|
2451
|
-
}
|
|
2452
|
-
const networkFee = {
|
|
2453
|
-
token: tonChain.nativeCurrency.address,
|
|
2454
|
-
chainKey: "ton",
|
|
2455
|
-
amount: TON_CONFIG.estimatedNetworkFee,
|
|
2456
|
-
type: "network"
|
|
2457
|
-
};
|
|
2458
|
-
const hasNetworkFee = quote.fees?.some(
|
|
2459
|
-
(fee) => fee.type === "network" && fee.chainKey === "ton"
|
|
2460
2951
|
);
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
}
|
|
2464
|
-
return {
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
};
|
|
2952
|
+
};
|
|
2953
|
+
const Tip = (props) => {
|
|
2954
|
+
const { children, text } = props;
|
|
2955
|
+
return /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
2956
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { className: "w-4 h-4 rounded-full bg-muted text-muted-foreground", children }),
|
|
2957
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { children: text }) })
|
|
2958
|
+
] });
|
|
2959
|
+
};
|
|
2960
|
+
const BASE_URL = "https://icons-ckg.pages.dev/stargate-light/tokens";
|
|
2961
|
+
const TokenSymbol = ({
|
|
2962
|
+
symbol,
|
|
2963
|
+
className = "w-4 h-4",
|
|
2964
|
+
alt
|
|
2965
|
+
}) => {
|
|
2966
|
+
const normalizedSymbol = normalizeTickerSymbol$1(symbol).toLowerCase();
|
|
2967
|
+
const src = `${BASE_URL}/${normalizedSymbol}.svg`;
|
|
2968
|
+
return /* @__PURE__ */ jsx("img", { src, alt: alt ?? symbol, className });
|
|
2969
|
+
};
|
|
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;
|
|
2468
2975
|
}
|
|
2469
|
-
function
|
|
2470
|
-
const
|
|
2471
|
-
const
|
|
2472
|
-
const
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
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;
|
|
2481
3048
|
}
|
|
2482
3049
|
function getRouteDisplayName(route) {
|
|
2483
3050
|
if (!route) return "Stargate Bridge";
|
|
@@ -2494,6 +3061,7 @@ const Details = () => {
|
|
|
2494
3061
|
const { toChain, fromChain, chains } = useChainsStore();
|
|
2495
3062
|
const { quote, status } = useBridgeQuoteStore();
|
|
2496
3063
|
const { slippageBps, routePriority } = useSettingsStore();
|
|
3064
|
+
const gas = useGasEstimate();
|
|
2497
3065
|
const dstToken = resolveTokenOnChainFromMatrix$2(
|
|
2498
3066
|
assetMatrix,
|
|
2499
3067
|
selectedAssetSymbol,
|
|
@@ -2504,9 +3072,8 @@ const Details = () => {
|
|
|
2504
3072
|
selectedAssetSymbol,
|
|
2505
3073
|
fromChain?.chainKey
|
|
2506
3074
|
);
|
|
2507
|
-
const quoteWithFees = addTonNetworkFee(quote, chains);
|
|
2508
3075
|
const quoteDetails = getQuoteDetails(
|
|
2509
|
-
|
|
3076
|
+
quote,
|
|
2510
3077
|
srcToken,
|
|
2511
3078
|
dstToken,
|
|
2512
3079
|
tokens,
|
|
@@ -2517,21 +3084,7 @@ const Details = () => {
|
|
|
2517
3084
|
const isLoading = status === "loading";
|
|
2518
3085
|
const receiveText = quoteDetails.outputAmount != null ? Number(quoteDetails.outputAmount).toFixed(6) : "0.00";
|
|
2519
3086
|
const etaText = quoteDetails.etaSeconds != null ? `≈ ${Math.max(1, Math.round(quoteDetails.etaSeconds / 60))}m` : "—";
|
|
2520
|
-
const
|
|
2521
|
-
const feeInDst = quoteDetails.fees?.inDstToken;
|
|
2522
|
-
const totalFeeUsd = quoteDetails.fees?.totalUsd;
|
|
2523
|
-
const totalFeeDisplay = (() => {
|
|
2524
|
-
if (feeInSrc != null && srcToken?.symbol) {
|
|
2525
|
-
return `${Number(feeInSrc).toFixed(6)} ${srcToken.symbol.toUpperCase()}`;
|
|
2526
|
-
}
|
|
2527
|
-
if (feeInDst != null && dstToken?.symbol) {
|
|
2528
|
-
return `${Number(feeInDst).toFixed(6)} ${dstToken.symbol.toUpperCase()}`;
|
|
2529
|
-
}
|
|
2530
|
-
if (totalFeeUsd != null) {
|
|
2531
|
-
return formatUsd(totalFeeUsd);
|
|
2532
|
-
}
|
|
2533
|
-
return "—";
|
|
2534
|
-
})();
|
|
3087
|
+
const totalFeeDisplay = gas.requiredNative > 0 ? `${gas.requiredNative.toFixed(6)} ${gas.nativeSym}` : "—";
|
|
2535
3088
|
const currentSlippageText = formatPercentage(slippageBps);
|
|
2536
3089
|
const routeText = quote?.route ? getRouteDisplayName(quote.route) : t(`settings.routePresets.${routePriority}`);
|
|
2537
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: [
|
|
@@ -2551,7 +3104,7 @@ const Details = () => {
|
|
|
2551
3104
|
DetailsRow,
|
|
2552
3105
|
{
|
|
2553
3106
|
label: t("transaction.route"),
|
|
2554
|
-
value: /* @__PURE__ */ jsxs("
|
|
3107
|
+
value: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
2555
3108
|
/* @__PURE__ */ jsx(StargateIcon, { className: "w-4 h-4" }),
|
|
2556
3109
|
/* @__PURE__ */ jsx("p", { className: "", children: routeText })
|
|
2557
3110
|
] })
|
|
@@ -2578,7 +3131,14 @@ const Details = () => {
|
|
|
2578
3131
|
label: t("transaction.totalFee"),
|
|
2579
3132
|
value: /* @__PURE__ */ jsxs("strong", { className: "inline-flex items-center gap-1", children: [
|
|
2580
3133
|
/* @__PURE__ */ jsx("span", { children: totalFeeDisplay }),
|
|
2581
|
-
/* @__PURE__ */ jsx(
|
|
3134
|
+
/* @__PURE__ */ jsx(
|
|
3135
|
+
TokenSymbol,
|
|
3136
|
+
{
|
|
3137
|
+
symbol: gas.nativeSym,
|
|
3138
|
+
className: "w-4 h-4",
|
|
3139
|
+
alt: "token"
|
|
3140
|
+
}
|
|
3141
|
+
)
|
|
2582
3142
|
] }),
|
|
2583
3143
|
isLoading
|
|
2584
3144
|
}
|
|
@@ -2593,7 +3153,7 @@ const DetailsRow = ({
|
|
|
2593
3153
|
}) => /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
|
|
2594
3154
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2595
3155
|
/* @__PURE__ */ jsx("p", { className: "text-sm text-priority font-normal", children: label }),
|
|
2596
|
-
/* @__PURE__ */ jsx(Tip, { text: label, children: /* @__PURE__ */ jsx(
|
|
3156
|
+
/* @__PURE__ */ jsx(Tip, { text: label, children: /* @__PURE__ */ jsx(InfoIcon, {}) })
|
|
2597
3157
|
] }),
|
|
2598
3158
|
isLoading ? /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-16 rounded-md" }) : /* @__PURE__ */ jsx("div", { className: "text-foreground text-sm", children: value ?? "—" })
|
|
2599
3159
|
] });
|
|
@@ -2646,6 +3206,20 @@ const useTransactionStore = create((set, get) => ({
|
|
|
2646
3206
|
};
|
|
2647
3207
|
set({ current: next });
|
|
2648
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
|
+
},
|
|
2649
3223
|
reset: () => {
|
|
2650
3224
|
set({ current: void 0 });
|
|
2651
3225
|
}
|
|
@@ -2791,9 +3365,13 @@ function isUserRejection(error) {
|
|
|
2791
3365
|
if (error instanceof TransactionRejectedError) {
|
|
2792
3366
|
return true;
|
|
2793
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
|
+
}
|
|
2794
3372
|
if (error instanceof Error) {
|
|
2795
3373
|
const message = error.message.toLowerCase();
|
|
2796
|
-
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");
|
|
2797
3375
|
}
|
|
2798
3376
|
return false;
|
|
2799
3377
|
}
|
|
@@ -2812,17 +3390,13 @@ function toChainStrategyError(error, chainKey, context) {
|
|
|
2812
3390
|
error
|
|
2813
3391
|
);
|
|
2814
3392
|
}
|
|
2815
|
-
return new ChainStrategyError(
|
|
2816
|
-
String(error),
|
|
2817
|
-
"UNKNOWN_ERROR",
|
|
2818
|
-
chainKey
|
|
2819
|
-
);
|
|
3393
|
+
return new ChainStrategyError(String(error), "UNKNOWN_ERROR", chainKey);
|
|
2820
3394
|
}
|
|
2821
3395
|
function useBridgeTransaction() {
|
|
2822
3396
|
const { quote } = useBridgeQuoteStore();
|
|
2823
3397
|
const { chainRegistry } = useChainStrategies();
|
|
2824
3398
|
const { srcAddress, dstAddress } = useAddresses();
|
|
2825
|
-
const { assetMatrix,
|
|
3399
|
+
const { assetMatrix, selectedAssetSymbol } = useTokensStore();
|
|
2826
3400
|
const { chains } = useChainsStore();
|
|
2827
3401
|
const txStore = useTransactionStore();
|
|
2828
3402
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
@@ -2851,31 +3425,33 @@ function useBridgeTransaction() {
|
|
|
2851
3425
|
}
|
|
2852
3426
|
const srcChain = chains?.find((c) => c.chainKey === quote.srcChainKey);
|
|
2853
3427
|
const dstChain = chains?.find((c) => c.chainKey === quote.dstChainKey);
|
|
2854
|
-
const
|
|
2855
|
-
const amounts = getQuoteAmounts(quoteWithFees, srcToken, dstToken);
|
|
2856
|
-
const fees = getQuoteFees(quoteWithFees, tokens, chains, srcToken, dstToken);
|
|
3428
|
+
const amounts = getQuoteAmounts(quote, srcToken, dstToken);
|
|
2857
3429
|
const metadata = {
|
|
2858
3430
|
srcChainName: srcChain?.name || quote.srcChainKey,
|
|
2859
3431
|
dstChainName: dstChain?.name || quote.dstChainKey,
|
|
2860
3432
|
srcTokenSymbol: srcToken?.symbol || quote.srcToken,
|
|
2861
3433
|
dstTokenSymbol: dstToken?.symbol || quote.dstToken,
|
|
2862
3434
|
srcAmountHuman: amounts.inputHuman,
|
|
2863
|
-
dstAmountHuman: amounts.outputHuman
|
|
2864
|
-
//
|
|
2865
|
-
totalFeeValue: fees.inSrcToken ?? fees.inDstToken ?? void 0,
|
|
2866
|
-
totalFeeSymbol: fees.inSrcToken ? srcToken?.symbol : fees.inDstToken ? dstToken?.symbol : void 0
|
|
3435
|
+
dstAmountHuman: amounts.outputHuman
|
|
3436
|
+
// Actual fee will be updated when transaction completes
|
|
2867
3437
|
};
|
|
2868
3438
|
txStore.setTransaction(quote, "executing", metadata);
|
|
2869
3439
|
setIsProcessing(true);
|
|
2870
3440
|
try {
|
|
2871
3441
|
const steps = quote.steps || [];
|
|
2872
3442
|
if (steps.length === 0) {
|
|
2873
|
-
throw new InvalidStepsError(
|
|
3443
|
+
throw new InvalidStepsError(
|
|
3444
|
+
quote.srcChainKey,
|
|
3445
|
+
"No transaction steps found in quote"
|
|
3446
|
+
);
|
|
2874
3447
|
}
|
|
2875
3448
|
const chainKey = steps[0].chainKey;
|
|
2876
3449
|
const strategy = chainRegistry.getStrategy(chainKey);
|
|
2877
3450
|
if (!strategy) {
|
|
2878
|
-
throw new InvalidStepsError(
|
|
3451
|
+
throw new InvalidStepsError(
|
|
3452
|
+
chainKey,
|
|
3453
|
+
`No strategy available for chain: ${chainKey}`
|
|
3454
|
+
);
|
|
2879
3455
|
}
|
|
2880
3456
|
if (!strategy.isConnected()) {
|
|
2881
3457
|
throw new WalletNotConnectedError(chainKey);
|
|
@@ -2887,7 +3463,6 @@ function useBridgeTransaction() {
|
|
|
2887
3463
|
srcChainKey: quote.srcChainKey,
|
|
2888
3464
|
dstChainKey: quote.dstChainKey
|
|
2889
3465
|
};
|
|
2890
|
-
console.log(steps);
|
|
2891
3466
|
const txResult = await strategy.executeSteps(steps, context, (hash) => {
|
|
2892
3467
|
txStore.setSrcHash(hash);
|
|
2893
3468
|
txStore.updateStatus("processing");
|
|
@@ -2898,6 +3473,21 @@ function useBridgeTransaction() {
|
|
|
2898
3473
|
if (result.dstTxHash) {
|
|
2899
3474
|
txStore.setDstHash(result.dstTxHash);
|
|
2900
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
|
+
}
|
|
2901
3491
|
txStore.updateStatus("completed");
|
|
2902
3492
|
console.log("Transaction completed successfully");
|
|
2903
3493
|
} else {
|
|
@@ -2915,19 +3505,26 @@ function useBridgeTransaction() {
|
|
|
2915
3505
|
console.error("Error tracking completion:", err);
|
|
2916
3506
|
});
|
|
2917
3507
|
} else {
|
|
2918
|
-
throw new TransactionFailedError(
|
|
3508
|
+
throw new TransactionFailedError(
|
|
3509
|
+
chainKey,
|
|
3510
|
+
"Transaction hash not received from wallet"
|
|
3511
|
+
);
|
|
2919
3512
|
}
|
|
2920
3513
|
return txResult;
|
|
2921
3514
|
} catch (err) {
|
|
2922
3515
|
if (isUserRejection(err)) {
|
|
2923
3516
|
txStore.setError("TRANSACTION_REJECTED");
|
|
2924
|
-
throw new TransactionFailedError(
|
|
3517
|
+
throw new TransactionFailedError(
|
|
3518
|
+
quote.srcChainKey,
|
|
3519
|
+
"Transaction rejected by user"
|
|
3520
|
+
);
|
|
2925
3521
|
}
|
|
2926
3522
|
if (ChainStrategyError.isChainStrategyError(err)) {
|
|
2927
3523
|
txStore.setError(err.code, { chainKey: err.chainKey });
|
|
2928
3524
|
console.error("Chain strategy error:", err.toJSON());
|
|
2929
3525
|
throw err;
|
|
2930
3526
|
}
|
|
3527
|
+
console.log(err);
|
|
2931
3528
|
txStore.setError("UNKNOWN_ERROR");
|
|
2932
3529
|
throw err;
|
|
2933
3530
|
} finally {
|
|
@@ -2940,66 +3537,6 @@ function useBridgeTransaction() {
|
|
|
2940
3537
|
hasQuote: !!quote
|
|
2941
3538
|
};
|
|
2942
3539
|
}
|
|
2943
|
-
function useGasEstimate(amountNum) {
|
|
2944
|
-
const { fromChain } = useChainsStore();
|
|
2945
|
-
const { selectedAssetSymbol, tokens } = useTokensStore();
|
|
2946
|
-
const getSourceGasReserveHuman = useSettingsStore(
|
|
2947
|
-
(state) => state.getSourceGasReserveHuman
|
|
2948
|
-
);
|
|
2949
|
-
const { srcAddress } = useAddresses();
|
|
2950
|
-
const { balances, isLoading: balancesLoading } = useBalances(
|
|
2951
|
-
fromChain?.chainKey,
|
|
2952
|
-
srcAddress
|
|
2953
|
-
);
|
|
2954
|
-
const { chainRegistry } = useChainStrategies();
|
|
2955
|
-
const balancesKnown = !balancesLoading;
|
|
2956
|
-
const strategy = useMemo(() => {
|
|
2957
|
-
if (!fromChain) return null;
|
|
2958
|
-
return chainRegistry.getStrategy(fromChain.chainKey);
|
|
2959
|
-
}, [fromChain, chainRegistry]);
|
|
2960
|
-
const { data: gasRequirement } = useQuery({
|
|
2961
|
-
queryKey: [
|
|
2962
|
-
"gas-estimate",
|
|
2963
|
-
fromChain?.chainKey,
|
|
2964
|
-
selectedAssetSymbol,
|
|
2965
|
-
amountNum,
|
|
2966
|
-
balances
|
|
2967
|
-
],
|
|
2968
|
-
queryFn: async () => {
|
|
2969
|
-
if (!fromChain || !strategy) {
|
|
2970
|
-
return null;
|
|
2971
|
-
}
|
|
2972
|
-
const selectedToken = tokens?.find(
|
|
2973
|
-
(t) => t.symbol.toUpperCase() === selectedAssetSymbol?.toUpperCase()
|
|
2974
|
-
) || null;
|
|
2975
|
-
const nativeTokenSymbol = fromChain.nativeCurrency?.symbol ?? "";
|
|
2976
|
-
const nativeDecimals = fromChain.nativeCurrency?.decimals || 18;
|
|
2977
|
-
const reserveFallback = getSourceGasReserveHuman(fromChain.chainKey);
|
|
2978
|
-
const result = await strategy.estimateGasRequirement({
|
|
2979
|
-
selectedToken,
|
|
2980
|
-
nativeTokenSymbol,
|
|
2981
|
-
amount: amountNum,
|
|
2982
|
-
balances,
|
|
2983
|
-
nativeDecimals,
|
|
2984
|
-
reserveFallback
|
|
2985
|
-
});
|
|
2986
|
-
return result;
|
|
2987
|
-
},
|
|
2988
|
-
enabled: !!fromChain && !!strategy,
|
|
2989
|
-
staleTime: 3e4,
|
|
2990
|
-
gcTime: 5 * 6e4,
|
|
2991
|
-
refetchOnWindowFocus: false,
|
|
2992
|
-
retry: 1
|
|
2993
|
-
});
|
|
2994
|
-
return {
|
|
2995
|
-
nativeSym: gasRequirement?.nativeSym || "",
|
|
2996
|
-
nativeBalance: gasRequirement?.nativeBalance || 0,
|
|
2997
|
-
requiredNative: gasRequirement?.requiredNative || 0,
|
|
2998
|
-
balancesKnown,
|
|
2999
|
-
isNativeSelected: gasRequirement?.isNativeSelected || false,
|
|
3000
|
-
hasEnoughGas: balancesKnown ? gasRequirement?.hasEnoughGas ?? true : true
|
|
3001
|
-
};
|
|
3002
|
-
}
|
|
3003
3540
|
function useBalanceCheck(amountNum, gas) {
|
|
3004
3541
|
const { fromChain } = useChainsStore();
|
|
3005
3542
|
const { selectedAssetSymbol } = useTokensStore();
|
|
@@ -3330,8 +3867,8 @@ const WalletSelectModal = () => {
|
|
|
3330
3867
|
{
|
|
3331
3868
|
strategy: tonWallet,
|
|
3332
3869
|
walletId: "ton",
|
|
3333
|
-
name: t("wallets.
|
|
3334
|
-
icon:
|
|
3870
|
+
name: t("wallets.tonconnect"),
|
|
3871
|
+
icon: TonConnectIcon
|
|
3335
3872
|
},
|
|
3336
3873
|
{
|
|
3337
3874
|
strategy: metaMaskWallet,
|
|
@@ -3369,8 +3906,8 @@ const WalletSelectModal = () => {
|
|
|
3369
3906
|
const tonWallets = [
|
|
3370
3907
|
{
|
|
3371
3908
|
id: "ton",
|
|
3372
|
-
name: t("wallets.
|
|
3373
|
-
icon:
|
|
3909
|
+
name: t("wallets.tonconnect"),
|
|
3910
|
+
icon: TonConnectIcon,
|
|
3374
3911
|
enabled: true
|
|
3375
3912
|
}
|
|
3376
3913
|
];
|
|
@@ -3582,7 +4119,7 @@ const SuccessStep = ({
|
|
|
3582
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: [
|
|
3583
4120
|
icon,
|
|
3584
4121
|
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: t("transaction.success") }) }),
|
|
3585
|
-
/* @__PURE__ */ jsxs("div", { className: "w-full space-y-
|
|
4122
|
+
/* @__PURE__ */ jsxs("div", { className: "w-full space-y-2 mt-4 relative z-10 pb-14", children: [
|
|
3586
4123
|
metadata?.srcAmountHuman && /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
|
|
3587
4124
|
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("transaction.bridged") }),
|
|
3588
4125
|
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 font-medium", children: [
|
|
@@ -3594,15 +4131,13 @@ const SuccessStep = ({
|
|
|
3594
4131
|
] }),
|
|
3595
4132
|
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
|
|
3596
4133
|
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("transaction.transferTitle") }),
|
|
3597
|
-
/* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
|
|
4134
|
+
/* @__PURE__ */ jsxs("span", { className: "font-medium space-x-1", children: [
|
|
3598
4135
|
/* @__PURE__ */ jsxs("span", { className: "inline-flex gap-1 items-center", children: [
|
|
3599
4136
|
metadata?.srcChainName,
|
|
3600
4137
|
" ",
|
|
3601
4138
|
/* @__PURE__ */ jsx(NetworkSymbol, { chainKey: metadata.srcChainName })
|
|
3602
4139
|
] }),
|
|
3603
|
-
" ",
|
|
3604
|
-
"→",
|
|
3605
|
-
" ",
|
|
4140
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "→" }),
|
|
3606
4141
|
/* @__PURE__ */ jsxs("span", { className: "inline-flex gap-1 items-center", children: [
|
|
3607
4142
|
metadata?.dstChainName,
|
|
3608
4143
|
" ",
|
|
@@ -3616,17 +4151,17 @@ const SuccessStep = ({
|
|
|
3616
4151
|
"button",
|
|
3617
4152
|
{
|
|
3618
4153
|
onClick: handleOpenExplorer,
|
|
3619
|
-
className: "font-medium
|
|
4154
|
+
className: "font-medium cursor-pointer inline-flex items-center gap-1 underline hover:no-underline",
|
|
3620
4155
|
children: formatHash(srcTxHash)
|
|
3621
4156
|
}
|
|
3622
4157
|
)
|
|
3623
4158
|
] }),
|
|
3624
|
-
metadata?.
|
|
4159
|
+
metadata?.actualFeeValue && /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
|
|
3625
4160
|
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("transaction.finalFee") }),
|
|
3626
4161
|
/* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
|
|
3627
|
-
metadata.
|
|
4162
|
+
metadata.actualFeeValue,
|
|
3628
4163
|
" ",
|
|
3629
|
-
metadata.
|
|
4164
|
+
metadata.actualFeeSymbol
|
|
3630
4165
|
] })
|
|
3631
4166
|
] })
|
|
3632
4167
|
] })
|
|
@@ -3723,7 +4258,7 @@ const useTokens = () => {
|
|
|
3723
4258
|
return { ...query };
|
|
3724
4259
|
};
|
|
3725
4260
|
const useChains = () => {
|
|
3726
|
-
const { setChains, setFromChain } = useChainsStore();
|
|
4261
|
+
const { setChains, setFromChain, fromChain } = useChainsStore();
|
|
3727
4262
|
const query = useQuery({
|
|
3728
4263
|
queryKey: ["chains"],
|
|
3729
4264
|
queryFn: () => getChains(),
|
|
@@ -3737,10 +4272,16 @@ const useChains = () => {
|
|
|
3737
4272
|
});
|
|
3738
4273
|
useEffect(() => {
|
|
3739
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
|
+
);
|
|
3740
4279
|
setChains(query.data);
|
|
3741
|
-
|
|
4280
|
+
if (!fromChain) {
|
|
4281
|
+
setFromChain(query.data[0]);
|
|
4282
|
+
}
|
|
3742
4283
|
}
|
|
3743
|
-
}, [query.isSuccess, query.data
|
|
4284
|
+
}, [query.isSuccess, query.data]);
|
|
3744
4285
|
useEffect(() => {
|
|
3745
4286
|
if (query.isError && query.error) {
|
|
3746
4287
|
console.error(query.error.name, query.error.message);
|
|
@@ -3898,7 +4439,9 @@ async function getEvmBalances(publicClient, address, tokens, priorityToken) {
|
|
|
3898
4439
|
}
|
|
3899
4440
|
} catch (error) {
|
|
3900
4441
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3901
|
-
const isZeroDataError = errorMessage.includes(
|
|
4442
|
+
const isZeroDataError = errorMessage.includes(
|
|
4443
|
+
'returned no data ("0x")'
|
|
4444
|
+
);
|
|
3902
4445
|
if (!isZeroDataError) {
|
|
3903
4446
|
console.debug(
|
|
3904
4447
|
`Failed to get priority token balance for ${priorityToken.symbol}:`,
|
|
@@ -4009,12 +4552,9 @@ async function getTonBalances(address, tokens, customTonClient, tonApiKey) {
|
|
|
4009
4552
|
}
|
|
4010
4553
|
const client = getTonClient(customTonClient, tonApiKey);
|
|
4011
4554
|
const accountAddress = Address$1.parse(address);
|
|
4012
|
-
console.log(address);
|
|
4013
|
-
console.log(tokens);
|
|
4014
4555
|
try {
|
|
4015
4556
|
const balance = await client.getBalance(accountAddress);
|
|
4016
4557
|
const tonBalance = Number(balance) / 1e9;
|
|
4017
|
-
console.log("tonBalance", tonBalance);
|
|
4018
4558
|
if (tonBalance > 0) {
|
|
4019
4559
|
balances.TON = { balance: tonBalance, address: "ton-native" };
|
|
4020
4560
|
}
|
|
@@ -4071,8 +4611,23 @@ async function getTronBalances(tronWeb, address, tokens) {
|
|
|
4071
4611
|
try {
|
|
4072
4612
|
if (!tronWeb) throw new Error("TronWeb not available");
|
|
4073
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
|
+
}
|
|
4074
4626
|
for (const token of tokens) {
|
|
4075
4627
|
try {
|
|
4628
|
+
if (isNativeAddress(token.address) && token.symbol.toUpperCase() === "TRX") {
|
|
4629
|
+
continue;
|
|
4630
|
+
}
|
|
4076
4631
|
let balance = 0;
|
|
4077
4632
|
if (isNativeAddress(token.address)) {
|
|
4078
4633
|
const trxBalanceSun = await tronWeb.trx.getBalance(ownerB58);
|
|
@@ -4159,6 +4714,9 @@ class EvmChainStrategy {
|
|
|
4159
4714
|
getConnectLabel(t) {
|
|
4160
4715
|
return t("wallets.connectEvmWallet");
|
|
4161
4716
|
}
|
|
4717
|
+
getClient() {
|
|
4718
|
+
return this.provider;
|
|
4719
|
+
}
|
|
4162
4720
|
async getBalances(address, tokens, priorityToken) {
|
|
4163
4721
|
if (!this.publicClient) {
|
|
4164
4722
|
console.warn("No publicClient available for balance query");
|
|
@@ -4175,52 +4733,6 @@ class EvmChainStrategy {
|
|
|
4175
4733
|
if (!address) return false;
|
|
4176
4734
|
return /^0x[0-9a-fA-F]{40}$/.test(address);
|
|
4177
4735
|
}
|
|
4178
|
-
async estimateGasRequirement(params) {
|
|
4179
|
-
const provider = this.provider;
|
|
4180
|
-
const {
|
|
4181
|
-
selectedToken,
|
|
4182
|
-
nativeTokenSymbol,
|
|
4183
|
-
amount,
|
|
4184
|
-
balances,
|
|
4185
|
-
nativeDecimals,
|
|
4186
|
-
reserveFallback
|
|
4187
|
-
} = params;
|
|
4188
|
-
const nativeSym = nativeTokenSymbol.toUpperCase();
|
|
4189
|
-
const isNativeSelected = nativeSym === (selectedToken?.symbol ?? "").toUpperCase();
|
|
4190
|
-
const nativeBalance = Number(balances[nativeSym]?.balance ?? 0);
|
|
4191
|
-
let estimatedGas = null;
|
|
4192
|
-
if (provider) {
|
|
4193
|
-
try {
|
|
4194
|
-
const gasPriceHex = await provider.send("eth_gasPrice", []);
|
|
4195
|
-
const gasPrice = BigInt(gasPriceHex);
|
|
4196
|
-
const approveGas = isNativeSelected ? 0n : EVM_CONFIG.gasEstimates.approve;
|
|
4197
|
-
const bridgeGas = EVM_CONFIG.gasEstimates.bridge;
|
|
4198
|
-
const totalGas = approveGas + bridgeGas;
|
|
4199
|
-
const bufferMultiplier = BigInt(Math.floor(EVM_CONFIG.gasBuffer * 100));
|
|
4200
|
-
const requiredWei = gasPrice * totalGas * bufferMultiplier / 100n;
|
|
4201
|
-
const { formatUnits: formatUnits2 } = await import("ethers");
|
|
4202
|
-
estimatedGas = Number(formatUnits2(requiredWei, nativeDecimals));
|
|
4203
|
-
} catch {
|
|
4204
|
-
estimatedGas = null;
|
|
4205
|
-
}
|
|
4206
|
-
}
|
|
4207
|
-
const requiredNative = estimatedGas ?? reserveFallback;
|
|
4208
|
-
const amountNum = amount ?? 0;
|
|
4209
|
-
let hasEnoughGas = true;
|
|
4210
|
-
if (isNativeSelected) {
|
|
4211
|
-
hasEnoughGas = nativeBalance - amountNum >= requiredNative;
|
|
4212
|
-
} else {
|
|
4213
|
-
hasEnoughGas = nativeBalance >= requiredNative;
|
|
4214
|
-
}
|
|
4215
|
-
return {
|
|
4216
|
-
estimatedGas,
|
|
4217
|
-
nativeBalance,
|
|
4218
|
-
requiredNative,
|
|
4219
|
-
hasEnoughGas,
|
|
4220
|
-
nativeSym,
|
|
4221
|
-
isNativeSelected
|
|
4222
|
-
};
|
|
4223
|
-
}
|
|
4224
4736
|
validateSteps(steps) {
|
|
4225
4737
|
if (!steps || steps.length === 0) {
|
|
4226
4738
|
throw new InvalidStepsError("evm", "No transaction steps provided");
|
|
@@ -4313,8 +4825,24 @@ class EvmChainStrategy {
|
|
|
4313
4825
|
console.log(
|
|
4314
4826
|
`EVM transaction confirmed in block ${receipt.blockNumber} with ${EVM_CONFIG.requiredConfirmations} confirmations`
|
|
4315
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
|
+
}
|
|
4316
4842
|
return {
|
|
4317
|
-
completed: true
|
|
4843
|
+
completed: true,
|
|
4844
|
+
actualFeeValue
|
|
4845
|
+
// Symbol will be determined by the caller based on chain info
|
|
4318
4846
|
};
|
|
4319
4847
|
} catch (error) {
|
|
4320
4848
|
if (error && typeof error === "object" && "code" in error && error.code === "TRANSACTION_REPLACED") {
|
|
@@ -4327,8 +4855,26 @@ class EvmChainStrategy {
|
|
|
4327
4855
|
console.log(
|
|
4328
4856
|
`Replacement transaction succeeded in block ${replacementReceipt.blockNumber}`
|
|
4329
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
|
+
}
|
|
4330
4875
|
return {
|
|
4331
|
-
completed: true
|
|
4876
|
+
completed: true,
|
|
4877
|
+
actualFeeValue
|
|
4332
4878
|
};
|
|
4333
4879
|
} else {
|
|
4334
4880
|
const chainError2 = new TransactionRevertedError("evm", txHash);
|
|
@@ -4531,6 +5077,9 @@ class TonChainStrategy {
|
|
|
4531
5077
|
getConnectLabel(t) {
|
|
4532
5078
|
return t("wallets.connectTonWallet");
|
|
4533
5079
|
}
|
|
5080
|
+
getClient() {
|
|
5081
|
+
return getTonClient(this.config.tonClient, this.config.tonApiKey);
|
|
5082
|
+
}
|
|
4534
5083
|
async getBalances(address, tokens) {
|
|
4535
5084
|
return await getTonBalances(
|
|
4536
5085
|
address,
|
|
@@ -4543,39 +5092,6 @@ class TonChainStrategy {
|
|
|
4543
5092
|
if (!address) return false;
|
|
4544
5093
|
return true;
|
|
4545
5094
|
}
|
|
4546
|
-
async estimateGasRequirement(params) {
|
|
4547
|
-
const {
|
|
4548
|
-
selectedToken,
|
|
4549
|
-
nativeTokenSymbol,
|
|
4550
|
-
amount,
|
|
4551
|
-
balances,
|
|
4552
|
-
nativeDecimals = 9,
|
|
4553
|
-
reserveFallback
|
|
4554
|
-
} = params;
|
|
4555
|
-
const nativeSym = nativeTokenSymbol.toUpperCase();
|
|
4556
|
-
const isNativeSelected = nativeSym === (selectedToken?.symbol ?? "").toUpperCase();
|
|
4557
|
-
const nativeBalance = Number(balances[nativeSym]?.balance ?? 0);
|
|
4558
|
-
const { formatUnits: formatUnits2 } = await import("ethers");
|
|
4559
|
-
const estimatedGas = Number(
|
|
4560
|
-
formatUnits2(TON_CONFIG.estimatedNetworkFee, nativeDecimals)
|
|
4561
|
-
);
|
|
4562
|
-
const requiredNative = estimatedGas > 0 ? estimatedGas : reserveFallback;
|
|
4563
|
-
const amountNum = amount ?? 0;
|
|
4564
|
-
let hasEnoughGas = true;
|
|
4565
|
-
if (isNativeSelected) {
|
|
4566
|
-
hasEnoughGas = nativeBalance - amountNum >= requiredNative;
|
|
4567
|
-
} else {
|
|
4568
|
-
hasEnoughGas = nativeBalance >= requiredNative;
|
|
4569
|
-
}
|
|
4570
|
-
return {
|
|
4571
|
-
estimatedGas,
|
|
4572
|
-
nativeBalance,
|
|
4573
|
-
requiredNative,
|
|
4574
|
-
hasEnoughGas,
|
|
4575
|
-
nativeSym,
|
|
4576
|
-
isNativeSelected
|
|
4577
|
-
};
|
|
4578
|
-
}
|
|
4579
5095
|
validateSteps(steps) {
|
|
4580
5096
|
if (!steps || steps.length === 0) {
|
|
4581
5097
|
throw new InvalidStepsError("ton", "No transaction steps provided");
|
|
@@ -4652,11 +5168,11 @@ class TonChainStrategy {
|
|
|
4652
5168
|
}
|
|
4653
5169
|
async waitForCompletion(txHash) {
|
|
4654
5170
|
try {
|
|
4655
|
-
const
|
|
5171
|
+
const result = await this.checkTonTransaction(
|
|
4656
5172
|
txHash,
|
|
4657
5173
|
TON_CONFIG.timeout
|
|
4658
5174
|
);
|
|
4659
|
-
if (!confirmed) {
|
|
5175
|
+
if (!result.confirmed) {
|
|
4660
5176
|
const error = new TransactionFailedError(
|
|
4661
5177
|
"ton",
|
|
4662
5178
|
"Transaction not confirmed on-chain",
|
|
@@ -4668,7 +5184,9 @@ class TonChainStrategy {
|
|
|
4668
5184
|
};
|
|
4669
5185
|
}
|
|
4670
5186
|
return {
|
|
4671
|
-
completed: true
|
|
5187
|
+
completed: true,
|
|
5188
|
+
actualFeeValue: result.fee,
|
|
5189
|
+
actualFeeSymbol: "TON"
|
|
4672
5190
|
};
|
|
4673
5191
|
} catch (error) {
|
|
4674
5192
|
const chainError = toChainStrategyError(
|
|
@@ -4711,7 +5229,7 @@ class TonChainStrategy {
|
|
|
4711
5229
|
"Expected external-in message, got:",
|
|
4712
5230
|
inMessage.info.type
|
|
4713
5231
|
);
|
|
4714
|
-
return false;
|
|
5232
|
+
return { confirmed: false };
|
|
4715
5233
|
}
|
|
4716
5234
|
accountAddress = inMessage.info.dest;
|
|
4717
5235
|
targetMessageHash = this.getNormalizedExtMessageHash(inMessage);
|
|
@@ -4719,7 +5237,7 @@ class TonChainStrategy {
|
|
|
4719
5237
|
targetMessageHash = Buffer.from(hashOrBoc, "hex");
|
|
4720
5238
|
if (!this.config.tonAddress) {
|
|
4721
5239
|
console.debug("No wallet address available for hex hash lookup");
|
|
4722
|
-
return false;
|
|
5240
|
+
return { confirmed: false };
|
|
4723
5241
|
}
|
|
4724
5242
|
accountAddress = Address.parse(this.config.tonAddress);
|
|
4725
5243
|
}
|
|
@@ -4746,7 +5264,13 @@ class TonChainStrategy {
|
|
|
4746
5264
|
);
|
|
4747
5265
|
if (txInMessageHash.equals(targetMessageHash)) {
|
|
4748
5266
|
console.debug("Transaction found by in-message hash");
|
|
4749
|
-
|
|
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
|
+
};
|
|
4750
5274
|
}
|
|
4751
5275
|
}
|
|
4752
5276
|
}
|
|
@@ -4761,10 +5285,10 @@ class TonChainStrategy {
|
|
|
4761
5285
|
hash = void 0;
|
|
4762
5286
|
}
|
|
4763
5287
|
}
|
|
4764
|
-
return false;
|
|
5288
|
+
return { confirmed: false };
|
|
4765
5289
|
} catch (error) {
|
|
4766
5290
|
console.debug("Error parsing BOC or checking transaction:", error);
|
|
4767
|
-
return false;
|
|
5291
|
+
return { confirmed: false };
|
|
4768
5292
|
}
|
|
4769
5293
|
}
|
|
4770
5294
|
}
|
|
@@ -4818,7 +5342,7 @@ class TronChainStrategy {
|
|
|
4818
5342
|
return t("wallets.connectTronWallet");
|
|
4819
5343
|
}
|
|
4820
5344
|
async getBalances(address, tokens) {
|
|
4821
|
-
const tronWeb = this.
|
|
5345
|
+
const tronWeb = this.getClient();
|
|
4822
5346
|
if (!tronWeb) return {};
|
|
4823
5347
|
return await getTronBalances(tronWeb, address, tokens);
|
|
4824
5348
|
}
|
|
@@ -4826,35 +5350,6 @@ class TronChainStrategy {
|
|
|
4826
5350
|
if (!address) return false;
|
|
4827
5351
|
return /^T[1-9A-HJ-NP-Za-km-z]{33}$/.test(address);
|
|
4828
5352
|
}
|
|
4829
|
-
async estimateGasRequirement(params) {
|
|
4830
|
-
const {
|
|
4831
|
-
selectedToken,
|
|
4832
|
-
nativeTokenSymbol,
|
|
4833
|
-
amount,
|
|
4834
|
-
balances,
|
|
4835
|
-
reserveFallback
|
|
4836
|
-
} = params;
|
|
4837
|
-
const nativeSym = nativeTokenSymbol.toUpperCase();
|
|
4838
|
-
const isNativeSelected = nativeSym === (selectedToken?.symbol ?? "").toUpperCase();
|
|
4839
|
-
const nativeBalance = Number(balances[nativeSym]?.balance ?? 0);
|
|
4840
|
-
const estimatedGas = null;
|
|
4841
|
-
const requiredNative = reserveFallback;
|
|
4842
|
-
const amountNum = amount ?? 0;
|
|
4843
|
-
let hasEnoughGas = true;
|
|
4844
|
-
if (isNativeSelected) {
|
|
4845
|
-
hasEnoughGas = nativeBalance - amountNum >= requiredNative;
|
|
4846
|
-
} else {
|
|
4847
|
-
hasEnoughGas = nativeBalance >= requiredNative;
|
|
4848
|
-
}
|
|
4849
|
-
return {
|
|
4850
|
-
estimatedGas,
|
|
4851
|
-
nativeBalance,
|
|
4852
|
-
requiredNative,
|
|
4853
|
-
hasEnoughGas,
|
|
4854
|
-
nativeSym,
|
|
4855
|
-
isNativeSelected
|
|
4856
|
-
};
|
|
4857
|
-
}
|
|
4858
5353
|
validateSteps(steps) {
|
|
4859
5354
|
console.log("validateSteps");
|
|
4860
5355
|
if (!steps?.length) {
|
|
@@ -4871,8 +5366,7 @@ class TronChainStrategy {
|
|
|
4871
5366
|
}
|
|
4872
5367
|
}
|
|
4873
5368
|
async executeSteps(steps, _context, onFirstHash) {
|
|
4874
|
-
|
|
4875
|
-
const tronWeb = this.getTronWeb();
|
|
5369
|
+
const tronWeb = this.getClient();
|
|
4876
5370
|
if (!tronWeb) {
|
|
4877
5371
|
throw new ProviderNotAvailableError("tron");
|
|
4878
5372
|
}
|
|
@@ -4966,7 +5460,7 @@ class TronChainStrategy {
|
|
|
4966
5460
|
}
|
|
4967
5461
|
async waitForCompletion(txHash) {
|
|
4968
5462
|
try {
|
|
4969
|
-
const tronWeb = this.
|
|
5463
|
+
const tronWeb = this.getClient();
|
|
4970
5464
|
if (!tronWeb) {
|
|
4971
5465
|
throw new ProviderNotAvailableError("tron");
|
|
4972
5466
|
}
|
|
@@ -4975,6 +5469,7 @@ class TronChainStrategy {
|
|
|
4975
5469
|
);
|
|
4976
5470
|
const deadline = Date.now() + TRON_CONFIG.timeout;
|
|
4977
5471
|
let txBlockNumber = null;
|
|
5472
|
+
let actualFeeTrx = null;
|
|
4978
5473
|
while (Date.now() < deadline && !txBlockNumber) {
|
|
4979
5474
|
try {
|
|
4980
5475
|
const info = await tronWeb.trx.getTransactionInfo(txHash);
|
|
@@ -5009,7 +5504,9 @@ class TronChainStrategy {
|
|
|
5009
5504
|
};
|
|
5010
5505
|
}
|
|
5011
5506
|
txBlockNumber = info.blockNumber;
|
|
5012
|
-
|
|
5507
|
+
const feeSun = info.fee || 0;
|
|
5508
|
+
actualFeeTrx = feeSun / 1e6;
|
|
5509
|
+
console.log(`TRON transaction found in block ${txBlockNumber}, fee: ${actualFeeTrx} TRX`);
|
|
5013
5510
|
}
|
|
5014
5511
|
} catch (e) {
|
|
5015
5512
|
console.debug("TRON getTransactionInfo error:", e);
|
|
@@ -5037,7 +5534,9 @@ class TronChainStrategy {
|
|
|
5037
5534
|
`TRON transaction confirmed in block ${txBlockNumber} with ${confirmations} confirmations`
|
|
5038
5535
|
);
|
|
5039
5536
|
return {
|
|
5040
|
-
completed: true
|
|
5537
|
+
completed: true,
|
|
5538
|
+
actualFeeValue: actualFeeTrx?.toString(),
|
|
5539
|
+
actualFeeSymbol: "TRX"
|
|
5041
5540
|
};
|
|
5042
5541
|
}
|
|
5043
5542
|
console.log(
|
|
@@ -5066,7 +5565,7 @@ class TronChainStrategy {
|
|
|
5066
5565
|
};
|
|
5067
5566
|
}
|
|
5068
5567
|
}
|
|
5069
|
-
|
|
5568
|
+
getClient() {
|
|
5070
5569
|
return typeof window !== "undefined" ? window.tronWeb : void 0;
|
|
5071
5570
|
}
|
|
5072
5571
|
/**
|
|
@@ -5383,7 +5882,7 @@ class TronChainStrategy {
|
|
|
5383
5882
|
*/
|
|
5384
5883
|
async checkSolidified(txHash) {
|
|
5385
5884
|
try {
|
|
5386
|
-
const tronWeb = this.
|
|
5885
|
+
const tronWeb = this.getClient();
|
|
5387
5886
|
if (!tronWeb) {
|
|
5388
5887
|
return false;
|
|
5389
5888
|
}
|
|
@@ -5529,7 +6028,7 @@ const SettingsModal = ({ isOpen, onClose }) => {
|
|
|
5529
6028
|
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
|
|
5530
6029
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
5531
6030
|
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm font-medium leading-4", children: t("settings.gasOnDestination") }),
|
|
5532
|
-
/* @__PURE__ */ jsx(Tip, { text: t("settings.gasOnDestination"), children: /* @__PURE__ */ jsx(
|
|
6031
|
+
/* @__PURE__ */ jsx(Tip, { text: t("settings.gasOnDestination"), children: /* @__PURE__ */ jsx(InfoIcon, { className: "w-4 h-4 text-muted-foreground" }) })
|
|
5533
6032
|
] }),
|
|
5534
6033
|
/* @__PURE__ */ jsx("p", { className: "text-foreground text-sm font-medium leading-4", children: formatUsd(gasUsdValue) })
|
|
5535
6034
|
] }),
|
|
@@ -5567,7 +6066,7 @@ const SettingsModal = ({ isOpen, onClose }) => {
|
|
|
5567
6066
|
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
|
|
5568
6067
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
5569
6068
|
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm font-medium leading-4", children: t("settings.slippageTolerance") }),
|
|
5570
|
-
/* @__PURE__ */ jsx(Tip, { text: t("settings.slippageTolerance"), children: /* @__PURE__ */ jsx(
|
|
6069
|
+
/* @__PURE__ */ jsx(Tip, { text: t("settings.slippageTolerance"), children: /* @__PURE__ */ jsx(InfoIcon, { className: "w-4 h-4 text-muted-foreground" }) })
|
|
5571
6070
|
] }),
|
|
5572
6071
|
slippageBps >= 500 && /* @__PURE__ */ jsx("p", { className: "text-destructive text-xs font-medium", children: t("settings.highSlippageWarning", {
|
|
5573
6072
|
defaultValue: "High slippage warning"
|
|
@@ -5597,7 +6096,7 @@ const SettingsModal = ({ isOpen, onClose }) => {
|
|
|
5597
6096
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-5", children: [
|
|
5598
6097
|
/* @__PURE__ */ jsx("div", { className: "flex justify-between items-center", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
5599
6098
|
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm font-medium leading-4", children: t("settings.routePriority") }),
|
|
5600
|
-
/* @__PURE__ */ jsx(Tip, { text: t("settings.routePriority"), children: /* @__PURE__ */ jsx(
|
|
6099
|
+
/* @__PURE__ */ jsx(Tip, { text: t("settings.routePriority"), children: /* @__PURE__ */ jsx(InfoIcon, { className: "w-4 h-4 text-muted-foreground" }) })
|
|
5601
6100
|
] }) }),
|
|
5602
6101
|
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-end gap-2", children: routePresets.map((r) => /* @__PURE__ */ jsx(
|
|
5603
6102
|
Badge,
|
|
@@ -5630,7 +6129,7 @@ const TokenRow = ({
|
|
|
5630
6129
|
Button,
|
|
5631
6130
|
{
|
|
5632
6131
|
onClick: onPick,
|
|
5633
|
-
className: `w-full rounded-
|
|
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" : ""}`,
|
|
5634
6133
|
children: [
|
|
5635
6134
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
5636
6135
|
/* @__PURE__ */ jsx(
|
|
@@ -5651,7 +6150,7 @@ const TokenRow = ({
|
|
|
5651
6150
|
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-12 rounded-md" })
|
|
5652
6151
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5653
6152
|
/* @__PURE__ */ jsx("div", { className: "font-semibold text-foreground text-lg leading-4 truncate", children: hasAnyWallet ? formatBalance(balance) : "—" }),
|
|
5654
|
-
/* @__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) : "—" })
|
|
5655
6154
|
] }) })
|
|
5656
6155
|
]
|
|
5657
6156
|
}
|
|
@@ -5794,7 +6293,7 @@ const TokenSelectModal = ({
|
|
|
5794
6293
|
[groupedTokens.willChangeSrcChain]
|
|
5795
6294
|
);
|
|
5796
6295
|
const hasNoResults = tokensToRender.length === 0 && willChangeSrcTokens.length === 0;
|
|
5797
|
-
return /* @__PURE__ */ jsx(Dialog, { open: isOpen, onOpenChange: (open) => !open && handleClose(), children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-h-[90dvh] h-[90dvh] overflow-hidden flex flex-col", children: [
|
|
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: [
|
|
5798
6297
|
/* @__PURE__ */ jsx(DialogHeader, { className: "text-left", children: /* @__PURE__ */ jsx(DialogTitle, { children: t("bridge.selectToken") }) }),
|
|
5799
6298
|
/* @__PURE__ */ jsx(
|
|
5800
6299
|
SearchInput,
|
|
@@ -5808,24 +6307,26 @@ const TokenSelectModal = ({
|
|
|
5808
6307
|
/* @__PURE__ */ jsx(
|
|
5809
6308
|
Button,
|
|
5810
6309
|
{
|
|
5811
|
-
variant: tab === "my" ? "default" : "
|
|
6310
|
+
variant: tab === "my" ? "default" : "ghost",
|
|
5812
6311
|
onClick: () => setTab("my"),
|
|
5813
6312
|
size: "sm",
|
|
6313
|
+
className: cn(tab !== "my" && "bg-muted hover:bg-accent"),
|
|
5814
6314
|
children: t("bridge.myTokens")
|
|
5815
6315
|
}
|
|
5816
6316
|
),
|
|
5817
6317
|
/* @__PURE__ */ jsx(
|
|
5818
6318
|
Button,
|
|
5819
6319
|
{
|
|
5820
|
-
variant: tab === "all" ? "default" : "
|
|
6320
|
+
variant: tab === "all" ? "default" : "ghost",
|
|
5821
6321
|
onClick: () => setTab("all"),
|
|
5822
6322
|
size: "sm",
|
|
6323
|
+
className: cn(tab !== "all" && "bg-muted hover:bg-accent"),
|
|
5823
6324
|
children: t("bridge.allTokens")
|
|
5824
6325
|
}
|
|
5825
6326
|
)
|
|
5826
6327
|
] }),
|
|
5827
|
-
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto -
|
|
5828
|
-
effectiveTab === "my" && myTokens.length === 0 && /* @__PURE__ */ jsx("p", { className: "leading-4 text-base font-semibold text-muted-foreground uppercase
|
|
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") }),
|
|
5829
6330
|
tokensToRender.map(({ token, willChangeSrc }) => {
|
|
5830
6331
|
const bal = getBalance(token.symbol);
|
|
5831
6332
|
const usd = getTokenUsdValue(token.symbol, token.price?.usd);
|
|
@@ -5876,6 +6377,7 @@ const TokenSelectModal = ({
|
|
|
5876
6377
|
function useBridgeRefresh() {
|
|
5877
6378
|
const qc = useQueryClient();
|
|
5878
6379
|
const { srcAddress, dstAddress } = useAddresses();
|
|
6380
|
+
const { fromChain, toChain } = useChainsStore();
|
|
5879
6381
|
const { setLoading: setQLoading, triggerRefetch } = useBridgeQuoteStore();
|
|
5880
6382
|
const { hasAnyWallet } = useConnectedWalletsStore();
|
|
5881
6383
|
const [spinning, setSpinning] = useState(false);
|
|
@@ -5888,14 +6390,30 @@ function useBridgeRefresh() {
|
|
|
5888
6390
|
qc.invalidateQueries({ queryKey: ["chains"] })
|
|
5889
6391
|
];
|
|
5890
6392
|
if (hasAnyWallet()) {
|
|
5891
|
-
if (srcAddress)
|
|
6393
|
+
if (srcAddress) {
|
|
5892
6394
|
queries.push(
|
|
5893
6395
|
qc.invalidateQueries({ queryKey: ["srcTokens", srcAddress] })
|
|
5894
6396
|
);
|
|
5895
|
-
|
|
6397
|
+
if (fromChain?.chainKey) {
|
|
6398
|
+
queries.push(
|
|
6399
|
+
qc.invalidateQueries({
|
|
6400
|
+
queryKey: ["balances", fromChain.chainKey, srcAddress]
|
|
6401
|
+
})
|
|
6402
|
+
);
|
|
6403
|
+
}
|
|
6404
|
+
}
|
|
6405
|
+
if (dstAddress) {
|
|
5896
6406
|
queries.push(
|
|
5897
6407
|
qc.invalidateQueries({ queryKey: ["dstTokens", dstAddress] })
|
|
5898
6408
|
);
|
|
6409
|
+
if (toChain?.chainKey) {
|
|
6410
|
+
queries.push(
|
|
6411
|
+
qc.invalidateQueries({
|
|
6412
|
+
queryKey: ["balances", toChain.chainKey, dstAddress]
|
|
6413
|
+
})
|
|
6414
|
+
);
|
|
6415
|
+
}
|
|
6416
|
+
}
|
|
5899
6417
|
}
|
|
5900
6418
|
await Promise.all(queries);
|
|
5901
6419
|
triggerRefetch();
|
|
@@ -5906,6 +6424,8 @@ function useBridgeRefresh() {
|
|
|
5906
6424
|
qc,
|
|
5907
6425
|
srcAddress,
|
|
5908
6426
|
dstAddress,
|
|
6427
|
+
fromChain?.chainKey,
|
|
6428
|
+
toChain?.chainKey,
|
|
5909
6429
|
hasAnyWallet,
|
|
5910
6430
|
setQLoading,
|
|
5911
6431
|
triggerRefetch
|
|
@@ -5925,7 +6445,7 @@ const RefreshButton = () => {
|
|
|
5925
6445
|
onClick: handleRefresh,
|
|
5926
6446
|
disabled: spinning,
|
|
5927
6447
|
variant: "secondary",
|
|
5928
|
-
className: "
|
|
6448
|
+
className: "h-9 w-11",
|
|
5929
6449
|
size: "sm",
|
|
5930
6450
|
children: /* @__PURE__ */ jsx(
|
|
5931
6451
|
ReloadIcon,
|
|
@@ -5953,7 +6473,7 @@ const SelectTokenButton = ({
|
|
|
5953
6473
|
onClick,
|
|
5954
6474
|
size: "sm",
|
|
5955
6475
|
variant: "secondary",
|
|
5956
|
-
className: "shrink-0 gap-2
|
|
6476
|
+
className: "shrink-0 gap-2 h-9 !pl-2",
|
|
5957
6477
|
type: "button",
|
|
5958
6478
|
"aria-label": label,
|
|
5959
6479
|
children: [
|
|
@@ -6001,7 +6521,7 @@ const Toolbar = () => {
|
|
|
6001
6521
|
{
|
|
6002
6522
|
onClick: onOpenSettings,
|
|
6003
6523
|
size: "sm",
|
|
6004
|
-
className: "
|
|
6524
|
+
className: "h-9 w-11",
|
|
6005
6525
|
variant: "secondary",
|
|
6006
6526
|
children: /* @__PURE__ */ jsx(BoltIcon, { stroke: "currentColor" })
|
|
6007
6527
|
}
|
|
@@ -6282,11 +6802,16 @@ export {
|
|
|
6282
6802
|
EvaaBridge,
|
|
6283
6803
|
RoutePriority,
|
|
6284
6804
|
RouteType,
|
|
6805
|
+
addEvmNetworkFee,
|
|
6285
6806
|
addTonNetworkFee,
|
|
6807
|
+
addTronNetworkFee,
|
|
6286
6808
|
addrForApi,
|
|
6287
6809
|
buildAssetMatrix,
|
|
6288
6810
|
calculateMinReceived,
|
|
6289
6811
|
computeFeesUsdFromArray,
|
|
6812
|
+
estimateEvmNetworkFee,
|
|
6813
|
+
estimateTonNetworkFee,
|
|
6814
|
+
estimateTronNetworkFee,
|
|
6290
6815
|
findNativeMeta,
|
|
6291
6816
|
formatAddress,
|
|
6292
6817
|
formatBalance,
|