@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.cjs
CHANGED
|
@@ -34,6 +34,7 @@ const wagmi = require("wagmi");
|
|
|
34
34
|
const tronwalletAdapterReactHooks = require("@tronweb3/tronwallet-adapter-react-hooks");
|
|
35
35
|
const uiReact = require("@tonconnect/ui-react");
|
|
36
36
|
const core = require("@ton/core");
|
|
37
|
+
const ton = require("@ton/ton");
|
|
37
38
|
const reactQuery = require("@tanstack/react-query");
|
|
38
39
|
const utils = require("@/lib/utils");
|
|
39
40
|
const skeleton = require("@/components/ui/skeleton");
|
|
@@ -44,7 +45,6 @@ const lucideReact = require("lucide-react");
|
|
|
44
45
|
const framerMotion = require("framer-motion");
|
|
45
46
|
const accordion = require("@/components/ui/accordion");
|
|
46
47
|
const tooltip = require("@/components/ui/tooltip");
|
|
47
|
-
const ton = require("@ton/ton");
|
|
48
48
|
const sonner = require("sonner");
|
|
49
49
|
const reactDialog = require("@radix-ui/react-dialog");
|
|
50
50
|
const ethers = require("ethers");
|
|
@@ -53,8 +53,8 @@ const tronwalletAdapters = require("@tronweb3/tronwallet-adapters");
|
|
|
53
53
|
const card = require("@/components/ui/card");
|
|
54
54
|
const badge = require("@/components/ui/badge");
|
|
55
55
|
const common$1 = { "connecting": "Connecting…", "initializing": "Initializing...", "loading": "Loading...", "paste": "paste", "close": "Close", "zeroPlaceholder": "0", "nativeToken": "Native Token" };
|
|
56
|
-
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", "
|
|
57
|
-
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
|
|
56
|
+
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" };
|
|
57
|
+
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" };
|
|
58
58
|
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" };
|
|
59
59
|
const app$1 = { "stargateWidgetName": "Stargate Bridge Widget", "liveWidget": "Live Widget", "getStarted": "Get Started" };
|
|
60
60
|
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" } };
|
|
@@ -69,8 +69,8 @@ const en = {
|
|
|
69
69
|
errors: errors$1
|
|
70
70
|
};
|
|
71
71
|
const common = { "connecting": "Подключение…", "initializing": "Инициализация...", "loading": "Загрузка...", "paste": "вставить", "close": "Закрыть", "zeroPlaceholder": "0", "nativeToken": "Нативный токен" };
|
|
72
|
-
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", "
|
|
73
|
-
const bridge = { "max": "Макс", "sourceNetwork": "Исходная сеть", "destinationNetwork": "Целевая сеть", "selectToken": "Выбрать токен", "selectNetwork": "Выбрать сеть", "searchToken": "Поиск токена", "searchDestinationChain": "Поиск целевой сети", "myTokens": "Мои токены", "allTokens": "Все токены", "willChangeSourceChain": "Сменит исходную сеть", "noBalancesFound": "Балансы не найдены.", "noResults": "Нет результатов", "sendToAnotherAddress": "Отправить на другой адрес", "youWillReceive": "Вы получите", "anotherAddressPlaceholder": "Адрес", "addressDoesntMatch": "Адрес не соответствует сети {{network}}", "checkBeforeTransfer": "Проверьте корректность перед переводом" };
|
|
72
|
+
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": "Подключить кошелёк" };
|
|
73
|
+
const bridge = { "max": "Макс", "sourceNetwork": "Исходная сеть", "destinationNetwork": "Целевая сеть", "selectToken": "Выбрать токен", "selectNetwork": "Выбрать сеть", "searchToken": "Поиск токена", "searchDestinationChain": "Поиск целевой сети", "myTokens": "Мои токены", "allTokens": "Все токены", "willChangeSourceChain": "Сменит исходную сеть", "willChangeSourceNetworkAndToken": "Сменит исходную сеть и токен", "noBalancesFound": "Балансы не найдены.", "noResults": "Нет результатов", "sendToAnotherAddress": "Отправить на другой адрес", "youWillReceive": "Вы получите", "anotherAddressPlaceholder": "Адрес", "addressDoesntMatch": "Адрес не соответствует сети {{network}}", "checkBeforeTransfer": "Проверьте корректность перед переводом" };
|
|
74
74
|
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": "Ошибка котировки" };
|
|
75
75
|
const app = { "stargateWidgetName": "Виджет Stargate Bridge", "liveWidget": "Живой виджет", "getStarted": "Начало работы" };
|
|
76
76
|
const settings = { "title": "Настройки", "gasOnDestination": "Газ на назначении", "slippageTolerance": "Толерантность к проскальзыванию", "routePriority": "Приоритет маршрута", "highSlippageWarning": "Высокое проскальзывание", "gasPresets": { "auto": "Авто", "none": "Нет", "medium": "Средний", "max": "Макс" }, "routePresets": { "fastest": "Быстрейший", "cheapest": "Дешевейший", "recommended": "Рекомендуемый" } };
|
|
@@ -121,72 +121,282 @@ function BridgeI18nProvider({
|
|
|
121
121
|
function useBridgeTranslation() {
|
|
122
122
|
return reactI18next.useTranslation("evaa-bridge", { i18n: bridgeI18n });
|
|
123
123
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
124
|
+
function createJSONStorage(getStorage, options) {
|
|
125
|
+
let storage;
|
|
126
|
+
try {
|
|
127
|
+
storage = getStorage();
|
|
128
|
+
} catch (e) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const persistStorage = {
|
|
132
|
+
getItem: (name) => {
|
|
133
|
+
var _a;
|
|
134
|
+
const parse = (str2) => {
|
|
135
|
+
if (str2 === null) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
return JSON.parse(str2, void 0);
|
|
139
|
+
};
|
|
140
|
+
const str = (_a = storage.getItem(name)) != null ? _a : null;
|
|
141
|
+
if (str instanceof Promise) {
|
|
142
|
+
return str.then(parse);
|
|
139
143
|
}
|
|
140
|
-
return
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (!prev || !data) return false;
|
|
160
|
-
if (prev.length !== data.length) return false;
|
|
161
|
-
for (let i = 0; i < prev.length; i++) {
|
|
162
|
-
if (prev[i]?.chainKey !== data[i]?.chainKey) return false;
|
|
144
|
+
return parse(str);
|
|
145
|
+
},
|
|
146
|
+
setItem: (name, newValue) => storage.setItem(name, JSON.stringify(newValue, void 0)),
|
|
147
|
+
removeItem: (name) => storage.removeItem(name)
|
|
148
|
+
};
|
|
149
|
+
return persistStorage;
|
|
150
|
+
}
|
|
151
|
+
const toThenable = (fn) => (input2) => {
|
|
152
|
+
try {
|
|
153
|
+
const result = fn(input2);
|
|
154
|
+
if (result instanceof Promise) {
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
then(onFulfilled) {
|
|
159
|
+
return toThenable(onFulfilled)(result);
|
|
160
|
+
},
|
|
161
|
+
catch(_onRejected) {
|
|
162
|
+
return this;
|
|
163
163
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (!prev && !data) return true;
|
|
173
|
-
if (!prev || !data) return false;
|
|
174
|
-
if (prev.length !== data.length) return false;
|
|
175
|
-
for (let i = 0; i < prev.length; i++) {
|
|
176
|
-
if (prev[i]?.chainKey !== data[i]?.chainKey) return false;
|
|
164
|
+
};
|
|
165
|
+
} catch (e) {
|
|
166
|
+
return {
|
|
167
|
+
then(_onFulfilled) {
|
|
168
|
+
return this;
|
|
169
|
+
},
|
|
170
|
+
catch(onRejected) {
|
|
171
|
+
return toThenable(onRejected)(e);
|
|
177
172
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
const persistImpl = (config, baseOptions) => (set, get, api) => {
|
|
177
|
+
let options = {
|
|
178
|
+
storage: createJSONStorage(() => localStorage),
|
|
179
|
+
partialize: (state) => state,
|
|
180
|
+
version: 0,
|
|
181
|
+
merge: (persistedState, currentState) => ({
|
|
182
|
+
...currentState,
|
|
183
|
+
...persistedState
|
|
184
|
+
}),
|
|
185
|
+
...baseOptions
|
|
186
|
+
};
|
|
187
|
+
let hasHydrated = false;
|
|
188
|
+
const hydrationListeners = /* @__PURE__ */ new Set();
|
|
189
|
+
const finishHydrationListeners = /* @__PURE__ */ new Set();
|
|
190
|
+
let storage = options.storage;
|
|
191
|
+
if (!storage) {
|
|
192
|
+
return config(
|
|
193
|
+
(...args) => {
|
|
194
|
+
console.warn(
|
|
195
|
+
`[zustand persist middleware] Unable to update item '${options.name}', the given storage is currently unavailable.`
|
|
196
|
+
);
|
|
197
|
+
set(...args);
|
|
198
|
+
},
|
|
199
|
+
get,
|
|
200
|
+
api
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
const setItem = () => {
|
|
204
|
+
const state = options.partialize({ ...get() });
|
|
205
|
+
return storage.setItem(options.name, {
|
|
206
|
+
state,
|
|
207
|
+
version: options.version
|
|
208
|
+
});
|
|
209
|
+
};
|
|
210
|
+
const savedSetState = api.setState;
|
|
211
|
+
api.setState = (state, replace) => {
|
|
212
|
+
savedSetState(state, replace);
|
|
213
|
+
return setItem();
|
|
214
|
+
};
|
|
215
|
+
const configResult = config(
|
|
216
|
+
(...args) => {
|
|
217
|
+
set(...args);
|
|
218
|
+
return setItem();
|
|
219
|
+
},
|
|
220
|
+
get,
|
|
221
|
+
api
|
|
222
|
+
);
|
|
223
|
+
api.getInitialState = () => configResult;
|
|
224
|
+
let stateFromStorage;
|
|
225
|
+
const hydrate = () => {
|
|
226
|
+
var _a, _b;
|
|
227
|
+
if (!storage) return;
|
|
228
|
+
hasHydrated = false;
|
|
229
|
+
hydrationListeners.forEach((cb) => {
|
|
230
|
+
var _a2;
|
|
231
|
+
return cb((_a2 = get()) != null ? _a2 : configResult);
|
|
232
|
+
});
|
|
233
|
+
const postRehydrationCallback = ((_b = options.onRehydrateStorage) == null ? void 0 : _b.call(options, (_a = get()) != null ? _a : configResult)) || void 0;
|
|
234
|
+
return toThenable(storage.getItem.bind(storage))(options.name).then((deserializedStorageValue) => {
|
|
235
|
+
if (deserializedStorageValue) {
|
|
236
|
+
if (typeof deserializedStorageValue.version === "number" && deserializedStorageValue.version !== options.version) {
|
|
237
|
+
if (options.migrate) {
|
|
238
|
+
const migration = options.migrate(
|
|
239
|
+
deserializedStorageValue.state,
|
|
240
|
+
deserializedStorageValue.version
|
|
241
|
+
);
|
|
242
|
+
if (migration instanceof Promise) {
|
|
243
|
+
return migration.then((result) => [true, result]);
|
|
244
|
+
}
|
|
245
|
+
return [true, migration];
|
|
246
|
+
}
|
|
247
|
+
console.error(
|
|
248
|
+
`State loaded from storage couldn't be migrated since no migrate function was provided`
|
|
249
|
+
);
|
|
250
|
+
} else {
|
|
251
|
+
return [false, deserializedStorageValue.state];
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return [false, void 0];
|
|
255
|
+
}).then((migrationResult) => {
|
|
256
|
+
var _a2;
|
|
257
|
+
const [migrated, migratedState] = migrationResult;
|
|
258
|
+
stateFromStorage = options.merge(
|
|
259
|
+
migratedState,
|
|
260
|
+
(_a2 = get()) != null ? _a2 : configResult
|
|
261
|
+
);
|
|
262
|
+
set(stateFromStorage, true);
|
|
263
|
+
if (migrated) {
|
|
264
|
+
return setItem();
|
|
265
|
+
}
|
|
266
|
+
}).then(() => {
|
|
267
|
+
postRehydrationCallback == null ? void 0 : postRehydrationCallback(stateFromStorage, void 0);
|
|
268
|
+
stateFromStorage = get();
|
|
269
|
+
hasHydrated = true;
|
|
270
|
+
finishHydrationListeners.forEach((cb) => cb(stateFromStorage));
|
|
271
|
+
}).catch((e) => {
|
|
272
|
+
postRehydrationCallback == null ? void 0 : postRehydrationCallback(void 0, e);
|
|
273
|
+
});
|
|
274
|
+
};
|
|
275
|
+
api.persist = {
|
|
276
|
+
setOptions: (newOptions) => {
|
|
277
|
+
options = {
|
|
278
|
+
...options,
|
|
279
|
+
...newOptions
|
|
280
|
+
};
|
|
281
|
+
if (newOptions.storage) {
|
|
282
|
+
storage = newOptions.storage;
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
clearStorage: () => {
|
|
286
|
+
storage == null ? void 0 : storage.removeItem(options.name);
|
|
287
|
+
},
|
|
288
|
+
getOptions: () => options,
|
|
289
|
+
rehydrate: () => hydrate(),
|
|
290
|
+
hasHydrated: () => hasHydrated,
|
|
291
|
+
onHydrate: (cb) => {
|
|
292
|
+
hydrationListeners.add(cb);
|
|
293
|
+
return () => {
|
|
294
|
+
hydrationListeners.delete(cb);
|
|
295
|
+
};
|
|
296
|
+
},
|
|
297
|
+
onFinishHydration: (cb) => {
|
|
298
|
+
finishHydrationListeners.add(cb);
|
|
299
|
+
return () => {
|
|
300
|
+
finishHydrationListeners.delete(cb);
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
if (!options.skipHydration) {
|
|
305
|
+
hydrate();
|
|
306
|
+
}
|
|
307
|
+
return stateFromStorage || configResult;
|
|
308
|
+
};
|
|
309
|
+
const persist = persistImpl;
|
|
310
|
+
const useChainsStore = zustand.create()(
|
|
311
|
+
persist(
|
|
312
|
+
(set, get) => ({
|
|
313
|
+
chains: void 0,
|
|
314
|
+
fromChain: void 0,
|
|
315
|
+
toChain: void 0,
|
|
316
|
+
allowedFromChains: void 0,
|
|
317
|
+
allowedToChains: void 0,
|
|
318
|
+
isLoadingToChains: false,
|
|
319
|
+
setChains: async (data) => {
|
|
320
|
+
const prev = get().chains;
|
|
321
|
+
const same = (() => {
|
|
322
|
+
if (!prev && !data) return true;
|
|
323
|
+
if (!prev || !data) return false;
|
|
324
|
+
if (prev.length !== data.length) return false;
|
|
325
|
+
for (let i = 0; i < prev.length; i++) {
|
|
326
|
+
if (prev[i]?.chainKey !== data[i]?.chainKey) return false;
|
|
327
|
+
}
|
|
328
|
+
return true;
|
|
329
|
+
})();
|
|
330
|
+
if (same) return;
|
|
331
|
+
set({ chains: data });
|
|
332
|
+
},
|
|
333
|
+
setFromChain: (data) => {
|
|
334
|
+
const prev = get().fromChain;
|
|
335
|
+
if ((prev?.chainKey ?? null) === (data?.chainKey ?? null)) return;
|
|
336
|
+
set({ fromChain: data });
|
|
337
|
+
},
|
|
338
|
+
setToChain: (data) => {
|
|
339
|
+
const prev = get().toChain;
|
|
340
|
+
if ((prev?.chainKey ?? null) === (data?.chainKey ?? null)) return;
|
|
341
|
+
set({ toChain: data });
|
|
342
|
+
},
|
|
343
|
+
setAllowedFromChains: (data) => {
|
|
344
|
+
const prev = get().allowedFromChains;
|
|
345
|
+
const same = (() => {
|
|
346
|
+
if (!prev && !data) return true;
|
|
347
|
+
if (!prev || !data) return false;
|
|
348
|
+
if (prev.length !== data.length) return false;
|
|
349
|
+
for (let i = 0; i < prev.length; i++) {
|
|
350
|
+
if (prev[i]?.chainKey !== data[i]?.chainKey) return false;
|
|
351
|
+
}
|
|
352
|
+
return true;
|
|
353
|
+
})();
|
|
354
|
+
if (same) return;
|
|
355
|
+
set({ allowedFromChains: data });
|
|
356
|
+
},
|
|
357
|
+
setAllowedToChains: (data) => {
|
|
358
|
+
const prev = get().allowedToChains;
|
|
359
|
+
const same = (() => {
|
|
360
|
+
if (!prev && !data) return true;
|
|
361
|
+
if (!prev || !data) return false;
|
|
362
|
+
if (prev.length !== data.length) return false;
|
|
363
|
+
for (let i = 0; i < prev.length; i++) {
|
|
364
|
+
if (prev[i]?.chainKey !== data[i]?.chainKey) return false;
|
|
365
|
+
}
|
|
366
|
+
return true;
|
|
367
|
+
})();
|
|
368
|
+
if (same) return;
|
|
369
|
+
set({ allowedToChains: data });
|
|
370
|
+
},
|
|
371
|
+
setIsLoadingToChains: (v) => set({ isLoadingToChains: v }),
|
|
372
|
+
swapChains: () => set((state) => ({
|
|
373
|
+
fromChain: state.toChain,
|
|
374
|
+
toChain: state.fromChain
|
|
375
|
+
}))
|
|
376
|
+
}),
|
|
377
|
+
{
|
|
378
|
+
name: "evaa-bridge-chains",
|
|
379
|
+
storage: createJSONStorage(() => sessionStorage),
|
|
380
|
+
partialize: (state) => ({
|
|
381
|
+
fromChain: state.fromChain,
|
|
382
|
+
toChain: state.toChain
|
|
383
|
+
})
|
|
384
|
+
}
|
|
385
|
+
)
|
|
386
|
+
);
|
|
387
|
+
function normalizeTickerSymbol$1(s) {
|
|
388
|
+
return s.toUpperCase().replace(/₮/g, "T").replace(/[^A-Z0-9]/g, "");
|
|
389
|
+
}
|
|
390
|
+
function normalizeTokenSymbol(token) {
|
|
391
|
+
let normalizedSymbol = token.symbol.toUpperCase().replace(/₮/g, "T");
|
|
392
|
+
normalizedSymbol = normalizedSymbol.replace(/^S\*/, "");
|
|
393
|
+
normalizedSymbol = normalizedSymbol.replace(/0\.S$/i, "").replace(/0$/, "").replace(/\.S$/i, "");
|
|
394
|
+
normalizedSymbol = normalizedSymbol.replace(/\.E$/i, "").replace(/\.N$/i, "");
|
|
395
|
+
return {
|
|
396
|
+
...token,
|
|
397
|
+
symbol: normalizedSymbol
|
|
398
|
+
};
|
|
399
|
+
}
|
|
190
400
|
const POPULAR_ORDER = [
|
|
191
401
|
"USDT",
|
|
192
402
|
"USDC",
|
|
@@ -222,6 +432,16 @@ function buildAssetMatrix(tokens) {
|
|
|
222
432
|
if (!symbol) continue;
|
|
223
433
|
(m[symbol] || (m[symbol] = {}))[t.chainKey] = t;
|
|
224
434
|
}
|
|
435
|
+
const chainStats = {};
|
|
436
|
+
Object.values(m).forEach((byChain) => {
|
|
437
|
+
Object.keys(byChain).forEach((chainKey) => {
|
|
438
|
+
chainStats[chainKey] = (chainStats[chainKey] || 0) + 1;
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
console.log(
|
|
442
|
+
`[DEBUG] Asset matrix built: ${Object.keys(m).length} assets across chains:`,
|
|
443
|
+
chainStats
|
|
444
|
+
);
|
|
225
445
|
return m;
|
|
226
446
|
}
|
|
227
447
|
function listAssetsForSelect(tokens) {
|
|
@@ -231,11 +451,11 @@ function listAssetsForSelect(tokens) {
|
|
|
231
451
|
const preferred = pickOrder.map((k) => byChain[k]).find(Boolean) ?? Object.values(byChain)[0];
|
|
232
452
|
return preferred;
|
|
233
453
|
});
|
|
234
|
-
const byNorm = new Map(base.map((e) => [
|
|
454
|
+
const byNorm = new Map(base.map((e) => [normalizeTickerSymbol$1(e.symbol), e]));
|
|
235
455
|
const used = /* @__PURE__ */ new Set();
|
|
236
456
|
const popular = [];
|
|
237
457
|
for (const s of POPULAR_ORDER) {
|
|
238
|
-
const key =
|
|
458
|
+
const key = normalizeTickerSymbol$1(s);
|
|
239
459
|
const item = byNorm.get(key);
|
|
240
460
|
if (item && !used.has(key)) {
|
|
241
461
|
popular.push(item);
|
|
@@ -243,11 +463,11 @@ function listAssetsForSelect(tokens) {
|
|
|
243
463
|
}
|
|
244
464
|
}
|
|
245
465
|
const stable = base.filter((e) => {
|
|
246
|
-
const k =
|
|
466
|
+
const k = normalizeTickerSymbol$1(e.symbol);
|
|
247
467
|
return !used.has(k) && STABLES.has(k);
|
|
248
468
|
}).sort((a, b) => a.symbol.localeCompare(b.symbol));
|
|
249
|
-
for (const e of stable) used.add(
|
|
250
|
-
const other = base.filter((e) => !used.has(
|
|
469
|
+
for (const e of stable) used.add(normalizeTickerSymbol$1(e.symbol));
|
|
470
|
+
const other = base.filter((e) => !used.has(normalizeTickerSymbol$1(e.symbol))).sort((a, b) => a.symbol.localeCompare(b.symbol));
|
|
251
471
|
return [...popular, ...stable, ...other];
|
|
252
472
|
}
|
|
253
473
|
function resolveTokenOnChain(tokens, assetSymbol, chainKey) {
|
|
@@ -652,163 +872,636 @@ function isAddressValidForChain(chainKey, addr) {
|
|
|
652
872
|
if (chainKey === "tron") return isTronAddress(addr);
|
|
653
873
|
return isEvmAddress(addr);
|
|
654
874
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
refetchTrigger,
|
|
663
|
-
setLoading: setQuoteLoading,
|
|
664
|
-
setQuote,
|
|
665
|
-
setError: setQError,
|
|
666
|
-
setNoRoute,
|
|
667
|
-
resetWithIdle
|
|
668
|
-
} = useBridgeQuoteStore();
|
|
669
|
-
const input2 = inputAmount;
|
|
670
|
-
const srcTokenOnFrom = react.useMemo(
|
|
671
|
-
() => resolveTokenOnChainFromMatrix$2(
|
|
672
|
-
assetMatrix,
|
|
673
|
-
selectedAssetSymbol,
|
|
674
|
-
fromChain?.chainKey
|
|
675
|
-
),
|
|
676
|
-
[assetMatrix, selectedAssetSymbol, fromChain?.chainKey]
|
|
677
|
-
);
|
|
678
|
-
const dstTokenOnTo = react.useMemo(
|
|
679
|
-
() => resolveTokenOnChainFromMatrix$2(
|
|
680
|
-
assetMatrix,
|
|
681
|
-
selectedAssetSymbol,
|
|
682
|
-
toChain?.chainKey
|
|
683
|
-
),
|
|
684
|
-
[assetMatrix, selectedAssetSymbol, toChain?.chainKey]
|
|
685
|
-
);
|
|
686
|
-
const [loading, setLoading] = react.useState(false);
|
|
687
|
-
react.useEffect(() => {
|
|
688
|
-
if (!input2 || input2 === "") {
|
|
689
|
-
setLoading(false);
|
|
690
|
-
resetWithIdle();
|
|
691
|
-
}
|
|
692
|
-
}, [input2, resetWithIdle]);
|
|
693
|
-
react.useEffect(() => {
|
|
694
|
-
const resetUi = (withError) => {
|
|
695
|
-
setLoading(false);
|
|
696
|
-
{
|
|
697
|
-
resetWithIdle();
|
|
698
|
-
}
|
|
699
|
-
};
|
|
700
|
-
if (!input2 || Number(input2) <= 0 || !fromChain?.chainKey || !toChain?.chainKey || !srcTokenOnFrom || !dstTokenOnTo || !srcAddress || !dstAddress) {
|
|
701
|
-
resetUi();
|
|
702
|
-
return;
|
|
703
|
-
}
|
|
704
|
-
if (!isAddressValidForChain(fromChain.chainKey, srcAddress) || !isAddressValidForChain(toChain.chainKey, dstAddress)) {
|
|
705
|
-
resetUi();
|
|
706
|
-
return;
|
|
707
|
-
}
|
|
708
|
-
if (fromChain.chainKey === toChain.chainKey) {
|
|
709
|
-
resetUi();
|
|
710
|
-
return;
|
|
711
|
-
}
|
|
712
|
-
let cancelled = false;
|
|
713
|
-
const run = async () => {
|
|
714
|
-
try {
|
|
715
|
-
setLoading(true);
|
|
716
|
-
setQuoteLoading?.();
|
|
717
|
-
if (assetMatrix && selectedAssetSymbol) {
|
|
718
|
-
const tokenMatrix = assetMatrix[selectedAssetSymbol.toUpperCase()];
|
|
719
|
-
if (!tokenMatrix || !tokenMatrix[fromChain.chainKey] || !tokenMatrix[toChain.chainKey]) {
|
|
720
|
-
if (!cancelled) {
|
|
721
|
-
resetUi();
|
|
722
|
-
}
|
|
723
|
-
return;
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
const srcAmountLD = toLD(input2, srcTokenOnFrom.decimals);
|
|
727
|
-
const srcAddrApi = addrForApi(fromChain.chainKey, srcAddress);
|
|
728
|
-
const dstAddrApi = addrForApi(toChain.chainKey, dstAddress);
|
|
729
|
-
const dstNativeAmount = getDstNativeAmount(toChain.chainKey);
|
|
730
|
-
const slippageDecimal = getSlippageDecimal();
|
|
731
|
-
const approximateMinLD = BigInt(srcAmountLD) * BigInt(9500) / BigInt(1e4);
|
|
732
|
-
const quoteRoute = await getQuotesByPriority({
|
|
733
|
-
srcChainKey: fromChain.chainKey,
|
|
734
|
-
dstChainKey: toChain.chainKey,
|
|
735
|
-
srcToken: srcTokenOnFrom.address,
|
|
736
|
-
dstToken: dstTokenOnTo.address,
|
|
737
|
-
srcAmountLD,
|
|
738
|
-
dstAmountMinLD: approximateMinLD.toString(),
|
|
739
|
-
srcAddress: srcAddrApi,
|
|
740
|
-
dstAddress: dstAddrApi,
|
|
741
|
-
dstNativeAmount,
|
|
742
|
-
slippage: slippageDecimal,
|
|
743
|
-
routePriority
|
|
744
|
-
});
|
|
745
|
-
if (quoteRoute?.route === null) {
|
|
746
|
-
setNoRoute(true);
|
|
747
|
-
if (!cancelled) resetUi();
|
|
748
|
-
return;
|
|
749
|
-
} else {
|
|
750
|
-
setNoRoute(false);
|
|
751
|
-
}
|
|
752
|
-
if (!quoteRoute || !quoteRoute.dstAmount || BigInt(quoteRoute.dstAmount) === BigInt(0)) {
|
|
753
|
-
if (!cancelled) resetUi();
|
|
754
|
-
return;
|
|
755
|
-
}
|
|
756
|
-
if (cancelled) return;
|
|
757
|
-
setQuote(quoteRoute);
|
|
758
|
-
} catch {
|
|
759
|
-
if (!cancelled) {
|
|
760
|
-
resetUi();
|
|
761
|
-
}
|
|
762
|
-
} finally {
|
|
763
|
-
if (!cancelled) setLoading(false);
|
|
764
|
-
}
|
|
765
|
-
};
|
|
766
|
-
run();
|
|
767
|
-
return () => {
|
|
768
|
-
cancelled = true;
|
|
769
|
-
};
|
|
770
|
-
}, [
|
|
771
|
-
input2,
|
|
772
|
-
fromChain?.chainKey,
|
|
773
|
-
toChain?.chainKey,
|
|
774
|
-
srcTokenOnFrom,
|
|
775
|
-
dstTokenOnTo,
|
|
776
|
-
srcAddress,
|
|
777
|
-
dstAddress,
|
|
778
|
-
setQuoteLoading,
|
|
779
|
-
setQuote,
|
|
780
|
-
setQError,
|
|
781
|
-
slippageBps,
|
|
782
|
-
routePriority,
|
|
783
|
-
refetchTrigger
|
|
784
|
-
]);
|
|
785
|
-
return { loading };
|
|
786
|
-
}
|
|
787
|
-
async function getChains() {
|
|
788
|
-
const res = await fetch("https://stargate.finance/api/v1/chains", {
|
|
789
|
-
credentials: "same-origin"
|
|
790
|
-
});
|
|
791
|
-
if (!res.ok) {
|
|
792
|
-
throw new Error(`Failed to load chains: ${res.status}`);
|
|
875
|
+
const ChainStrategyContext = react.createContext(void 0);
|
|
876
|
+
function useChainStrategies() {
|
|
877
|
+
const context = react.useContext(ChainStrategyContext);
|
|
878
|
+
if (!context) {
|
|
879
|
+
throw new Error(
|
|
880
|
+
"useChainStrategies must be used within ChainStrategyProvider"
|
|
881
|
+
);
|
|
793
882
|
}
|
|
794
|
-
|
|
795
|
-
return Array.isArray(data) ? data : data.chains ?? [];
|
|
883
|
+
return context;
|
|
796
884
|
}
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
885
|
+
const truncateToDecimals = (num, decimals) => {
|
|
886
|
+
if (!isFinite(num) || isNaN(num)) return "0.00";
|
|
887
|
+
const multiplier = Math.pow(10, decimals);
|
|
888
|
+
const truncated = Math.floor(num * multiplier) / multiplier;
|
|
889
|
+
return truncated.toFixed(decimals);
|
|
890
|
+
};
|
|
891
|
+
const formatTokenAmount = (amount, symbol, options) => {
|
|
892
|
+
const normalizedSymbol = (symbol ?? "").toUpperCase();
|
|
893
|
+
if (["USDT", "USDC", "DAI", "BUSD"].includes(normalizedSymbol) && amount >= 1) {
|
|
894
|
+
return `${Math.floor(amount)} ${normalizedSymbol}`;
|
|
803
895
|
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
896
|
+
if (options?.decimals !== void 0) {
|
|
897
|
+
return `${amount.toFixed(options.decimals)} ${normalizedSymbol}`;
|
|
898
|
+
}
|
|
899
|
+
if (amount >= 1) {
|
|
900
|
+
return `${amount.toFixed(0)} ${normalizedSymbol}`;
|
|
901
|
+
} else if (amount >= 1e-3) {
|
|
902
|
+
return `${amount.toFixed(3)} ${normalizedSymbol}`;
|
|
903
|
+
} else {
|
|
904
|
+
return `${amount.toFixed(6)} ${normalizedSymbol}`;
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
const formatUsd = (value) => {
|
|
908
|
+
if (!value || !isFinite(value)) return "$0";
|
|
909
|
+
if (value >= 1) return `$${value.toFixed(2)}`;
|
|
910
|
+
return `$${value.toFixed(6).replace(/0+$/, "").replace(/\.$/, "")}`;
|
|
911
|
+
};
|
|
912
|
+
const formatPercentage = (bps, decimals = 2) => {
|
|
913
|
+
return `${(bps / 100).toFixed(decimals)}%`;
|
|
914
|
+
};
|
|
915
|
+
const formatBalance = (amount, decimals = 2) => {
|
|
916
|
+
if (!isFinite(amount) || isNaN(amount) || amount <= 0) {
|
|
917
|
+
return "0.00";
|
|
918
|
+
}
|
|
919
|
+
return amount.toFixed(decimals);
|
|
920
|
+
};
|
|
921
|
+
const formatHash = (hash, startChars = 4, endChars = 4) => {
|
|
922
|
+
if (!hash) return "";
|
|
923
|
+
if (hash.length <= startChars + endChars) return hash;
|
|
924
|
+
return `${hash.slice(0, startChars)}...${hash.slice(-endChars)}`;
|
|
925
|
+
};
|
|
926
|
+
const formatAddress = formatHash;
|
|
927
|
+
const EVM_CONFIG = {
|
|
928
|
+
usdtAddress: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
|
929
|
+
gasEstimates: {
|
|
930
|
+
approve: 65000n,
|
|
931
|
+
bridge: 300000n
|
|
932
|
+
},
|
|
933
|
+
gasBuffer: 1.2,
|
|
934
|
+
// 20% buffer
|
|
935
|
+
timeout: 3e5,
|
|
936
|
+
// 5 minutes (increased for slower networks)
|
|
937
|
+
requiredConfirmations: 3
|
|
938
|
+
// Wait for 3 confirmations for reorg protection
|
|
939
|
+
};
|
|
940
|
+
const TON_CONFIG = {
|
|
941
|
+
apiUrl: "https://toncenter.com/api/v2",
|
|
942
|
+
timeout: 36e4,
|
|
943
|
+
// 6 minutes
|
|
944
|
+
validUntil: 600,
|
|
945
|
+
// 10 minutes
|
|
946
|
+
pollingInterval: 5e3,
|
|
947
|
+
// 5 seconds between transaction status checks
|
|
948
|
+
estimatedNetworkFee: "100000000"
|
|
949
|
+
// 0.1 TON in nanoton (conservative estimate)
|
|
950
|
+
};
|
|
951
|
+
const TRON_CONFIG = {
|
|
952
|
+
timeout: 12e4,
|
|
953
|
+
// 2 minutes (for 19 confirmations)
|
|
954
|
+
feeLimit: 1e8,
|
|
955
|
+
// 100 TRX in sun
|
|
956
|
+
requiredConfirmations: 19,
|
|
957
|
+
// TRON standard: 19 blocks for confirmation
|
|
958
|
+
pollingInterval: 3e3,
|
|
959
|
+
// 3 seconds between checks
|
|
960
|
+
estimatedNetworkFee: "10000000"
|
|
961
|
+
// 10 TRX in SUN (fallback estimate)
|
|
962
|
+
};
|
|
963
|
+
let tonClientInstance = null;
|
|
964
|
+
function getTonClient(customClient, apiKey) {
|
|
965
|
+
if (customClient) {
|
|
966
|
+
return customClient;
|
|
967
|
+
}
|
|
968
|
+
if (!tonClientInstance) {
|
|
969
|
+
tonClientInstance = new ton.TonClient({
|
|
970
|
+
endpoint: `${TON_CONFIG.apiUrl}/jsonRPC`,
|
|
971
|
+
apiKey
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
return tonClientInstance;
|
|
975
|
+
}
|
|
976
|
+
function getQuoteAmounts(quote, srcToken, dstToken) {
|
|
977
|
+
if (!quote || !srcToken || !dstToken) {
|
|
978
|
+
return {
|
|
979
|
+
inputHuman: 0,
|
|
980
|
+
outputHuman: 0,
|
|
981
|
+
outputHumanRounded: "0",
|
|
982
|
+
minReceivedHuman: 0
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
const inputHuman = fromLD(quote.srcAmount, srcToken.decimals);
|
|
986
|
+
const outputHuman = fromLD(quote.dstAmount, dstToken.decimals);
|
|
987
|
+
const outputHumanRounded = truncateToDecimals(outputHuman, 2);
|
|
988
|
+
const minReceivedHuman = fromLD(
|
|
989
|
+
quote.dstAmountMin || "0",
|
|
990
|
+
dstToken.decimals
|
|
991
|
+
);
|
|
992
|
+
return {
|
|
993
|
+
inputHuman,
|
|
994
|
+
outputHuman,
|
|
995
|
+
outputHumanRounded,
|
|
996
|
+
minReceivedHuman
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
function getQuoteFees(quote, tokens, chains, srcToken, dstToken) {
|
|
1000
|
+
if (!quote || !tokens || !chains) {
|
|
1001
|
+
return {
|
|
1002
|
+
totalUsd: 0,
|
|
1003
|
+
protocolFeeUsd: void 0,
|
|
1004
|
+
messageFeeUsd: void 0,
|
|
1005
|
+
serviceUsd: void 0,
|
|
1006
|
+
blockchainUsd: void 0,
|
|
1007
|
+
inSrcToken: void 0,
|
|
1008
|
+
inDstToken: void 0
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
const feeData = computeFeesUsdFromArray(quote.fees, tokens, chains);
|
|
1012
|
+
let inSrcToken = void 0;
|
|
1013
|
+
let inDstToken = void 0;
|
|
1014
|
+
if (srcToken && quote.srcChainKey) {
|
|
1015
|
+
const feeInSrcLD = sumFeeByTokenLD(
|
|
1016
|
+
quote.fees,
|
|
1017
|
+
srcToken.address,
|
|
1018
|
+
quote.srcChainKey
|
|
1019
|
+
);
|
|
1020
|
+
const feeInSrcHuman = fromLD(feeInSrcLD, srcToken.decimals);
|
|
1021
|
+
if (feeInSrcHuman > 0) {
|
|
1022
|
+
inSrcToken = Number(truncateToDecimals(feeInSrcHuman, 8));
|
|
1023
|
+
} else if (feeData.totalUsd > 0 && srcToken.price?.usd) {
|
|
1024
|
+
const feeInSrcApprox = feeData.totalUsd / srcToken.price.usd;
|
|
1025
|
+
inSrcToken = Number(truncateToDecimals(feeInSrcApprox, 8));
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
if (dstToken && quote.dstChainKey) {
|
|
1029
|
+
const feeInDstLD = sumFeeByTokenLD(
|
|
1030
|
+
quote.fees,
|
|
1031
|
+
dstToken.address,
|
|
1032
|
+
quote.dstChainKey
|
|
1033
|
+
);
|
|
1034
|
+
const feeInDstHuman = fromLD(feeInDstLD, dstToken.decimals);
|
|
1035
|
+
if (feeInDstHuman > 0) {
|
|
1036
|
+
inDstToken = Number(truncateToDecimals(feeInDstHuman, 8));
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
return {
|
|
1040
|
+
totalUsd: feeData.totalUsd,
|
|
1041
|
+
protocolFeeUsd: feeData.protocolFeeUsd,
|
|
1042
|
+
messageFeeUsd: feeData.messageFeeUsd,
|
|
1043
|
+
serviceUsd: feeData.serviceUsd,
|
|
1044
|
+
blockchainUsd: feeData.blockchainUsd,
|
|
1045
|
+
inSrcToken,
|
|
1046
|
+
inDstToken
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
function calculateMinReceived(quote, slippageBps, dstToken) {
|
|
1050
|
+
if (!quote || !dstToken) return 0;
|
|
1051
|
+
const dstAmountLD = BigInt(quote.dstAmount);
|
|
1052
|
+
const minAmountLD = dstAmountLD * BigInt(1e4 - slippageBps) / BigInt(1e4);
|
|
1053
|
+
return fromLD(minAmountLD.toString(), dstToken.decimals);
|
|
1054
|
+
}
|
|
1055
|
+
function estimateTonNetworkFee(quote) {
|
|
1056
|
+
try {
|
|
1057
|
+
const tonStep = quote.steps?.find(
|
|
1058
|
+
(s) => s.chainKey.toLowerCase() === "ton" && s.type === "bridge"
|
|
1059
|
+
);
|
|
1060
|
+
if (!tonStep?.transaction?.messages) {
|
|
1061
|
+
console.warn("No TON messages found in quote, using fallback");
|
|
1062
|
+
return TON_CONFIG.estimatedNetworkFee;
|
|
1063
|
+
}
|
|
1064
|
+
const messages = tonStep.transaction.messages;
|
|
1065
|
+
const messageCount = messages.length;
|
|
1066
|
+
const baseFeePerMessage = 10000000n;
|
|
1067
|
+
let totalFee = 0n;
|
|
1068
|
+
for (const message of messages) {
|
|
1069
|
+
let messageFee = baseFeePerMessage;
|
|
1070
|
+
if (message.payload) {
|
|
1071
|
+
const payloadSize = message.payload.length;
|
|
1072
|
+
const sizeFee = BigInt(Math.floor(payloadSize / 100)) * 100000n;
|
|
1073
|
+
messageFee += sizeFee;
|
|
1074
|
+
}
|
|
1075
|
+
if (message.amount) {
|
|
1076
|
+
const amount = BigInt(message.amount);
|
|
1077
|
+
if (amount > 0n && message.payload) {
|
|
1078
|
+
messageFee += 5000000n;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
totalFee += messageFee;
|
|
1082
|
+
}
|
|
1083
|
+
const minBuffer = 50000000n;
|
|
1084
|
+
totalFee = totalFee > minBuffer ? totalFee : minBuffer;
|
|
1085
|
+
totalFee = totalFee * 120n / 100n;
|
|
1086
|
+
const maxFee = 1000000000n;
|
|
1087
|
+
if (totalFee > maxFee) {
|
|
1088
|
+
totalFee = maxFee;
|
|
1089
|
+
}
|
|
1090
|
+
console.log(
|
|
1091
|
+
`TON fee estimate: ${messageCount} messages = ${Number(totalFee) / 1e9} TON`
|
|
1092
|
+
);
|
|
1093
|
+
return totalFee.toString();
|
|
1094
|
+
} catch (error) {
|
|
1095
|
+
console.warn("Failed to estimate TON fee:", error);
|
|
1096
|
+
return TON_CONFIG.estimatedNetworkFee;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
function addTonNetworkFee(quote, chains, estimatedFee) {
|
|
1100
|
+
if (!quote || quote.srcChainKey.toLowerCase() !== "ton") {
|
|
1101
|
+
return quote;
|
|
1102
|
+
}
|
|
1103
|
+
const tonChain = chains?.find(
|
|
1104
|
+
(c) => c.chainKey.toLowerCase() === "ton"
|
|
1105
|
+
);
|
|
1106
|
+
if (!tonChain?.nativeCurrency?.address) {
|
|
1107
|
+
console.warn("Could not find TON native currency address");
|
|
1108
|
+
return quote;
|
|
1109
|
+
}
|
|
1110
|
+
const networkFee = {
|
|
1111
|
+
token: tonChain.nativeCurrency.address,
|
|
1112
|
+
chainKey: "ton",
|
|
1113
|
+
amount: estimatedFee || TON_CONFIG.estimatedNetworkFee,
|
|
1114
|
+
type: "network"
|
|
1115
|
+
};
|
|
1116
|
+
const hasNetworkFee = quote.fees?.some(
|
|
1117
|
+
(fee) => fee.type === "network" && fee.chainKey === "ton"
|
|
1118
|
+
);
|
|
1119
|
+
if (hasNetworkFee) {
|
|
1120
|
+
return quote;
|
|
1121
|
+
}
|
|
1122
|
+
return {
|
|
1123
|
+
...quote,
|
|
1124
|
+
fees: [...quote.fees || [], networkFee]
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
async function estimateTronNetworkFee(tronWeb, quote, userAddress) {
|
|
1128
|
+
try {
|
|
1129
|
+
const accountResources = await tronWeb.trx.getAccountResources(userAddress);
|
|
1130
|
+
const availableEnergy = accountResources.EnergyLimit || 0;
|
|
1131
|
+
const tronStep = quote.steps?.find(
|
|
1132
|
+
(s) => s.chainKey.toLowerCase() === "tron" && s.type === "bridge"
|
|
1133
|
+
);
|
|
1134
|
+
if (!tronStep?.transaction?.data || !tronStep.transaction.to) {
|
|
1135
|
+
console.warn("No TRON transaction data in quote, using fallback");
|
|
1136
|
+
return TRON_CONFIG.estimatedNetworkFee;
|
|
1137
|
+
}
|
|
1138
|
+
const contractAddress = tronWeb.address.fromHex(
|
|
1139
|
+
tronStep.transaction.to.startsWith("41") ? tronStep.transaction.to : "41" + tronStep.transaction.to.slice(2)
|
|
1140
|
+
);
|
|
1141
|
+
const simulation = await tronWeb.transactionBuilder.triggerSmartContract(
|
|
1142
|
+
contractAddress,
|
|
1143
|
+
"function signature doesn't matter for energy estimation",
|
|
1144
|
+
{
|
|
1145
|
+
feeLimit: TRON_CONFIG.feeLimit,
|
|
1146
|
+
callValue: tronStep.transaction.value ? Number(tronStep.transaction.value) : 0
|
|
1147
|
+
},
|
|
1148
|
+
[],
|
|
1149
|
+
userAddress
|
|
1150
|
+
);
|
|
1151
|
+
const requiredEnergy = simulation.energy_used || 0;
|
|
1152
|
+
const energyDeficit = Math.max(0, requiredEnergy - availableEnergy);
|
|
1153
|
+
if (energyDeficit === 0) {
|
|
1154
|
+
return "0";
|
|
1155
|
+
}
|
|
1156
|
+
const energyPriceInSun = 420;
|
|
1157
|
+
const feeSun = energyDeficit * energyPriceInSun;
|
|
1158
|
+
console.log(
|
|
1159
|
+
`TRON fee estimation: ${requiredEnergy} energy required, ${availableEnergy} available, ${feeSun / 1e6} TRX fee`
|
|
1160
|
+
);
|
|
1161
|
+
return feeSun.toString();
|
|
1162
|
+
} catch (error) {
|
|
1163
|
+
console.warn("Failed to estimate TRON network fee:", error);
|
|
1164
|
+
return TRON_CONFIG.estimatedNetworkFee;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
function addTronNetworkFee(quote, chains, estimatedFee) {
|
|
1168
|
+
if (!quote || quote.srcChainKey.toLowerCase() !== "tron") {
|
|
1169
|
+
return quote;
|
|
1170
|
+
}
|
|
1171
|
+
const tronChain = chains?.find(
|
|
1172
|
+
(c) => c.chainKey.toLowerCase() === "tron"
|
|
1173
|
+
);
|
|
1174
|
+
if (!tronChain?.nativeCurrency?.address) {
|
|
1175
|
+
console.warn("Could not find TRON native currency address");
|
|
1176
|
+
return quote;
|
|
1177
|
+
}
|
|
1178
|
+
const networkFee = {
|
|
1179
|
+
token: tronChain.nativeCurrency.address,
|
|
1180
|
+
chainKey: "tron",
|
|
1181
|
+
amount: estimatedFee || TRON_CONFIG.estimatedNetworkFee,
|
|
1182
|
+
type: "network"
|
|
1183
|
+
};
|
|
1184
|
+
const hasNetworkFee = quote.fees?.some(
|
|
1185
|
+
(fee) => fee.type === "network" && fee.chainKey === "tron"
|
|
1186
|
+
);
|
|
1187
|
+
if (hasNetworkFee) {
|
|
1188
|
+
return quote;
|
|
1189
|
+
}
|
|
1190
|
+
return {
|
|
1191
|
+
...quote,
|
|
1192
|
+
fees: [...quote.fees || [], networkFee]
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
async function estimateEvmNetworkFee(publicClient, quote, chainKey) {
|
|
1196
|
+
try {
|
|
1197
|
+
let totalGasLimit = 0n;
|
|
1198
|
+
for (const step of quote.steps || []) {
|
|
1199
|
+
if (step.chainKey.toLowerCase() !== chainKey.toLowerCase()) continue;
|
|
1200
|
+
if (step.type === "approve") {
|
|
1201
|
+
totalGasLimit += EVM_CONFIG.gasEstimates.approve;
|
|
1202
|
+
} else if (step.type === "bridge") {
|
|
1203
|
+
totalGasLimit += EVM_CONFIG.gasEstimates.bridge;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
if (totalGasLimit === 0n) {
|
|
1207
|
+
return "0";
|
|
1208
|
+
}
|
|
1209
|
+
totalGasLimit = totalGasLimit * BigInt(Math.floor(EVM_CONFIG.gasBuffer * 100)) / 100n;
|
|
1210
|
+
const gasPrice = await publicClient.getGasPrice();
|
|
1211
|
+
const totalCostWei = totalGasLimit * gasPrice;
|
|
1212
|
+
console.log(
|
|
1213
|
+
`EVM gas estimate: ${totalGasLimit} gas × ${gasPrice} Wei/gas = ${totalCostWei} Wei`
|
|
1214
|
+
);
|
|
1215
|
+
return totalCostWei.toString();
|
|
1216
|
+
} catch (error) {
|
|
1217
|
+
console.warn("Failed to estimate EVM gas fee:", error);
|
|
1218
|
+
return "10000000000000000";
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
function addEvmNetworkFee(quote, chains, estimatedFee) {
|
|
1222
|
+
if (!quote) return quote;
|
|
1223
|
+
const srcChainKey = quote.srcChainKey.toLowerCase();
|
|
1224
|
+
if (srcChainKey === "ton" || srcChainKey === "tron") {
|
|
1225
|
+
return quote;
|
|
1226
|
+
}
|
|
1227
|
+
const srcChain = chains?.find(
|
|
1228
|
+
(c) => c.chainKey.toLowerCase() === srcChainKey
|
|
1229
|
+
);
|
|
1230
|
+
if (!srcChain?.nativeCurrency?.address) {
|
|
1231
|
+
return quote;
|
|
1232
|
+
}
|
|
1233
|
+
const hasNetworkFee = quote.fees?.some(
|
|
1234
|
+
(fee) => fee.type === "network" && fee.chainKey.toLowerCase() === srcChainKey
|
|
1235
|
+
);
|
|
1236
|
+
if (hasNetworkFee) {
|
|
1237
|
+
return quote;
|
|
1238
|
+
}
|
|
1239
|
+
const networkFee = {
|
|
1240
|
+
token: srcChain.nativeCurrency.address,
|
|
1241
|
+
chainKey: quote.srcChainKey,
|
|
1242
|
+
amount: estimatedFee || "10000000000000000",
|
|
1243
|
+
// 0.01 ETH fallback
|
|
1244
|
+
type: "network"
|
|
1245
|
+
};
|
|
1246
|
+
return {
|
|
1247
|
+
...quote,
|
|
1248
|
+
fees: [...quote.fees || [], networkFee]
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
function getQuoteDetails(quote, srcToken, dstToken, tokens, chains, slippageBps) {
|
|
1252
|
+
const amounts = getQuoteAmounts(quote, srcToken, dstToken);
|
|
1253
|
+
const fees = getQuoteFees(quote, tokens, chains, srcToken, dstToken);
|
|
1254
|
+
const minimumReceived = calculateMinReceived(quote, slippageBps, dstToken);
|
|
1255
|
+
return {
|
|
1256
|
+
inputAmount: amounts.inputHuman,
|
|
1257
|
+
outputAmount: amounts.outputHuman,
|
|
1258
|
+
outputAmountRounded: amounts.outputHumanRounded,
|
|
1259
|
+
minimumReceived,
|
|
1260
|
+
etaSeconds: quote?.duration?.estimated,
|
|
1261
|
+
fees
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
function useBridgeQuote() {
|
|
1265
|
+
const { assetMatrix, selectedAssetSymbol } = useTokensStore();
|
|
1266
|
+
const { fromChain, toChain, chains } = useChainsStore();
|
|
1267
|
+
const { srcAddress, dstAddress } = useAddresses();
|
|
1268
|
+
const { slippageBps, routePriority, getDstNativeAmount, getSlippageDecimal } = useSettingsStore();
|
|
1269
|
+
const {
|
|
1270
|
+
inputAmount,
|
|
1271
|
+
refetchTrigger,
|
|
1272
|
+
setLoading: setQuoteLoading,
|
|
1273
|
+
setQuote,
|
|
1274
|
+
setError: setQError,
|
|
1275
|
+
setNoRoute,
|
|
1276
|
+
resetWithIdle
|
|
1277
|
+
} = useBridgeQuoteStore();
|
|
1278
|
+
const { chainRegistry } = useChainStrategies();
|
|
1279
|
+
const publicClient = wagmi.usePublicClient();
|
|
1280
|
+
const input2 = inputAmount;
|
|
1281
|
+
const srcTokenOnFrom = react.useMemo(
|
|
1282
|
+
() => resolveTokenOnChainFromMatrix$2(
|
|
1283
|
+
assetMatrix,
|
|
1284
|
+
selectedAssetSymbol,
|
|
1285
|
+
fromChain?.chainKey
|
|
1286
|
+
),
|
|
1287
|
+
[assetMatrix, selectedAssetSymbol, fromChain?.chainKey]
|
|
1288
|
+
);
|
|
1289
|
+
const dstTokenOnTo = react.useMemo(
|
|
1290
|
+
() => resolveTokenOnChainFromMatrix$2(
|
|
1291
|
+
assetMatrix,
|
|
1292
|
+
selectedAssetSymbol,
|
|
1293
|
+
toChain?.chainKey
|
|
1294
|
+
),
|
|
1295
|
+
[assetMatrix, selectedAssetSymbol, toChain?.chainKey]
|
|
1296
|
+
);
|
|
1297
|
+
const [loading, setLoading] = react.useState(false);
|
|
1298
|
+
react.useEffect(() => {
|
|
1299
|
+
if (!input2 || input2 === "") {
|
|
1300
|
+
setLoading(false);
|
|
1301
|
+
resetWithIdle();
|
|
1302
|
+
}
|
|
1303
|
+
}, [input2, resetWithIdle]);
|
|
1304
|
+
react.useEffect(() => {
|
|
1305
|
+
const resetUi = (withError) => {
|
|
1306
|
+
setLoading(false);
|
|
1307
|
+
{
|
|
1308
|
+
resetWithIdle();
|
|
1309
|
+
}
|
|
1310
|
+
};
|
|
1311
|
+
if (!input2 || Number(input2) <= 0 || !fromChain?.chainKey || !toChain?.chainKey || !srcTokenOnFrom || !dstTokenOnTo || !srcAddress || !dstAddress) {
|
|
1312
|
+
resetUi();
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
if (!isAddressValidForChain(fromChain.chainKey, srcAddress) || !isAddressValidForChain(toChain.chainKey, dstAddress)) {
|
|
1316
|
+
resetUi();
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
if (fromChain.chainKey === toChain.chainKey) {
|
|
1320
|
+
resetUi();
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
let cancelled = false;
|
|
1324
|
+
const run = async () => {
|
|
1325
|
+
try {
|
|
1326
|
+
setLoading(true);
|
|
1327
|
+
setQuoteLoading?.();
|
|
1328
|
+
if (assetMatrix && selectedAssetSymbol) {
|
|
1329
|
+
const tokenMatrix = assetMatrix[selectedAssetSymbol.toUpperCase()];
|
|
1330
|
+
if (!tokenMatrix || !tokenMatrix[fromChain.chainKey] || !tokenMatrix[toChain.chainKey]) {
|
|
1331
|
+
if (!cancelled) {
|
|
1332
|
+
resetUi();
|
|
1333
|
+
}
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
const srcAmountLD = toLD(input2, srcTokenOnFrom.decimals);
|
|
1338
|
+
const srcAddrApi = addrForApi(fromChain.chainKey, srcAddress);
|
|
1339
|
+
const dstAddrApi = addrForApi(toChain.chainKey, dstAddress);
|
|
1340
|
+
const dstNativeAmount = getDstNativeAmount(toChain.chainKey);
|
|
1341
|
+
const slippageDecimal = getSlippageDecimal();
|
|
1342
|
+
const approximateMinLD = BigInt(srcAmountLD) * BigInt(9500) / BigInt(1e4);
|
|
1343
|
+
const quoteRoute = await getQuotesByPriority({
|
|
1344
|
+
srcChainKey: fromChain.chainKey,
|
|
1345
|
+
dstChainKey: toChain.chainKey,
|
|
1346
|
+
srcToken: srcTokenOnFrom.address,
|
|
1347
|
+
dstToken: dstTokenOnTo.address,
|
|
1348
|
+
srcAmountLD,
|
|
1349
|
+
dstAmountMinLD: approximateMinLD.toString(),
|
|
1350
|
+
srcAddress: srcAddrApi,
|
|
1351
|
+
dstAddress: dstAddrApi,
|
|
1352
|
+
dstNativeAmount,
|
|
1353
|
+
slippage: slippageDecimal,
|
|
1354
|
+
routePriority
|
|
1355
|
+
});
|
|
1356
|
+
if (quoteRoute?.route === null) {
|
|
1357
|
+
setNoRoute(true);
|
|
1358
|
+
if (!cancelled) resetUi();
|
|
1359
|
+
return;
|
|
1360
|
+
} else {
|
|
1361
|
+
setNoRoute(false);
|
|
1362
|
+
}
|
|
1363
|
+
if (!quoteRoute || !quoteRoute.dstAmount || BigInt(quoteRoute.dstAmount) === BigInt(0)) {
|
|
1364
|
+
if (!cancelled) resetUi();
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
if (cancelled) return;
|
|
1368
|
+
let quoteWithFees = quoteRoute;
|
|
1369
|
+
if (quoteRoute.srcChainKey.toLowerCase() === "ton") {
|
|
1370
|
+
try {
|
|
1371
|
+
const estimatedFee = estimateTonNetworkFee(quoteRoute);
|
|
1372
|
+
quoteWithFees = addTonNetworkFee(quoteRoute, chains, estimatedFee);
|
|
1373
|
+
console.log("TON network fee estimated for quote:", estimatedFee);
|
|
1374
|
+
} catch (error) {
|
|
1375
|
+
console.warn("Failed to estimate TON fee, using fallback:", error);
|
|
1376
|
+
quoteWithFees = addTonNetworkFee(quoteRoute, chains);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
if (quoteRoute.srcChainKey.toLowerCase() === "tron") {
|
|
1380
|
+
const tronStrategy = chainRegistry.getStrategy("tron");
|
|
1381
|
+
if (tronStrategy?.isConnected()) {
|
|
1382
|
+
const tronWeb = tronStrategy.getClient();
|
|
1383
|
+
const tronAddress = tronStrategy.getAccount();
|
|
1384
|
+
if (tronWeb && tronAddress) {
|
|
1385
|
+
try {
|
|
1386
|
+
const estimatedFee = await estimateTronNetworkFee(
|
|
1387
|
+
tronWeb,
|
|
1388
|
+
quoteRoute,
|
|
1389
|
+
tronAddress
|
|
1390
|
+
);
|
|
1391
|
+
quoteWithFees = addTronNetworkFee(
|
|
1392
|
+
quoteWithFees || quoteRoute,
|
|
1393
|
+
chains,
|
|
1394
|
+
estimatedFee
|
|
1395
|
+
);
|
|
1396
|
+
console.log("TRON network fee estimated for quote:", estimatedFee);
|
|
1397
|
+
} catch (error) {
|
|
1398
|
+
console.warn("Failed to estimate TRON fee, using fallback:", error);
|
|
1399
|
+
quoteWithFees = addTronNetworkFee(
|
|
1400
|
+
quoteWithFees || quoteRoute,
|
|
1401
|
+
chains
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1404
|
+
} else {
|
|
1405
|
+
quoteWithFees = addTronNetworkFee(
|
|
1406
|
+
quoteWithFees || quoteRoute,
|
|
1407
|
+
chains
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
} else {
|
|
1411
|
+
quoteWithFees = addTronNetworkFee(
|
|
1412
|
+
quoteWithFees || quoteRoute,
|
|
1413
|
+
chains
|
|
1414
|
+
);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
const srcChainKey = quoteRoute.srcChainKey.toLowerCase();
|
|
1418
|
+
if (srcChainKey !== "ton" && srcChainKey !== "tron") {
|
|
1419
|
+
if (publicClient) {
|
|
1420
|
+
try {
|
|
1421
|
+
const estimatedFee = await estimateEvmNetworkFee(
|
|
1422
|
+
publicClient,
|
|
1423
|
+
quoteRoute,
|
|
1424
|
+
quoteRoute.srcChainKey
|
|
1425
|
+
);
|
|
1426
|
+
quoteWithFees = addEvmNetworkFee(
|
|
1427
|
+
quoteWithFees || quoteRoute,
|
|
1428
|
+
chains,
|
|
1429
|
+
estimatedFee
|
|
1430
|
+
);
|
|
1431
|
+
console.log("EVM network fee estimated for quote:", estimatedFee);
|
|
1432
|
+
} catch (error) {
|
|
1433
|
+
console.warn("Failed to estimate EVM fee, using fallback:", error);
|
|
1434
|
+
quoteWithFees = addEvmNetworkFee(
|
|
1435
|
+
quoteWithFees || quoteRoute,
|
|
1436
|
+
chains
|
|
1437
|
+
);
|
|
1438
|
+
}
|
|
1439
|
+
} else {
|
|
1440
|
+
quoteWithFees = addEvmNetworkFee(
|
|
1441
|
+
quoteWithFees || quoteRoute,
|
|
1442
|
+
chains
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
setQuote(quoteWithFees || quoteRoute);
|
|
1447
|
+
} catch {
|
|
1448
|
+
if (!cancelled) {
|
|
1449
|
+
resetUi();
|
|
1450
|
+
}
|
|
1451
|
+
} finally {
|
|
1452
|
+
if (!cancelled) setLoading(false);
|
|
1453
|
+
}
|
|
1454
|
+
};
|
|
1455
|
+
run();
|
|
1456
|
+
return () => {
|
|
1457
|
+
cancelled = true;
|
|
1458
|
+
};
|
|
1459
|
+
}, [
|
|
1460
|
+
input2,
|
|
1461
|
+
fromChain?.chainKey,
|
|
1462
|
+
toChain?.chainKey,
|
|
1463
|
+
srcTokenOnFrom,
|
|
1464
|
+
dstTokenOnTo,
|
|
1465
|
+
srcAddress,
|
|
1466
|
+
dstAddress,
|
|
1467
|
+
setQuoteLoading,
|
|
1468
|
+
setQuote,
|
|
1469
|
+
setQError,
|
|
1470
|
+
slippageBps,
|
|
1471
|
+
routePriority,
|
|
1472
|
+
refetchTrigger,
|
|
1473
|
+
chainRegistry,
|
|
1474
|
+
chains,
|
|
1475
|
+
publicClient
|
|
1476
|
+
]);
|
|
1477
|
+
return { loading };
|
|
1478
|
+
}
|
|
1479
|
+
async function getChains() {
|
|
1480
|
+
const res = await fetch("https://stargate.finance/api/v1/chains", {
|
|
1481
|
+
credentials: "same-origin"
|
|
1482
|
+
});
|
|
1483
|
+
if (!res.ok) {
|
|
1484
|
+
throw new Error(`Failed to load chains: ${res.status}`);
|
|
1485
|
+
}
|
|
1486
|
+
const data = await res.json();
|
|
1487
|
+
return Array.isArray(data) ? data : data.chains ?? [];
|
|
1488
|
+
}
|
|
1489
|
+
async function getTokens() {
|
|
1490
|
+
const res = await fetch("https://stargate.finance/api/v1/tokens", {
|
|
1491
|
+
credentials: "same-origin"
|
|
1492
|
+
});
|
|
1493
|
+
if (!res.ok) {
|
|
1494
|
+
throw new Error(`Failed to load chains: ${res.status}`);
|
|
1495
|
+
}
|
|
1496
|
+
const data = await res.json();
|
|
1497
|
+
const tokens = Array.isArray(data) ? data : data.tokens ?? [];
|
|
1498
|
+
return tokens.map(normalizeTokenSymbol);
|
|
1499
|
+
}
|
|
1500
|
+
async function getDestTokens(srcChainKey, srcTokenAddr) {
|
|
1501
|
+
const url = new URL("https://stargate.finance/api/v1/tokens");
|
|
1502
|
+
url.searchParams.set("srcChainKey", srcChainKey);
|
|
1503
|
+
url.searchParams.set("srcToken", srcTokenAddr);
|
|
1504
|
+
const res = await fetch(url.toString(), { credentials: "omit" });
|
|
812
1505
|
if (!res.ok) throw new Error(`Failed to load dest tokens: ${res.status}`);
|
|
813
1506
|
const data = await res.json();
|
|
814
1507
|
const raw = data.tokens ?? [];
|
|
@@ -829,10 +1522,7 @@ async function getDestTokens(srcChainKey, srcTokenAddr) {
|
|
|
829
1522
|
if (sa === sb) return (a.name ?? "").localeCompare(b.name ?? "");
|
|
830
1523
|
return sa.localeCompare(sb);
|
|
831
1524
|
});
|
|
832
|
-
return unique;
|
|
833
|
-
}
|
|
834
|
-
function normalizeTickerSymbol$1(s) {
|
|
835
|
-
return s.toUpperCase().replace(/₮/g, "T").replace(/[^A-Z0-9]/g, "");
|
|
1525
|
+
return unique.map(normalizeTokenSymbol);
|
|
836
1526
|
}
|
|
837
1527
|
function resolveTokenOnChainFromMatrix$1(assetMatrix, assetSymbol, chainKey) {
|
|
838
1528
|
if (!assetMatrix || !assetSymbol || !chainKey) return void 0;
|
|
@@ -859,7 +1549,14 @@ const useChainDerivations = () => {
|
|
|
859
1549
|
return [];
|
|
860
1550
|
const byChain = assetMatrix[selectedAssetSymbol.toUpperCase()];
|
|
861
1551
|
const keys = new Set(Object.keys(byChain ?? {}));
|
|
862
|
-
|
|
1552
|
+
const result = chains.filter((c) => keys.has(c.chainKey));
|
|
1553
|
+
console.log(
|
|
1554
|
+
`[DEBUG] supportedFrom for ${selectedAssetSymbol}:`,
|
|
1555
|
+
`${result.length} of ${chains.length} chains`,
|
|
1556
|
+
result.map((c) => c.chainKey),
|
|
1557
|
+
`| Has Arbitrum: ${result.some((c) => c.chainKey === "arbitrum")}`
|
|
1558
|
+
);
|
|
1559
|
+
return result;
|
|
863
1560
|
}, [selectedAssetSymbol, assetMatrix, chains]);
|
|
864
1561
|
react.useEffect(() => {
|
|
865
1562
|
setAllowedFromChains(supportedFrom);
|
|
@@ -927,6 +1624,13 @@ const useChainDerivations = () => {
|
|
|
927
1624
|
if (fromChain?.chainKey) {
|
|
928
1625
|
list = list.filter((c) => c.chainKey !== fromChain.chainKey);
|
|
929
1626
|
}
|
|
1627
|
+
console.log(
|
|
1628
|
+
`[DEBUG] allowedToChains from ${fromChain?.chainKey}:`,
|
|
1629
|
+
`${list.length} destinations`,
|
|
1630
|
+
list.map((c) => c.chainKey),
|
|
1631
|
+
`| Has Arbitrum: ${list.some((c) => c.chainKey === "arbitrum")}`,
|
|
1632
|
+
`| API returned ${dest.length} tokens, filtered to ${filteredDest.length}`
|
|
1633
|
+
);
|
|
930
1634
|
setAllowedToChains(list);
|
|
931
1635
|
if (!toChain || !list.some((c) => c.chainKey === toChain.chainKey)) {
|
|
932
1636
|
setToChain(list[0]);
|
|
@@ -961,16 +1665,6 @@ const useChainDerivations = () => {
|
|
|
961
1665
|
isLoadingToChains
|
|
962
1666
|
};
|
|
963
1667
|
};
|
|
964
|
-
const ChainStrategyContext = react.createContext(void 0);
|
|
965
|
-
function useChainStrategies() {
|
|
966
|
-
const context = react.useContext(ChainStrategyContext);
|
|
967
|
-
if (!context) {
|
|
968
|
-
throw new Error(
|
|
969
|
-
"useChainStrategies must be used within ChainStrategyProvider"
|
|
970
|
-
);
|
|
971
|
-
}
|
|
972
|
-
return context;
|
|
973
|
-
}
|
|
974
1668
|
function useBalances(chainKey, address, priorityTokenSymbol) {
|
|
975
1669
|
const { chainRegistry } = useChainStrategies();
|
|
976
1670
|
const { assetMatrix } = useTokensStore();
|
|
@@ -1334,7 +2028,7 @@ const StargateIcon = (props) => {
|
|
|
1334
2028
|
"path",
|
|
1335
2029
|
{
|
|
1336
2030
|
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",
|
|
1337
|
-
fill: "
|
|
2031
|
+
fill: "currentColor"
|
|
1338
2032
|
}
|
|
1339
2033
|
)
|
|
1340
2034
|
] }),
|
|
@@ -1470,7 +2164,7 @@ const WalletConnectIcon = (props) => {
|
|
|
1470
2164
|
}
|
|
1471
2165
|
);
|
|
1472
2166
|
};
|
|
1473
|
-
const
|
|
2167
|
+
const TonConnectIcon = (props) => {
|
|
1474
2168
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1475
2169
|
"svg",
|
|
1476
2170
|
{
|
|
@@ -1660,7 +2354,7 @@ const SelectNetworkButton = ({
|
|
|
1660
2354
|
size: "sm",
|
|
1661
2355
|
variant: "secondary",
|
|
1662
2356
|
type: "button",
|
|
1663
|
-
className: "shrink-0 gap-2
|
|
2357
|
+
className: "shrink-0 gap-2 h-9 !pl-2",
|
|
1664
2358
|
"aria-label": label,
|
|
1665
2359
|
children: [
|
|
1666
2360
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 items-center", children: [
|
|
@@ -1751,49 +2445,37 @@ const SearchInput = ({
|
|
|
1751
2445
|
placeholder,
|
|
1752
2446
|
value,
|
|
1753
2447
|
onChange,
|
|
1754
|
-
className
|
|
1755
|
-
containerClassName
|
|
2448
|
+
className
|
|
1756
2449
|
}) => {
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
className: utils.cn(
|
|
1773
|
-
"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",
|
|
1774
|
-
className
|
|
1775
|
-
),
|
|
1776
|
-
value,
|
|
1777
|
-
onChange: (e) => onChange(e.target.value),
|
|
1778
|
-
onFocus: () => setIsFocused(true),
|
|
1779
|
-
onBlur: () => setIsFocused(false)
|
|
1780
|
-
}
|
|
1781
|
-
)
|
|
1782
|
-
]
|
|
1783
|
-
}
|
|
1784
|
-
);
|
|
2450
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("rounded-xs relative"), children: [
|
|
2451
|
+
/* @__PURE__ */ jsxRuntime.jsx(SearchIcon, { className: "w-6 h-6 absolute left-5 top-0 bottom-0 my-auto" }),
|
|
2452
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2453
|
+
input.Input,
|
|
2454
|
+
{
|
|
2455
|
+
placeholder,
|
|
2456
|
+
className: utils.cn(
|
|
2457
|
+
"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",
|
|
2458
|
+
className
|
|
2459
|
+
),
|
|
2460
|
+
value,
|
|
2461
|
+
onChange: (e) => onChange(e.target.value)
|
|
2462
|
+
}
|
|
2463
|
+
)
|
|
2464
|
+
] });
|
|
1785
2465
|
};
|
|
1786
2466
|
const ChainSelectModal = ({
|
|
1787
2467
|
isOpen,
|
|
1788
2468
|
onClose,
|
|
1789
2469
|
items,
|
|
1790
2470
|
allowedItems,
|
|
1791
|
-
onChangeChain
|
|
2471
|
+
onChangeChain,
|
|
2472
|
+
isSource
|
|
1792
2473
|
}) => {
|
|
1793
2474
|
const { t } = useBridgeTranslation();
|
|
1794
2475
|
const [query, setQuery] = react.useState("");
|
|
1795
2476
|
const { setFromChain, chains, fromChain, toChain } = useChainsStore();
|
|
1796
|
-
const { assetMatrix, selectedAssetSymbol } = useTokensStore();
|
|
2477
|
+
const { assetMatrix, selectedAssetSymbol, setSelectedAssetSymbol } = useTokensStore();
|
|
2478
|
+
console.log("allowedItems", allowedItems);
|
|
1797
2479
|
const handleClose = react.useCallback(() => {
|
|
1798
2480
|
setQuery("");
|
|
1799
2481
|
onClose();
|
|
@@ -1822,40 +2504,84 @@ const ChainSelectModal = ({
|
|
|
1822
2504
|
},
|
|
1823
2505
|
[chains, selectedAssetSymbol, assetMatrix]
|
|
1824
2506
|
);
|
|
2507
|
+
const findCompatibleTokenAndSrcChain = react.useCallback(
|
|
2508
|
+
(dstChain) => {
|
|
2509
|
+
if (!chains || !assetMatrix) return void 0;
|
|
2510
|
+
const PRIORITY_CHAINS = [
|
|
2511
|
+
"ethereum",
|
|
2512
|
+
"arbitrum",
|
|
2513
|
+
"bsc",
|
|
2514
|
+
"polygon",
|
|
2515
|
+
"optimism",
|
|
2516
|
+
"base"
|
|
2517
|
+
];
|
|
2518
|
+
for (const tokenSymbol of Object.keys(assetMatrix)) {
|
|
2519
|
+
const assetByChain = assetMatrix[tokenSymbol];
|
|
2520
|
+
if (!assetByChain[dstChain.chainKey]) continue;
|
|
2521
|
+
const availableChains = chains.filter(
|
|
2522
|
+
(c) => assetByChain[c.chainKey] && c.chainKey !== dstChain.chainKey
|
|
2523
|
+
);
|
|
2524
|
+
if (availableChains.length === 0) continue;
|
|
2525
|
+
let sourceChain;
|
|
2526
|
+
for (const chainKey of PRIORITY_CHAINS) {
|
|
2527
|
+
sourceChain = availableChains.find((c) => c.chainKey === chainKey);
|
|
2528
|
+
if (sourceChain) break;
|
|
2529
|
+
}
|
|
2530
|
+
if (!sourceChain) sourceChain = availableChains[0];
|
|
2531
|
+
return { tokenSymbol, sourceChain };
|
|
2532
|
+
}
|
|
2533
|
+
return void 0;
|
|
2534
|
+
},
|
|
2535
|
+
[chains, assetMatrix]
|
|
2536
|
+
);
|
|
1825
2537
|
const groupedChains = react.useMemo(() => {
|
|
1826
2538
|
const q = query.trim().toLowerCase();
|
|
1827
2539
|
const filtered = q ? (items ?? []).filter(
|
|
1828
2540
|
(c) => c.name.toLowerCase().includes(q) || c.chainKey.toLowerCase().includes(q)
|
|
1829
2541
|
) : items ?? [];
|
|
1830
|
-
const groups = { available: [], willChangeSrc: [] };
|
|
2542
|
+
const groups = { available: [], willChangeSrc: [], willChangeTokenAndSrc: [] };
|
|
1831
2543
|
for (const chain of filtered) {
|
|
1832
2544
|
const isAllowed = allowedItems?.some((c) => c.chainKey === chain.chainKey) || false;
|
|
1833
2545
|
if (isAllowed) {
|
|
1834
2546
|
groups.available.push(chain);
|
|
1835
2547
|
} else {
|
|
1836
2548
|
const compatibleSrc = findCompatibleSrcChain(chain);
|
|
1837
|
-
if (compatibleSrc)
|
|
2549
|
+
if (compatibleSrc) {
|
|
2550
|
+
groups.willChangeSrc.push(chain);
|
|
2551
|
+
} else {
|
|
2552
|
+
const compatibleTokenAndSrc = findCompatibleTokenAndSrcChain(chain);
|
|
2553
|
+
if (compatibleTokenAndSrc) {
|
|
2554
|
+
groups.willChangeTokenAndSrc.push(chain);
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
1838
2557
|
}
|
|
1839
2558
|
}
|
|
1840
2559
|
groups.available.sort((a, b) => a.name.localeCompare(b.name));
|
|
1841
2560
|
groups.willChangeSrc.sort((a, b) => a.name.localeCompare(b.name));
|
|
2561
|
+
groups.willChangeTokenAndSrc.sort((a, b) => a.name.localeCompare(b.name));
|
|
1842
2562
|
return groups;
|
|
1843
|
-
}, [items, query, allowedItems, findCompatibleSrcChain]);
|
|
1844
|
-
const onChainPick = (chain, willChangeSrc = false) => {
|
|
1845
|
-
if (
|
|
2563
|
+
}, [items, query, allowedItems, findCompatibleSrcChain, findCompatibleTokenAndSrcChain]);
|
|
2564
|
+
const onChainPick = (chain, willChangeSrc = false, willChangeTokenAndSrc = false) => {
|
|
2565
|
+
if (willChangeTokenAndSrc) {
|
|
2566
|
+
const result = findCompatibleTokenAndSrcChain(chain);
|
|
2567
|
+
if (result) {
|
|
2568
|
+
setSelectedAssetSymbol(result.tokenSymbol);
|
|
2569
|
+
if (setFromChain) setFromChain(result.sourceChain);
|
|
2570
|
+
}
|
|
2571
|
+
} else if (willChangeSrc) {
|
|
1846
2572
|
const newSrcChain = findCompatibleSrcChain(chain);
|
|
1847
2573
|
if (newSrcChain && setFromChain) setFromChain(newSrcChain);
|
|
1848
2574
|
}
|
|
1849
2575
|
onChangeChain(chain);
|
|
1850
2576
|
handleClose();
|
|
1851
2577
|
};
|
|
1852
|
-
const renderChainItem = (chain, willChangeSrc) => {
|
|
1853
|
-
const isSelected = fromChain?.chainKey === chain.chainKey
|
|
2578
|
+
const renderChainItem = (chain, willChangeSrc, willChangeTokenAndSrc = false) => {
|
|
2579
|
+
const isSelected = isSource ? fromChain?.chainKey === chain.chainKey : toChain?.chainKey === chain.chainKey;
|
|
1854
2580
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1855
2581
|
button.Button,
|
|
1856
2582
|
{
|
|
1857
|
-
onClick: () => onChainPick(chain, willChangeSrc),
|
|
1858
|
-
className: `w-full cursor-pointer flex shadow-none rounded-
|
|
2583
|
+
onClick: () => onChainPick(chain, willChangeSrc, willChangeTokenAndSrc),
|
|
2584
|
+
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" : ""}`,
|
|
1859
2585
|
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1860
2586
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1861
2587
|
NetworkSymbol,
|
|
@@ -1871,7 +2597,7 @@ const ChainSelectModal = ({
|
|
|
1871
2597
|
chain.chainKey
|
|
1872
2598
|
);
|
|
1873
2599
|
};
|
|
1874
|
-
return /* @__PURE__ */ jsxRuntime.jsx(dialog.Dialog, { open: isOpen, onOpenChange: (open) => !open && handleClose(), children: /* @__PURE__ */ jsxRuntime.jsxs(dialog.DialogContent, { className: "max-h-[90dvh] h-[90dvh] overflow-hidden flex flex-col", children: [
|
|
2600
|
+
return /* @__PURE__ */ jsxRuntime.jsx(dialog.Dialog, { open: isOpen, onOpenChange: (open) => !open && handleClose(), children: /* @__PURE__ */ jsxRuntime.jsxs(dialog.DialogContent, { className: "md:max-h-[90dvh] md:h-[90dvh] overflow-hidden flex flex-col", children: [
|
|
1875
2601
|
/* @__PURE__ */ jsxRuntime.jsx(dialog.DialogHeader, { className: "text-left", children: /* @__PURE__ */ jsxRuntime.jsx(dialog.DialogTitle, { children: t("bridge.selectNetwork") }) }),
|
|
1876
2602
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1877
2603
|
SearchInput,
|
|
@@ -1879,19 +2605,24 @@ const ChainSelectModal = ({
|
|
|
1879
2605
|
placeholder: t("bridge.searchDestinationChain"),
|
|
1880
2606
|
value: query,
|
|
1881
2607
|
onChange: setQuery,
|
|
1882
|
-
containerClassName: "rounded-md",
|
|
1883
2608
|
className: "text-foreground placeholder:text-muted-foreground"
|
|
1884
2609
|
}
|
|
1885
2610
|
),
|
|
1886
2611
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto", children: [
|
|
1887
2612
|
groupedChains.available.length > 0 && groupedChains.available.map((c) => renderChainItem(c, false)),
|
|
1888
|
-
groupedChains.
|
|
1889
|
-
|
|
1890
|
-
groupedChains.willChangeSrc.
|
|
1891
|
-
(c) => renderChainItem(c, true)
|
|
2613
|
+
groupedChains.willChangeSrc.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2614
|
+
/* @__PURE__ */ jsxRuntime.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") }),
|
|
2615
|
+
groupedChains.willChangeSrc.map(
|
|
2616
|
+
(c) => renderChainItem(c, true, false)
|
|
1892
2617
|
)
|
|
1893
2618
|
] }),
|
|
1894
|
-
groupedChains.
|
|
2619
|
+
groupedChains.willChangeTokenAndSrc.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2620
|
+
/* @__PURE__ */ jsxRuntime.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") }),
|
|
2621
|
+
groupedChains.willChangeTokenAndSrc.map(
|
|
2622
|
+
(c) => renderChainItem(c, false, true)
|
|
2623
|
+
)
|
|
2624
|
+
] }),
|
|
2625
|
+
groupedChains.available.length === 0 && groupedChains.willChangeSrc.length === 0 && groupedChains.willChangeTokenAndSrc.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-5 py-4 text-sm text-muted-foreground", children: t("bridge.noResults") })
|
|
1895
2626
|
] })
|
|
1896
2627
|
] }) });
|
|
1897
2628
|
};
|
|
@@ -1901,52 +2632,10 @@ const useWalletSelectModal = zustand.create((set) => ({
|
|
|
1901
2632
|
onOpen: (addressType) => set({ isOpen: true, addressType }),
|
|
1902
2633
|
onClose: () => set({ isOpen: false, addressType: void 0 })
|
|
1903
2634
|
}));
|
|
1904
|
-
const truncateToDecimals = (num, decimals) => {
|
|
1905
|
-
if (!isFinite(num) || isNaN(num)) return "0.00";
|
|
1906
|
-
const multiplier = Math.pow(10, decimals);
|
|
1907
|
-
const truncated = Math.floor(num * multiplier) / multiplier;
|
|
1908
|
-
return truncated.toFixed(decimals);
|
|
1909
|
-
};
|
|
1910
|
-
const formatTokenAmount = (amount, symbol, options) => {
|
|
1911
|
-
const normalizedSymbol = (symbol ?? "").toUpperCase();
|
|
1912
|
-
if (["USDT", "USDC", "DAI", "BUSD"].includes(normalizedSymbol) && amount >= 1) {
|
|
1913
|
-
return `${Math.floor(amount)} ${normalizedSymbol}`;
|
|
1914
|
-
}
|
|
1915
|
-
if (options?.decimals !== void 0) {
|
|
1916
|
-
return `${amount.toFixed(options.decimals)} ${normalizedSymbol}`;
|
|
1917
|
-
}
|
|
1918
|
-
if (amount >= 1) {
|
|
1919
|
-
return `${amount.toFixed(0)} ${normalizedSymbol}`;
|
|
1920
|
-
} else if (amount >= 1e-3) {
|
|
1921
|
-
return `${amount.toFixed(3)} ${normalizedSymbol}`;
|
|
1922
|
-
} else {
|
|
1923
|
-
return `${amount.toFixed(6)} ${normalizedSymbol}`;
|
|
1924
|
-
}
|
|
1925
|
-
};
|
|
1926
|
-
const formatUsd = (value) => {
|
|
1927
|
-
if (!value || !isFinite(value)) return "$0";
|
|
1928
|
-
if (value >= 1) return `$${value.toFixed(2)}`;
|
|
1929
|
-
return `$${value.toFixed(6).replace(/0+$/, "").replace(/\.$/, "")}`;
|
|
1930
|
-
};
|
|
1931
|
-
const formatPercentage = (bps, decimals = 2) => {
|
|
1932
|
-
return `${(bps / 100).toFixed(decimals)}%`;
|
|
1933
|
-
};
|
|
1934
|
-
const formatBalance = (amount, decimals = 2) => {
|
|
1935
|
-
if (!isFinite(amount) || isNaN(amount) || amount <= 0) {
|
|
1936
|
-
return "0.00";
|
|
1937
|
-
}
|
|
1938
|
-
return amount.toFixed(decimals);
|
|
1939
|
-
};
|
|
1940
|
-
const formatHash = (hash, startChars = 4, endChars = 4) => {
|
|
1941
|
-
if (!hash) return "";
|
|
1942
|
-
if (hash.length <= startChars + endChars) return hash;
|
|
1943
|
-
return `${hash.slice(0, startChars)}...${hash.slice(-endChars)}`;
|
|
1944
|
-
};
|
|
1945
|
-
const formatAddress = formatHash;
|
|
1946
2635
|
const prefixIcons = {
|
|
1947
2636
|
tronlink: /* @__PURE__ */ jsxRuntime.jsx(TronLinkIcon, { className: "w-5 h-5" }),
|
|
1948
2637
|
metamask: /* @__PURE__ */ jsxRuntime.jsx(MetaMaskIcon, { className: "w-5 h-5" }),
|
|
1949
|
-
ton: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2638
|
+
ton: /* @__PURE__ */ jsxRuntime.jsx(TonConnectIcon, { className: "w-5 h-5" })
|
|
1950
2639
|
};
|
|
1951
2640
|
const mapWalletToType = (wallet) => {
|
|
1952
2641
|
switch (wallet) {
|
|
@@ -1997,7 +2686,7 @@ const WalletInlineButton = ({
|
|
|
1997
2686
|
disabled: isButtonDisabled,
|
|
1998
2687
|
variant: "ghost",
|
|
1999
2688
|
size: "sm",
|
|
2000
|
-
className: "flex gap-1 cursor-pointer !px-0 pr-1 h-5",
|
|
2689
|
+
className: "flex gap-1 cursor-pointer hover:opacity-60 hover:bg-transparent !px-0 pr-1 h-5",
|
|
2001
2690
|
children: [
|
|
2002
2691
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: isConnected ? prefixIcons[wallet] : null }),
|
|
2003
2692
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "leading-3 text-sm border-b border-dotted border-link text-link", children: buttonText })
|
|
@@ -2133,7 +2822,8 @@ const SwapSection = ({
|
|
|
2133
2822
|
onClose,
|
|
2134
2823
|
items: chains,
|
|
2135
2824
|
allowedItems: allowedChains,
|
|
2136
|
-
onChangeChain
|
|
2825
|
+
onChangeChain,
|
|
2826
|
+
isSource
|
|
2137
2827
|
}
|
|
2138
2828
|
)
|
|
2139
2829
|
] });
|
|
@@ -2187,7 +2877,6 @@ const AnotherAddress = () => {
|
|
|
2187
2877
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2188
2878
|
_switch.Switch,
|
|
2189
2879
|
{
|
|
2190
|
-
className: "data-[state=unchecked]:bg-switch-inactive data-[state=checked]:bg-switch-active",
|
|
2191
2880
|
"aria-pressed": enabled,
|
|
2192
2881
|
checked: enabled,
|
|
2193
2882
|
onClick: () => setEnabled((v) => !v)
|
|
@@ -2206,7 +2895,7 @@ const AnotherAddress = () => {
|
|
|
2206
2895
|
"div",
|
|
2207
2896
|
{
|
|
2208
2897
|
className: utils.cn(
|
|
2209
|
-
"bg-input py-2
|
|
2898
|
+
"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",
|
|
2210
2899
|
{
|
|
2211
2900
|
"py-4": value,
|
|
2212
2901
|
"border border-ring": isFocused,
|
|
@@ -2216,13 +2905,13 @@ const AnotherAddress = () => {
|
|
|
2216
2905
|
children: [
|
|
2217
2906
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 w-full", children: [
|
|
2218
2907
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2219
|
-
|
|
2908
|
+
"textarea",
|
|
2220
2909
|
{
|
|
2221
2910
|
className: utils.cn(
|
|
2222
|
-
"
|
|
2911
|
+
"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"
|
|
2223
2912
|
),
|
|
2224
2913
|
placeholder: t("bridge.anotherAddressPlaceholder"),
|
|
2225
|
-
|
|
2914
|
+
rows: value.length >= 32 ? 2 : 1,
|
|
2226
2915
|
value,
|
|
2227
2916
|
onFocus: () => setIsFocused(true),
|
|
2228
2917
|
onBlur: () => setIsFocused(false),
|
|
@@ -2239,7 +2928,7 @@ const AnotherAddress = () => {
|
|
|
2239
2928
|
button.Button,
|
|
2240
2929
|
{
|
|
2241
2930
|
variant: "secondary",
|
|
2242
|
-
className: "bg-
|
|
2931
|
+
className: "bg-accent text-card-foreground uppercase text-xs",
|
|
2243
2932
|
size: "sm",
|
|
2244
2933
|
onClick: onPaste,
|
|
2245
2934
|
children: t("common.paste")
|
|
@@ -2249,258 +2938,136 @@ const AnotherAddress = () => {
|
|
|
2249
2938
|
{
|
|
2250
2939
|
variant: "ghost",
|
|
2251
2940
|
size: "sm",
|
|
2252
|
-
className: "rounded-full p-0 w-5 h-5 self-start",
|
|
2253
|
-
onClick: () => setValue(""),
|
|
2254
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-4 h-4" })
|
|
2255
|
-
}
|
|
2256
|
-
)
|
|
2257
|
-
]
|
|
2258
|
-
}
|
|
2259
|
-
)
|
|
2260
|
-
},
|
|
2261
|
-
"custom-address-block"
|
|
2262
|
-
) })
|
|
2263
|
-
] });
|
|
2264
|
-
};
|
|
2265
|
-
const
|
|
2266
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
2267
|
-
"svg",
|
|
2268
|
-
{
|
|
2269
|
-
width: "16",
|
|
2270
|
-
height: "16",
|
|
2271
|
-
viewBox: "0 0 16 16",
|
|
2272
|
-
fill: "none",
|
|
2273
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
2274
|
-
...props,
|
|
2275
|
-
children:
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
width: "100%"
|
|
2285
|
-
}
|
|
2286
|
-
}
|
|
2287
|
-
) }),
|
|
2288
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2289
|
-
"circle",
|
|
2290
|
-
{
|
|
2291
|
-
"data-figma-bg-blur-radius": "4.4893",
|
|
2292
|
-
cx: "8",
|
|
2293
|
-
cy: "8",
|
|
2294
|
-
r: "8",
|
|
2295
|
-
fill: "currentColor",
|
|
2296
|
-
fillOpacity: "0.2"
|
|
2297
|
-
}
|
|
2298
|
-
),
|
|
2299
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2300
|
-
"path",
|
|
2301
|
-
{
|
|
2302
|
-
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",
|
|
2303
|
-
stroke: "currentColor",
|
|
2304
|
-
strokeOpacity: "0.5",
|
|
2305
|
-
strokeWidth: "1.68349",
|
|
2306
|
-
strokeLinecap: "round"
|
|
2307
|
-
}
|
|
2308
|
-
),
|
|
2309
|
-
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2310
|
-
"clipPath",
|
|
2311
|
-
{
|
|
2312
|
-
id: "bgblur_0_13066_11660_clip_path",
|
|
2313
|
-
transform: "translate(4.4893 4.4893)",
|
|
2314
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "8", cy: "8", r: "8" })
|
|
2315
|
-
}
|
|
2316
|
-
) })
|
|
2317
|
-
]
|
|
2318
|
-
}
|
|
2319
|
-
);
|
|
2320
|
-
};
|
|
2321
|
-
const Tip = (props) => {
|
|
2322
|
-
const { children, text } = props;
|
|
2323
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(tooltip.Tooltip, { children: [
|
|
2324
|
-
/* @__PURE__ */ jsxRuntime.jsx(tooltip.TooltipTrigger, { children }),
|
|
2325
|
-
/* @__PURE__ */ jsxRuntime.jsx(tooltip.TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: text }) })
|
|
2326
|
-
] });
|
|
2327
|
-
};
|
|
2328
|
-
const BASE_URL = "https://icons-ckg.pages.dev/stargate-light/tokens";
|
|
2329
|
-
const TokenSymbol = ({
|
|
2330
|
-
symbol,
|
|
2331
|
-
className = "w-4 h-4",
|
|
2332
|
-
alt
|
|
2333
|
-
}) => {
|
|
2334
|
-
const normalizedSymbol = normalizeTickerSymbol$1(symbol).toLowerCase();
|
|
2335
|
-
const src = `${BASE_URL}/${normalizedSymbol}.svg`;
|
|
2336
|
-
return /* @__PURE__ */ jsxRuntime.jsx("img", { src, alt: alt ?? symbol, className });
|
|
2337
|
-
};
|
|
2338
|
-
const EVM_CONFIG = {
|
|
2339
|
-
usdtAddress: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
|
2340
|
-
gasEstimates: {
|
|
2341
|
-
approve: 65000n,
|
|
2342
|
-
bridge: 300000n
|
|
2343
|
-
},
|
|
2344
|
-
gasBuffer: 1.2,
|
|
2345
|
-
// 20% buffer
|
|
2346
|
-
timeout: 3e5,
|
|
2347
|
-
// 5 minutes (increased for slower networks)
|
|
2348
|
-
requiredConfirmations: 3
|
|
2349
|
-
// Wait for 3 confirmations for reorg protection
|
|
2350
|
-
};
|
|
2351
|
-
const TON_CONFIG = {
|
|
2352
|
-
apiUrl: "https://toncenter.com/api/v2",
|
|
2353
|
-
timeout: 36e4,
|
|
2354
|
-
// 6 minutes
|
|
2355
|
-
validUntil: 600,
|
|
2356
|
-
// 10 minutes
|
|
2357
|
-
pollingInterval: 5e3,
|
|
2358
|
-
// 5 seconds between transaction status checks
|
|
2359
|
-
estimatedNetworkFee: "100000000"
|
|
2360
|
-
// 0.1 TON in nanoton (conservative estimate)
|
|
2361
|
-
};
|
|
2362
|
-
const TRON_CONFIG = {
|
|
2363
|
-
timeout: 12e4,
|
|
2364
|
-
// 2 minutes (for 19 confirmations)
|
|
2365
|
-
feeLimit: 1e8,
|
|
2366
|
-
// 100 TRX in sun
|
|
2367
|
-
requiredConfirmations: 19,
|
|
2368
|
-
// TRON standard: 19 blocks for confirmation
|
|
2369
|
-
pollingInterval: 3e3
|
|
2370
|
-
// 3 seconds between checks
|
|
2371
|
-
};
|
|
2372
|
-
let tonClientInstance = null;
|
|
2373
|
-
function getTonClient(customClient, apiKey) {
|
|
2374
|
-
if (customClient) {
|
|
2375
|
-
return customClient;
|
|
2376
|
-
}
|
|
2377
|
-
if (!tonClientInstance) {
|
|
2378
|
-
tonClientInstance = new ton.TonClient({
|
|
2379
|
-
endpoint: `${TON_CONFIG.apiUrl}/jsonRPC`,
|
|
2380
|
-
apiKey
|
|
2381
|
-
});
|
|
2382
|
-
}
|
|
2383
|
-
return tonClientInstance;
|
|
2384
|
-
}
|
|
2385
|
-
function getQuoteAmounts(quote, srcToken, dstToken) {
|
|
2386
|
-
if (!quote || !srcToken || !dstToken) {
|
|
2387
|
-
return {
|
|
2388
|
-
inputHuman: 0,
|
|
2389
|
-
outputHuman: 0,
|
|
2390
|
-
outputHumanRounded: "0",
|
|
2391
|
-
minReceivedHuman: 0
|
|
2392
|
-
};
|
|
2393
|
-
}
|
|
2394
|
-
const inputHuman = fromLD(quote.srcAmount, srcToken.decimals);
|
|
2395
|
-
const outputHuman = fromLD(quote.dstAmount, dstToken.decimals);
|
|
2396
|
-
const outputHumanRounded = truncateToDecimals(outputHuman, 2);
|
|
2397
|
-
const minReceivedHuman = fromLD(
|
|
2398
|
-
quote.dstAmountMin || "0",
|
|
2399
|
-
dstToken.decimals
|
|
2400
|
-
);
|
|
2401
|
-
return {
|
|
2402
|
-
inputHuman,
|
|
2403
|
-
outputHuman,
|
|
2404
|
-
outputHumanRounded,
|
|
2405
|
-
minReceivedHuman
|
|
2406
|
-
};
|
|
2407
|
-
}
|
|
2408
|
-
function getQuoteFees(quote, tokens, chains, srcToken, dstToken) {
|
|
2409
|
-
if (!quote || !tokens || !chains) {
|
|
2410
|
-
return {
|
|
2411
|
-
totalUsd: 0,
|
|
2412
|
-
protocolFeeUsd: void 0,
|
|
2413
|
-
messageFeeUsd: void 0,
|
|
2414
|
-
serviceUsd: void 0,
|
|
2415
|
-
blockchainUsd: void 0,
|
|
2416
|
-
inSrcToken: void 0,
|
|
2417
|
-
inDstToken: void 0
|
|
2418
|
-
};
|
|
2419
|
-
}
|
|
2420
|
-
const feeData = computeFeesUsdFromArray(quote.fees, tokens, chains);
|
|
2421
|
-
let inSrcToken = void 0;
|
|
2422
|
-
let inDstToken = void 0;
|
|
2423
|
-
if (srcToken && quote.srcChainKey) {
|
|
2424
|
-
const feeInSrcLD = sumFeeByTokenLD(
|
|
2425
|
-
quote.fees,
|
|
2426
|
-
srcToken.address,
|
|
2427
|
-
quote.srcChainKey
|
|
2428
|
-
);
|
|
2429
|
-
const feeInSrcHuman = fromLD(feeInSrcLD, srcToken.decimals);
|
|
2430
|
-
if (feeInSrcHuman > 0) {
|
|
2431
|
-
inSrcToken = Number(truncateToDecimals(feeInSrcHuman, 8));
|
|
2432
|
-
} else if (feeData.totalUsd > 0 && srcToken.price?.usd) {
|
|
2433
|
-
const feeInSrcApprox = feeData.totalUsd / srcToken.price.usd;
|
|
2434
|
-
inSrcToken = Number(truncateToDecimals(feeInSrcApprox, 8));
|
|
2435
|
-
}
|
|
2436
|
-
}
|
|
2437
|
-
if (dstToken && quote.dstChainKey) {
|
|
2438
|
-
const feeInDstLD = sumFeeByTokenLD(
|
|
2439
|
-
quote.fees,
|
|
2440
|
-
dstToken.address,
|
|
2441
|
-
quote.dstChainKey
|
|
2442
|
-
);
|
|
2443
|
-
const feeInDstHuman = fromLD(feeInDstLD, dstToken.decimals);
|
|
2444
|
-
if (feeInDstHuman > 0) {
|
|
2445
|
-
inDstToken = Number(truncateToDecimals(feeInDstHuman, 8));
|
|
2941
|
+
className: "rounded-full p-0 w-5 h-5 self-start",
|
|
2942
|
+
onClick: () => setValue(""),
|
|
2943
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-4 h-4" })
|
|
2944
|
+
}
|
|
2945
|
+
)
|
|
2946
|
+
]
|
|
2947
|
+
}
|
|
2948
|
+
)
|
|
2949
|
+
},
|
|
2950
|
+
"custom-address-block"
|
|
2951
|
+
) })
|
|
2952
|
+
] });
|
|
2953
|
+
};
|
|
2954
|
+
const InfoIcon = (props) => {
|
|
2955
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2956
|
+
"svg",
|
|
2957
|
+
{
|
|
2958
|
+
width: "16",
|
|
2959
|
+
height: "16",
|
|
2960
|
+
viewBox: "0 0 16 16",
|
|
2961
|
+
fill: "none",
|
|
2962
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
2963
|
+
...props,
|
|
2964
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2965
|
+
"path",
|
|
2966
|
+
{
|
|
2967
|
+
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",
|
|
2968
|
+
stroke: "currentColor",
|
|
2969
|
+
"stroke-width": "1.68349",
|
|
2970
|
+
"stroke-linecap": "round"
|
|
2971
|
+
}
|
|
2972
|
+
)
|
|
2446
2973
|
}
|
|
2447
|
-
}
|
|
2448
|
-
return {
|
|
2449
|
-
totalUsd: feeData.totalUsd,
|
|
2450
|
-
protocolFeeUsd: feeData.protocolFeeUsd,
|
|
2451
|
-
messageFeeUsd: feeData.messageFeeUsd,
|
|
2452
|
-
serviceUsd: feeData.serviceUsd,
|
|
2453
|
-
blockchainUsd: feeData.blockchainUsd,
|
|
2454
|
-
inSrcToken,
|
|
2455
|
-
inDstToken
|
|
2456
|
-
};
|
|
2457
|
-
}
|
|
2458
|
-
function calculateMinReceived(quote, slippageBps, dstToken) {
|
|
2459
|
-
if (!quote || !dstToken) return 0;
|
|
2460
|
-
const dstAmountLD = BigInt(quote.dstAmount);
|
|
2461
|
-
const minAmountLD = dstAmountLD * BigInt(1e4 - slippageBps) / BigInt(1e4);
|
|
2462
|
-
return fromLD(minAmountLD.toString(), dstToken.decimals);
|
|
2463
|
-
}
|
|
2464
|
-
function addTonNetworkFee(quote, chains) {
|
|
2465
|
-
if (!quote || quote.srcChainKey.toLowerCase() !== "ton") {
|
|
2466
|
-
return quote;
|
|
2467
|
-
}
|
|
2468
|
-
const tonChain = chains?.find(
|
|
2469
|
-
(c) => c.chainKey.toLowerCase() === "ton"
|
|
2470
|
-
);
|
|
2471
|
-
if (!tonChain?.nativeCurrency?.address) {
|
|
2472
|
-
console.warn("Could not find TON native currency address");
|
|
2473
|
-
return quote;
|
|
2474
|
-
}
|
|
2475
|
-
const networkFee = {
|
|
2476
|
-
token: tonChain.nativeCurrency.address,
|
|
2477
|
-
chainKey: "ton",
|
|
2478
|
-
amount: TON_CONFIG.estimatedNetworkFee,
|
|
2479
|
-
type: "network"
|
|
2480
|
-
};
|
|
2481
|
-
const hasNetworkFee = quote.fees?.some(
|
|
2482
|
-
(fee) => fee.type === "network" && fee.chainKey === "ton"
|
|
2483
2974
|
);
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
}
|
|
2487
|
-
return {
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
};
|
|
2975
|
+
};
|
|
2976
|
+
const Tip = (props) => {
|
|
2977
|
+
const { children, text } = props;
|
|
2978
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(tooltip.Tooltip, { children: [
|
|
2979
|
+
/* @__PURE__ */ jsxRuntime.jsx(tooltip.TooltipTrigger, { className: "w-4 h-4 rounded-full bg-muted text-muted-foreground", children }),
|
|
2980
|
+
/* @__PURE__ */ jsxRuntime.jsx(tooltip.TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: text }) })
|
|
2981
|
+
] });
|
|
2982
|
+
};
|
|
2983
|
+
const BASE_URL = "https://icons-ckg.pages.dev/stargate-light/tokens";
|
|
2984
|
+
const TokenSymbol = ({
|
|
2985
|
+
symbol,
|
|
2986
|
+
className = "w-4 h-4",
|
|
2987
|
+
alt
|
|
2988
|
+
}) => {
|
|
2989
|
+
const normalizedSymbol = normalizeTickerSymbol$1(symbol).toLowerCase();
|
|
2990
|
+
const src = `${BASE_URL}/${normalizedSymbol}.svg`;
|
|
2991
|
+
return /* @__PURE__ */ jsxRuntime.jsx("img", { src, alt: alt ?? symbol, className });
|
|
2992
|
+
};
|
|
2993
|
+
function getSimpleFallback(chainKey) {
|
|
2994
|
+
const key = chainKey.toLowerCase();
|
|
2995
|
+
if (key === "ton") return 0.15;
|
|
2996
|
+
if (key === "tron") return 10;
|
|
2997
|
+
return 0.01;
|
|
2491
2998
|
}
|
|
2492
|
-
function
|
|
2493
|
-
const
|
|
2494
|
-
const
|
|
2495
|
-
const
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2999
|
+
function useGasEstimate(amountNum) {
|
|
3000
|
+
const { fromChain } = useChainsStore();
|
|
3001
|
+
const { selectedAssetSymbol } = useTokensStore();
|
|
3002
|
+
const { srcAddress } = useAddresses();
|
|
3003
|
+
const { balances, isLoading: balancesLoading } = useBalances(
|
|
3004
|
+
fromChain?.chainKey,
|
|
3005
|
+
srcAddress
|
|
3006
|
+
);
|
|
3007
|
+
const { quote } = useBridgeQuoteStore();
|
|
3008
|
+
const balancesKnown = !balancesLoading;
|
|
3009
|
+
const chainKey = fromChain?.chainKey;
|
|
3010
|
+
const nativeCurrencySymbol = fromChain?.nativeCurrency?.symbol;
|
|
3011
|
+
const nativeCurrencyAddress = fromChain?.nativeCurrency?.address;
|
|
3012
|
+
const nativeCurrencyDecimals = fromChain?.nativeCurrency?.decimals;
|
|
3013
|
+
const quoteFees = quote?.fees ? JSON.stringify(quote.fees) : null;
|
|
3014
|
+
const quoteSrcChainKey = quote?.srcChainKey;
|
|
3015
|
+
const nativeBalanceValue = nativeCurrencySymbol ? Number(balances[nativeCurrencySymbol.toUpperCase()]?.balance ?? 0) : 0;
|
|
3016
|
+
const result = react.useMemo(() => {
|
|
3017
|
+
if (!chainKey || !nativeCurrencySymbol) {
|
|
3018
|
+
return {
|
|
3019
|
+
nativeSym: "",
|
|
3020
|
+
nativeBalance: 0,
|
|
3021
|
+
requiredNative: 0,
|
|
3022
|
+
balancesKnown,
|
|
3023
|
+
isNativeSelected: false,
|
|
3024
|
+
hasEnoughGas: true
|
|
3025
|
+
};
|
|
3026
|
+
}
|
|
3027
|
+
const nativeSym = nativeCurrencySymbol.toUpperCase();
|
|
3028
|
+
const nativeBalance = nativeBalanceValue;
|
|
3029
|
+
const isNativeSelected = nativeSym === (selectedAssetSymbol || "").toUpperCase();
|
|
3030
|
+
let requiredNative = 0;
|
|
3031
|
+
if (quoteFees && quoteSrcChainKey === chainKey) {
|
|
3032
|
+
const fees = JSON.parse(quoteFees);
|
|
3033
|
+
const feesInNative = fees.filter(
|
|
3034
|
+
(f) => f.chainKey === chainKey && f.token === nativeCurrencyAddress
|
|
3035
|
+
).reduce(
|
|
3036
|
+
(sum, f) => sum + BigInt(f.amount || "0"),
|
|
3037
|
+
0n
|
|
3038
|
+
);
|
|
3039
|
+
const decimals = nativeCurrencyDecimals || 18;
|
|
3040
|
+
requiredNative = Number(feesInNative) / Math.pow(10, decimals);
|
|
3041
|
+
} else {
|
|
3042
|
+
requiredNative = getSimpleFallback(chainKey);
|
|
3043
|
+
}
|
|
3044
|
+
let hasEnoughGas = true;
|
|
3045
|
+
if (isNativeSelected) {
|
|
3046
|
+
hasEnoughGas = nativeBalance - (amountNum ?? 0) >= requiredNative;
|
|
3047
|
+
} else {
|
|
3048
|
+
hasEnoughGas = nativeBalance >= requiredNative;
|
|
3049
|
+
}
|
|
3050
|
+
return {
|
|
3051
|
+
nativeSym,
|
|
3052
|
+
nativeBalance,
|
|
3053
|
+
requiredNative,
|
|
3054
|
+
balancesKnown,
|
|
3055
|
+
isNativeSelected,
|
|
3056
|
+
hasEnoughGas: balancesKnown ? hasEnoughGas : true
|
|
3057
|
+
};
|
|
3058
|
+
}, [
|
|
3059
|
+
chainKey,
|
|
3060
|
+
nativeCurrencySymbol,
|
|
3061
|
+
nativeCurrencyAddress,
|
|
3062
|
+
nativeCurrencyDecimals,
|
|
3063
|
+
selectedAssetSymbol,
|
|
3064
|
+
quoteFees,
|
|
3065
|
+
quoteSrcChainKey,
|
|
3066
|
+
amountNum,
|
|
3067
|
+
balancesKnown,
|
|
3068
|
+
nativeBalanceValue
|
|
3069
|
+
]);
|
|
3070
|
+
return result;
|
|
2504
3071
|
}
|
|
2505
3072
|
function getRouteDisplayName(route) {
|
|
2506
3073
|
if (!route) return "Stargate Bridge";
|
|
@@ -2517,6 +3084,7 @@ const Details = () => {
|
|
|
2517
3084
|
const { toChain, fromChain, chains } = useChainsStore();
|
|
2518
3085
|
const { quote, status } = useBridgeQuoteStore();
|
|
2519
3086
|
const { slippageBps, routePriority } = useSettingsStore();
|
|
3087
|
+
const gas = useGasEstimate();
|
|
2520
3088
|
const dstToken = resolveTokenOnChainFromMatrix$2(
|
|
2521
3089
|
assetMatrix,
|
|
2522
3090
|
selectedAssetSymbol,
|
|
@@ -2527,9 +3095,8 @@ const Details = () => {
|
|
|
2527
3095
|
selectedAssetSymbol,
|
|
2528
3096
|
fromChain?.chainKey
|
|
2529
3097
|
);
|
|
2530
|
-
const quoteWithFees = addTonNetworkFee(quote, chains);
|
|
2531
3098
|
const quoteDetails = getQuoteDetails(
|
|
2532
|
-
|
|
3099
|
+
quote,
|
|
2533
3100
|
srcToken,
|
|
2534
3101
|
dstToken,
|
|
2535
3102
|
tokens,
|
|
@@ -2540,21 +3107,7 @@ const Details = () => {
|
|
|
2540
3107
|
const isLoading = status === "loading";
|
|
2541
3108
|
const receiveText = quoteDetails.outputAmount != null ? Number(quoteDetails.outputAmount).toFixed(6) : "0.00";
|
|
2542
3109
|
const etaText = quoteDetails.etaSeconds != null ? `≈ ${Math.max(1, Math.round(quoteDetails.etaSeconds / 60))}m` : "—";
|
|
2543
|
-
const
|
|
2544
|
-
const feeInDst = quoteDetails.fees?.inDstToken;
|
|
2545
|
-
const totalFeeUsd = quoteDetails.fees?.totalUsd;
|
|
2546
|
-
const totalFeeDisplay = (() => {
|
|
2547
|
-
if (feeInSrc != null && srcToken?.symbol) {
|
|
2548
|
-
return `${Number(feeInSrc).toFixed(6)} ${srcToken.symbol.toUpperCase()}`;
|
|
2549
|
-
}
|
|
2550
|
-
if (feeInDst != null && dstToken?.symbol) {
|
|
2551
|
-
return `${Number(feeInDst).toFixed(6)} ${dstToken.symbol.toUpperCase()}`;
|
|
2552
|
-
}
|
|
2553
|
-
if (totalFeeUsd != null) {
|
|
2554
|
-
return formatUsd(totalFeeUsd);
|
|
2555
|
-
}
|
|
2556
|
-
return "—";
|
|
2557
|
-
})();
|
|
3110
|
+
const totalFeeDisplay = gas.requiredNative > 0 ? `${gas.requiredNative.toFixed(6)} ${gas.nativeSym}` : "—";
|
|
2558
3111
|
const currentSlippageText = formatPercentage(slippageBps);
|
|
2559
3112
|
const routeText = quote?.route ? getRouteDisplayName(quote.route) : t(`settings.routePresets.${routePriority}`);
|
|
2560
3113
|
return /* @__PURE__ */ jsxRuntime.jsx(accordion.Accordion, { type: "single", collapsible: true, className: "w-full", children: /* @__PURE__ */ jsxRuntime.jsxs(accordion.AccordionItem, { value: "item-1", className: "bg-muted/50 rounded-sm", children: [
|
|
@@ -2574,7 +3127,7 @@ const Details = () => {
|
|
|
2574
3127
|
DetailsRow,
|
|
2575
3128
|
{
|
|
2576
3129
|
label: t("transaction.route"),
|
|
2577
|
-
value: /* @__PURE__ */ jsxRuntime.jsxs("
|
|
3130
|
+
value: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [
|
|
2578
3131
|
/* @__PURE__ */ jsxRuntime.jsx(StargateIcon, { className: "w-4 h-4" }),
|
|
2579
3132
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "", children: routeText })
|
|
2580
3133
|
] })
|
|
@@ -2601,7 +3154,14 @@ const Details = () => {
|
|
|
2601
3154
|
label: t("transaction.totalFee"),
|
|
2602
3155
|
value: /* @__PURE__ */ jsxRuntime.jsxs("strong", { className: "inline-flex items-center gap-1", children: [
|
|
2603
3156
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: totalFeeDisplay }),
|
|
2604
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3157
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3158
|
+
TokenSymbol,
|
|
3159
|
+
{
|
|
3160
|
+
symbol: gas.nativeSym,
|
|
3161
|
+
className: "w-4 h-4",
|
|
3162
|
+
alt: "token"
|
|
3163
|
+
}
|
|
3164
|
+
)
|
|
2605
3165
|
] }),
|
|
2606
3166
|
isLoading
|
|
2607
3167
|
}
|
|
@@ -2616,7 +3176,7 @@ const DetailsRow = ({
|
|
|
2616
3176
|
}) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center", children: [
|
|
2617
3177
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2618
3178
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-priority font-normal", children: label }),
|
|
2619
|
-
/* @__PURE__ */ jsxRuntime.jsx(Tip, { text: label, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3179
|
+
/* @__PURE__ */ jsxRuntime.jsx(Tip, { text: label, children: /* @__PURE__ */ jsxRuntime.jsx(InfoIcon, {}) })
|
|
2620
3180
|
] }),
|
|
2621
3181
|
isLoading ? /* @__PURE__ */ jsxRuntime.jsx(skeleton.Skeleton, { className: "h-4 w-16 rounded-md" }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-foreground text-sm", children: value ?? "—" })
|
|
2622
3182
|
] });
|
|
@@ -2669,6 +3229,20 @@ const useTransactionStore = zustand.create((set, get) => ({
|
|
|
2669
3229
|
};
|
|
2670
3230
|
set({ current: next });
|
|
2671
3231
|
},
|
|
3232
|
+
updateActualFee: (feeValue, feeSymbol) => {
|
|
3233
|
+
const cur = get().current;
|
|
3234
|
+
if (!cur) return;
|
|
3235
|
+
const next = {
|
|
3236
|
+
...cur,
|
|
3237
|
+
metadata: {
|
|
3238
|
+
...cur.metadata,
|
|
3239
|
+
actualFeeValue: feeValue,
|
|
3240
|
+
actualFeeSymbol: feeSymbol
|
|
3241
|
+
},
|
|
3242
|
+
updatedAt: Date.now()
|
|
3243
|
+
};
|
|
3244
|
+
set({ current: next });
|
|
3245
|
+
},
|
|
2672
3246
|
reset: () => {
|
|
2673
3247
|
set({ current: void 0 });
|
|
2674
3248
|
}
|
|
@@ -2814,9 +3388,13 @@ function isUserRejection(error) {
|
|
|
2814
3388
|
if (error instanceof TransactionRejectedError) {
|
|
2815
3389
|
return true;
|
|
2816
3390
|
}
|
|
3391
|
+
if (typeof error === "string") {
|
|
3392
|
+
const message = error.toLowerCase();
|
|
3393
|
+
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");
|
|
3394
|
+
}
|
|
2817
3395
|
if (error instanceof Error) {
|
|
2818
3396
|
const message = error.message.toLowerCase();
|
|
2819
|
-
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");
|
|
3397
|
+
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");
|
|
2820
3398
|
}
|
|
2821
3399
|
return false;
|
|
2822
3400
|
}
|
|
@@ -2835,17 +3413,13 @@ function toChainStrategyError(error, chainKey, context) {
|
|
|
2835
3413
|
error
|
|
2836
3414
|
);
|
|
2837
3415
|
}
|
|
2838
|
-
return new ChainStrategyError(
|
|
2839
|
-
String(error),
|
|
2840
|
-
"UNKNOWN_ERROR",
|
|
2841
|
-
chainKey
|
|
2842
|
-
);
|
|
3416
|
+
return new ChainStrategyError(String(error), "UNKNOWN_ERROR", chainKey);
|
|
2843
3417
|
}
|
|
2844
3418
|
function useBridgeTransaction() {
|
|
2845
3419
|
const { quote } = useBridgeQuoteStore();
|
|
2846
3420
|
const { chainRegistry } = useChainStrategies();
|
|
2847
3421
|
const { srcAddress, dstAddress } = useAddresses();
|
|
2848
|
-
const { assetMatrix,
|
|
3422
|
+
const { assetMatrix, selectedAssetSymbol } = useTokensStore();
|
|
2849
3423
|
const { chains } = useChainsStore();
|
|
2850
3424
|
const txStore = useTransactionStore();
|
|
2851
3425
|
const [isProcessing, setIsProcessing] = react.useState(false);
|
|
@@ -2874,31 +3448,33 @@ function useBridgeTransaction() {
|
|
|
2874
3448
|
}
|
|
2875
3449
|
const srcChain = chains?.find((c) => c.chainKey === quote.srcChainKey);
|
|
2876
3450
|
const dstChain = chains?.find((c) => c.chainKey === quote.dstChainKey);
|
|
2877
|
-
const
|
|
2878
|
-
const amounts = getQuoteAmounts(quoteWithFees, srcToken, dstToken);
|
|
2879
|
-
const fees = getQuoteFees(quoteWithFees, tokens, chains, srcToken, dstToken);
|
|
3451
|
+
const amounts = getQuoteAmounts(quote, srcToken, dstToken);
|
|
2880
3452
|
const metadata = {
|
|
2881
3453
|
srcChainName: srcChain?.name || quote.srcChainKey,
|
|
2882
3454
|
dstChainName: dstChain?.name || quote.dstChainKey,
|
|
2883
3455
|
srcTokenSymbol: srcToken?.symbol || quote.srcToken,
|
|
2884
3456
|
dstTokenSymbol: dstToken?.symbol || quote.dstToken,
|
|
2885
3457
|
srcAmountHuman: amounts.inputHuman,
|
|
2886
|
-
dstAmountHuman: amounts.outputHuman
|
|
2887
|
-
//
|
|
2888
|
-
totalFeeValue: fees.inSrcToken ?? fees.inDstToken ?? void 0,
|
|
2889
|
-
totalFeeSymbol: fees.inSrcToken ? srcToken?.symbol : fees.inDstToken ? dstToken?.symbol : void 0
|
|
3458
|
+
dstAmountHuman: amounts.outputHuman
|
|
3459
|
+
// Actual fee will be updated when transaction completes
|
|
2890
3460
|
};
|
|
2891
3461
|
txStore.setTransaction(quote, "executing", metadata);
|
|
2892
3462
|
setIsProcessing(true);
|
|
2893
3463
|
try {
|
|
2894
3464
|
const steps = quote.steps || [];
|
|
2895
3465
|
if (steps.length === 0) {
|
|
2896
|
-
throw new InvalidStepsError(
|
|
3466
|
+
throw new InvalidStepsError(
|
|
3467
|
+
quote.srcChainKey,
|
|
3468
|
+
"No transaction steps found in quote"
|
|
3469
|
+
);
|
|
2897
3470
|
}
|
|
2898
3471
|
const chainKey = steps[0].chainKey;
|
|
2899
3472
|
const strategy = chainRegistry.getStrategy(chainKey);
|
|
2900
3473
|
if (!strategy) {
|
|
2901
|
-
throw new InvalidStepsError(
|
|
3474
|
+
throw new InvalidStepsError(
|
|
3475
|
+
chainKey,
|
|
3476
|
+
`No strategy available for chain: ${chainKey}`
|
|
3477
|
+
);
|
|
2902
3478
|
}
|
|
2903
3479
|
if (!strategy.isConnected()) {
|
|
2904
3480
|
throw new WalletNotConnectedError(chainKey);
|
|
@@ -2910,7 +3486,6 @@ function useBridgeTransaction() {
|
|
|
2910
3486
|
srcChainKey: quote.srcChainKey,
|
|
2911
3487
|
dstChainKey: quote.dstChainKey
|
|
2912
3488
|
};
|
|
2913
|
-
console.log(steps);
|
|
2914
3489
|
const txResult = await strategy.executeSteps(steps, context, (hash) => {
|
|
2915
3490
|
txStore.setSrcHash(hash);
|
|
2916
3491
|
txStore.updateStatus("processing");
|
|
@@ -2921,6 +3496,21 @@ function useBridgeTransaction() {
|
|
|
2921
3496
|
if (result.dstTxHash) {
|
|
2922
3497
|
txStore.setDstHash(result.dstTxHash);
|
|
2923
3498
|
}
|
|
3499
|
+
if (result.actualFeeValue) {
|
|
3500
|
+
let feeSymbol = result.actualFeeSymbol;
|
|
3501
|
+
if (!feeSymbol) {
|
|
3502
|
+
const srcChain2 = chains?.find(
|
|
3503
|
+
(c) => c.chainKey === quote.srcChainKey
|
|
3504
|
+
);
|
|
3505
|
+
feeSymbol = srcChain2?.nativeCurrency?.symbol || "";
|
|
3506
|
+
}
|
|
3507
|
+
if (feeSymbol) {
|
|
3508
|
+
const feeValue = parseFloat(result.actualFeeValue);
|
|
3509
|
+
if (!isNaN(feeValue)) {
|
|
3510
|
+
txStore.updateActualFee(feeValue, feeSymbol);
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
2924
3514
|
txStore.updateStatus("completed");
|
|
2925
3515
|
console.log("Transaction completed successfully");
|
|
2926
3516
|
} else {
|
|
@@ -2938,19 +3528,26 @@ function useBridgeTransaction() {
|
|
|
2938
3528
|
console.error("Error tracking completion:", err);
|
|
2939
3529
|
});
|
|
2940
3530
|
} else {
|
|
2941
|
-
throw new TransactionFailedError(
|
|
3531
|
+
throw new TransactionFailedError(
|
|
3532
|
+
chainKey,
|
|
3533
|
+
"Transaction hash not received from wallet"
|
|
3534
|
+
);
|
|
2942
3535
|
}
|
|
2943
3536
|
return txResult;
|
|
2944
3537
|
} catch (err) {
|
|
2945
3538
|
if (isUserRejection(err)) {
|
|
2946
3539
|
txStore.setError("TRANSACTION_REJECTED");
|
|
2947
|
-
throw new TransactionFailedError(
|
|
3540
|
+
throw new TransactionFailedError(
|
|
3541
|
+
quote.srcChainKey,
|
|
3542
|
+
"Transaction rejected by user"
|
|
3543
|
+
);
|
|
2948
3544
|
}
|
|
2949
3545
|
if (ChainStrategyError.isChainStrategyError(err)) {
|
|
2950
3546
|
txStore.setError(err.code, { chainKey: err.chainKey });
|
|
2951
3547
|
console.error("Chain strategy error:", err.toJSON());
|
|
2952
3548
|
throw err;
|
|
2953
3549
|
}
|
|
3550
|
+
console.log(err);
|
|
2954
3551
|
txStore.setError("UNKNOWN_ERROR");
|
|
2955
3552
|
throw err;
|
|
2956
3553
|
} finally {
|
|
@@ -2963,66 +3560,6 @@ function useBridgeTransaction() {
|
|
|
2963
3560
|
hasQuote: !!quote
|
|
2964
3561
|
};
|
|
2965
3562
|
}
|
|
2966
|
-
function useGasEstimate(amountNum) {
|
|
2967
|
-
const { fromChain } = useChainsStore();
|
|
2968
|
-
const { selectedAssetSymbol, tokens } = useTokensStore();
|
|
2969
|
-
const getSourceGasReserveHuman = useSettingsStore(
|
|
2970
|
-
(state) => state.getSourceGasReserveHuman
|
|
2971
|
-
);
|
|
2972
|
-
const { srcAddress } = useAddresses();
|
|
2973
|
-
const { balances, isLoading: balancesLoading } = useBalances(
|
|
2974
|
-
fromChain?.chainKey,
|
|
2975
|
-
srcAddress
|
|
2976
|
-
);
|
|
2977
|
-
const { chainRegistry } = useChainStrategies();
|
|
2978
|
-
const balancesKnown = !balancesLoading;
|
|
2979
|
-
const strategy = react.useMemo(() => {
|
|
2980
|
-
if (!fromChain) return null;
|
|
2981
|
-
return chainRegistry.getStrategy(fromChain.chainKey);
|
|
2982
|
-
}, [fromChain, chainRegistry]);
|
|
2983
|
-
const { data: gasRequirement } = reactQuery.useQuery({
|
|
2984
|
-
queryKey: [
|
|
2985
|
-
"gas-estimate",
|
|
2986
|
-
fromChain?.chainKey,
|
|
2987
|
-
selectedAssetSymbol,
|
|
2988
|
-
amountNum,
|
|
2989
|
-
balances
|
|
2990
|
-
],
|
|
2991
|
-
queryFn: async () => {
|
|
2992
|
-
if (!fromChain || !strategy) {
|
|
2993
|
-
return null;
|
|
2994
|
-
}
|
|
2995
|
-
const selectedToken = tokens?.find(
|
|
2996
|
-
(t) => t.symbol.toUpperCase() === selectedAssetSymbol?.toUpperCase()
|
|
2997
|
-
) || null;
|
|
2998
|
-
const nativeTokenSymbol = fromChain.nativeCurrency?.symbol ?? "";
|
|
2999
|
-
const nativeDecimals = fromChain.nativeCurrency?.decimals || 18;
|
|
3000
|
-
const reserveFallback = getSourceGasReserveHuman(fromChain.chainKey);
|
|
3001
|
-
const result = await strategy.estimateGasRequirement({
|
|
3002
|
-
selectedToken,
|
|
3003
|
-
nativeTokenSymbol,
|
|
3004
|
-
amount: amountNum,
|
|
3005
|
-
balances,
|
|
3006
|
-
nativeDecimals,
|
|
3007
|
-
reserveFallback
|
|
3008
|
-
});
|
|
3009
|
-
return result;
|
|
3010
|
-
},
|
|
3011
|
-
enabled: !!fromChain && !!strategy,
|
|
3012
|
-
staleTime: 3e4,
|
|
3013
|
-
gcTime: 5 * 6e4,
|
|
3014
|
-
refetchOnWindowFocus: false,
|
|
3015
|
-
retry: 1
|
|
3016
|
-
});
|
|
3017
|
-
return {
|
|
3018
|
-
nativeSym: gasRequirement?.nativeSym || "",
|
|
3019
|
-
nativeBalance: gasRequirement?.nativeBalance || 0,
|
|
3020
|
-
requiredNative: gasRequirement?.requiredNative || 0,
|
|
3021
|
-
balancesKnown,
|
|
3022
|
-
isNativeSelected: gasRequirement?.isNativeSelected || false,
|
|
3023
|
-
hasEnoughGas: balancesKnown ? gasRequirement?.hasEnoughGas ?? true : true
|
|
3024
|
-
};
|
|
3025
|
-
}
|
|
3026
3563
|
function useBalanceCheck(amountNum, gas) {
|
|
3027
3564
|
const { fromChain } = useChainsStore();
|
|
3028
3565
|
const { selectedAssetSymbol } = useTokensStore();
|
|
@@ -3353,8 +3890,8 @@ const WalletSelectModal = () => {
|
|
|
3353
3890
|
{
|
|
3354
3891
|
strategy: tonWallet,
|
|
3355
3892
|
walletId: "ton",
|
|
3356
|
-
name: t("wallets.
|
|
3357
|
-
icon:
|
|
3893
|
+
name: t("wallets.tonconnect"),
|
|
3894
|
+
icon: TonConnectIcon
|
|
3358
3895
|
},
|
|
3359
3896
|
{
|
|
3360
3897
|
strategy: metaMaskWallet,
|
|
@@ -3392,8 +3929,8 @@ const WalletSelectModal = () => {
|
|
|
3392
3929
|
const tonWallets = [
|
|
3393
3930
|
{
|
|
3394
3931
|
id: "ton",
|
|
3395
|
-
name: t("wallets.
|
|
3396
|
-
icon:
|
|
3932
|
+
name: t("wallets.tonconnect"),
|
|
3933
|
+
icon: TonConnectIcon,
|
|
3397
3934
|
enabled: true
|
|
3398
3935
|
}
|
|
3399
3936
|
];
|
|
@@ -3605,7 +4142,7 @@ const SuccessStep = ({
|
|
|
3605
4142
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 pt-10 px-8 flex-1 justify-start items-center text-center noise bg-background", children: [
|
|
3606
4143
|
icon,
|
|
3607
4144
|
/* @__PURE__ */ jsxRuntime.jsx(dialog.DialogHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(dialog.DialogTitle, { children: t("transaction.success") }) }),
|
|
3608
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full space-y-
|
|
4145
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full space-y-2 mt-4 relative z-10 pb-14", children: [
|
|
3609
4146
|
metadata?.srcAmountHuman && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center", children: [
|
|
3610
4147
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: t("transaction.bridged") }),
|
|
3611
4148
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1 font-medium", children: [
|
|
@@ -3617,15 +4154,13 @@ const SuccessStep = ({
|
|
|
3617
4154
|
] }),
|
|
3618
4155
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center", children: [
|
|
3619
4156
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: t("transaction.transferTitle") }),
|
|
3620
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-medium", children: [
|
|
4157
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-medium space-x-1", children: [
|
|
3621
4158
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex gap-1 items-center", children: [
|
|
3622
4159
|
metadata?.srcChainName,
|
|
3623
4160
|
" ",
|
|
3624
4161
|
/* @__PURE__ */ jsxRuntime.jsx(NetworkSymbol, { chainKey: metadata.srcChainName })
|
|
3625
4162
|
] }),
|
|
3626
|
-
" ",
|
|
3627
|
-
"→",
|
|
3628
|
-
" ",
|
|
4163
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "→" }),
|
|
3629
4164
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex gap-1 items-center", children: [
|
|
3630
4165
|
metadata?.dstChainName,
|
|
3631
4166
|
" ",
|
|
@@ -3639,17 +4174,17 @@ const SuccessStep = ({
|
|
|
3639
4174
|
"button",
|
|
3640
4175
|
{
|
|
3641
4176
|
onClick: handleOpenExplorer,
|
|
3642
|
-
className: "font-medium
|
|
4177
|
+
className: "font-medium cursor-pointer inline-flex items-center gap-1 underline hover:no-underline",
|
|
3643
4178
|
children: formatHash(srcTxHash)
|
|
3644
4179
|
}
|
|
3645
4180
|
)
|
|
3646
4181
|
] }),
|
|
3647
|
-
metadata?.
|
|
4182
|
+
metadata?.actualFeeValue && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center", children: [
|
|
3648
4183
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: t("transaction.finalFee") }),
|
|
3649
4184
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-medium", children: [
|
|
3650
|
-
metadata.
|
|
4185
|
+
metadata.actualFeeValue,
|
|
3651
4186
|
" ",
|
|
3652
|
-
metadata.
|
|
4187
|
+
metadata.actualFeeSymbol
|
|
3653
4188
|
] })
|
|
3654
4189
|
] })
|
|
3655
4190
|
] })
|
|
@@ -3746,7 +4281,7 @@ const useTokens = () => {
|
|
|
3746
4281
|
return { ...query };
|
|
3747
4282
|
};
|
|
3748
4283
|
const useChains = () => {
|
|
3749
|
-
const { setChains, setFromChain } = useChainsStore();
|
|
4284
|
+
const { setChains, setFromChain, fromChain } = useChainsStore();
|
|
3750
4285
|
const query = reactQuery.useQuery({
|
|
3751
4286
|
queryKey: ["chains"],
|
|
3752
4287
|
queryFn: () => getChains(),
|
|
@@ -3760,10 +4295,16 @@ const useChains = () => {
|
|
|
3760
4295
|
});
|
|
3761
4296
|
react.useEffect(() => {
|
|
3762
4297
|
if (query.isSuccess && query.data?.length) {
|
|
4298
|
+
console.log(
|
|
4299
|
+
`[DEBUG] Loaded ${query.data.length} chains from API:`,
|
|
4300
|
+
query.data.map((c) => c.chainKey)
|
|
4301
|
+
);
|
|
3763
4302
|
setChains(query.data);
|
|
3764
|
-
|
|
4303
|
+
if (!fromChain) {
|
|
4304
|
+
setFromChain(query.data[0]);
|
|
4305
|
+
}
|
|
3765
4306
|
}
|
|
3766
|
-
}, [query.isSuccess, query.data
|
|
4307
|
+
}, [query.isSuccess, query.data]);
|
|
3767
4308
|
react.useEffect(() => {
|
|
3768
4309
|
if (query.isError && query.error) {
|
|
3769
4310
|
console.error(query.error.name, query.error.message);
|
|
@@ -3921,7 +4462,9 @@ async function getEvmBalances(publicClient, address, tokens, priorityToken) {
|
|
|
3921
4462
|
}
|
|
3922
4463
|
} catch (error) {
|
|
3923
4464
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3924
|
-
const isZeroDataError = errorMessage.includes(
|
|
4465
|
+
const isZeroDataError = errorMessage.includes(
|
|
4466
|
+
'returned no data ("0x")'
|
|
4467
|
+
);
|
|
3925
4468
|
if (!isZeroDataError) {
|
|
3926
4469
|
console.debug(
|
|
3927
4470
|
`Failed to get priority token balance for ${priorityToken.symbol}:`,
|
|
@@ -4032,12 +4575,9 @@ async function getTonBalances(address, tokens, customTonClient, tonApiKey) {
|
|
|
4032
4575
|
}
|
|
4033
4576
|
const client = getTonClient(customTonClient, tonApiKey);
|
|
4034
4577
|
const accountAddress = ton.Address.parse(address);
|
|
4035
|
-
console.log(address);
|
|
4036
|
-
console.log(tokens);
|
|
4037
4578
|
try {
|
|
4038
4579
|
const balance = await client.getBalance(accountAddress);
|
|
4039
4580
|
const tonBalance = Number(balance) / 1e9;
|
|
4040
|
-
console.log("tonBalance", tonBalance);
|
|
4041
4581
|
if (tonBalance > 0) {
|
|
4042
4582
|
balances.TON = { balance: tonBalance, address: "ton-native" };
|
|
4043
4583
|
}
|
|
@@ -4094,8 +4634,23 @@ async function getTronBalances(tronWeb, address, tokens) {
|
|
|
4094
4634
|
try {
|
|
4095
4635
|
if (!tronWeb) throw new Error("TronWeb not available");
|
|
4096
4636
|
const ownerB58 = toTronBase58(address, tronWeb);
|
|
4637
|
+
try {
|
|
4638
|
+
const trxBalanceSun = await tronWeb.trx.getBalance(ownerB58);
|
|
4639
|
+
const sunNum = Number(trxBalanceSun);
|
|
4640
|
+
const trxVal = tronWeb.fromSun(sunNum);
|
|
4641
|
+
const trxStr = typeof trxVal === "string" ? trxVal : trxVal.toString();
|
|
4642
|
+
const trxBalance = parseFloat(trxStr);
|
|
4643
|
+
if (trxBalance > 0) {
|
|
4644
|
+
balances.TRX = { balance: trxBalance, address: ownerB58 };
|
|
4645
|
+
}
|
|
4646
|
+
} catch (error) {
|
|
4647
|
+
console.warn("Failed to get native TRX balance:", error);
|
|
4648
|
+
}
|
|
4097
4649
|
for (const token of tokens) {
|
|
4098
4650
|
try {
|
|
4651
|
+
if (isNativeAddress(token.address) && token.symbol.toUpperCase() === "TRX") {
|
|
4652
|
+
continue;
|
|
4653
|
+
}
|
|
4099
4654
|
let balance = 0;
|
|
4100
4655
|
if (isNativeAddress(token.address)) {
|
|
4101
4656
|
const trxBalanceSun = await tronWeb.trx.getBalance(ownerB58);
|
|
@@ -4182,6 +4737,9 @@ class EvmChainStrategy {
|
|
|
4182
4737
|
getConnectLabel(t) {
|
|
4183
4738
|
return t("wallets.connectEvmWallet");
|
|
4184
4739
|
}
|
|
4740
|
+
getClient() {
|
|
4741
|
+
return this.provider;
|
|
4742
|
+
}
|
|
4185
4743
|
async getBalances(address, tokens, priorityToken) {
|
|
4186
4744
|
if (!this.publicClient) {
|
|
4187
4745
|
console.warn("No publicClient available for balance query");
|
|
@@ -4198,52 +4756,6 @@ class EvmChainStrategy {
|
|
|
4198
4756
|
if (!address) return false;
|
|
4199
4757
|
return /^0x[0-9a-fA-F]{40}$/.test(address);
|
|
4200
4758
|
}
|
|
4201
|
-
async estimateGasRequirement(params) {
|
|
4202
|
-
const provider = this.provider;
|
|
4203
|
-
const {
|
|
4204
|
-
selectedToken,
|
|
4205
|
-
nativeTokenSymbol,
|
|
4206
|
-
amount,
|
|
4207
|
-
balances,
|
|
4208
|
-
nativeDecimals,
|
|
4209
|
-
reserveFallback
|
|
4210
|
-
} = params;
|
|
4211
|
-
const nativeSym = nativeTokenSymbol.toUpperCase();
|
|
4212
|
-
const isNativeSelected = nativeSym === (selectedToken?.symbol ?? "").toUpperCase();
|
|
4213
|
-
const nativeBalance = Number(balances[nativeSym]?.balance ?? 0);
|
|
4214
|
-
let estimatedGas = null;
|
|
4215
|
-
if (provider) {
|
|
4216
|
-
try {
|
|
4217
|
-
const gasPriceHex = await provider.send("eth_gasPrice", []);
|
|
4218
|
-
const gasPrice = BigInt(gasPriceHex);
|
|
4219
|
-
const approveGas = isNativeSelected ? 0n : EVM_CONFIG.gasEstimates.approve;
|
|
4220
|
-
const bridgeGas = EVM_CONFIG.gasEstimates.bridge;
|
|
4221
|
-
const totalGas = approveGas + bridgeGas;
|
|
4222
|
-
const bufferMultiplier = BigInt(Math.floor(EVM_CONFIG.gasBuffer * 100));
|
|
4223
|
-
const requiredWei = gasPrice * totalGas * bufferMultiplier / 100n;
|
|
4224
|
-
const { formatUnits } = await import("ethers");
|
|
4225
|
-
estimatedGas = Number(formatUnits(requiredWei, nativeDecimals));
|
|
4226
|
-
} catch {
|
|
4227
|
-
estimatedGas = null;
|
|
4228
|
-
}
|
|
4229
|
-
}
|
|
4230
|
-
const requiredNative = estimatedGas ?? reserveFallback;
|
|
4231
|
-
const amountNum = amount ?? 0;
|
|
4232
|
-
let hasEnoughGas = true;
|
|
4233
|
-
if (isNativeSelected) {
|
|
4234
|
-
hasEnoughGas = nativeBalance - amountNum >= requiredNative;
|
|
4235
|
-
} else {
|
|
4236
|
-
hasEnoughGas = nativeBalance >= requiredNative;
|
|
4237
|
-
}
|
|
4238
|
-
return {
|
|
4239
|
-
estimatedGas,
|
|
4240
|
-
nativeBalance,
|
|
4241
|
-
requiredNative,
|
|
4242
|
-
hasEnoughGas,
|
|
4243
|
-
nativeSym,
|
|
4244
|
-
isNativeSelected
|
|
4245
|
-
};
|
|
4246
|
-
}
|
|
4247
4759
|
validateSteps(steps) {
|
|
4248
4760
|
if (!steps || steps.length === 0) {
|
|
4249
4761
|
throw new InvalidStepsError("evm", "No transaction steps provided");
|
|
@@ -4336,8 +4848,24 @@ class EvmChainStrategy {
|
|
|
4336
4848
|
console.log(
|
|
4337
4849
|
`EVM transaction confirmed in block ${receipt.blockNumber} with ${EVM_CONFIG.requiredConfirmations} confirmations`
|
|
4338
4850
|
);
|
|
4851
|
+
let actualFeeValue;
|
|
4852
|
+
try {
|
|
4853
|
+
const gasUsed = receipt.gasUsed;
|
|
4854
|
+
const effectiveGasPrice = receipt.gasPrice;
|
|
4855
|
+
if (gasUsed && effectiveGasPrice) {
|
|
4856
|
+
const feeWei = gasUsed * effectiveGasPrice;
|
|
4857
|
+
const { formatUnits } = await import("ethers");
|
|
4858
|
+
const feeInNative = formatUnits(feeWei, 18);
|
|
4859
|
+
actualFeeValue = feeInNative;
|
|
4860
|
+
console.log(`EVM transaction fee: ${feeInNative} (native token)`);
|
|
4861
|
+
}
|
|
4862
|
+
} catch (error) {
|
|
4863
|
+
console.warn("Failed to calculate actual fee:", error);
|
|
4864
|
+
}
|
|
4339
4865
|
return {
|
|
4340
|
-
completed: true
|
|
4866
|
+
completed: true,
|
|
4867
|
+
actualFeeValue
|
|
4868
|
+
// Symbol will be determined by the caller based on chain info
|
|
4341
4869
|
};
|
|
4342
4870
|
} catch (error) {
|
|
4343
4871
|
if (error && typeof error === "object" && "code" in error && error.code === "TRANSACTION_REPLACED") {
|
|
@@ -4350,8 +4878,26 @@ class EvmChainStrategy {
|
|
|
4350
4878
|
console.log(
|
|
4351
4879
|
`Replacement transaction succeeded in block ${replacementReceipt.blockNumber}`
|
|
4352
4880
|
);
|
|
4881
|
+
let actualFeeValue;
|
|
4882
|
+
try {
|
|
4883
|
+
const receipt = error.receipt;
|
|
4884
|
+
const gasUsed = receipt.gasUsed;
|
|
4885
|
+
const effectiveGasPrice = receipt.gasPrice || receipt.effectiveGasPrice;
|
|
4886
|
+
if (gasUsed && effectiveGasPrice) {
|
|
4887
|
+
const feeWei = gasUsed * effectiveGasPrice;
|
|
4888
|
+
const { formatUnits } = await import("ethers");
|
|
4889
|
+
const feeInNative = formatUnits(feeWei, 18);
|
|
4890
|
+
actualFeeValue = feeInNative;
|
|
4891
|
+
console.log(
|
|
4892
|
+
`Replacement transaction fee: ${feeInNative} (native token)`
|
|
4893
|
+
);
|
|
4894
|
+
}
|
|
4895
|
+
} catch (feeError) {
|
|
4896
|
+
console.warn("Failed to calculate replacement transaction fee:", feeError);
|
|
4897
|
+
}
|
|
4353
4898
|
return {
|
|
4354
|
-
completed: true
|
|
4899
|
+
completed: true,
|
|
4900
|
+
actualFeeValue
|
|
4355
4901
|
};
|
|
4356
4902
|
} else {
|
|
4357
4903
|
const chainError2 = new TransactionRevertedError("evm", txHash);
|
|
@@ -4554,6 +5100,9 @@ class TonChainStrategy {
|
|
|
4554
5100
|
getConnectLabel(t) {
|
|
4555
5101
|
return t("wallets.connectTonWallet");
|
|
4556
5102
|
}
|
|
5103
|
+
getClient() {
|
|
5104
|
+
return getTonClient(this.config.tonClient, this.config.tonApiKey);
|
|
5105
|
+
}
|
|
4557
5106
|
async getBalances(address, tokens) {
|
|
4558
5107
|
return await getTonBalances(
|
|
4559
5108
|
address,
|
|
@@ -4566,39 +5115,6 @@ class TonChainStrategy {
|
|
|
4566
5115
|
if (!address) return false;
|
|
4567
5116
|
return true;
|
|
4568
5117
|
}
|
|
4569
|
-
async estimateGasRequirement(params) {
|
|
4570
|
-
const {
|
|
4571
|
-
selectedToken,
|
|
4572
|
-
nativeTokenSymbol,
|
|
4573
|
-
amount,
|
|
4574
|
-
balances,
|
|
4575
|
-
nativeDecimals = 9,
|
|
4576
|
-
reserveFallback
|
|
4577
|
-
} = params;
|
|
4578
|
-
const nativeSym = nativeTokenSymbol.toUpperCase();
|
|
4579
|
-
const isNativeSelected = nativeSym === (selectedToken?.symbol ?? "").toUpperCase();
|
|
4580
|
-
const nativeBalance = Number(balances[nativeSym]?.balance ?? 0);
|
|
4581
|
-
const { formatUnits } = await import("ethers");
|
|
4582
|
-
const estimatedGas = Number(
|
|
4583
|
-
formatUnits(TON_CONFIG.estimatedNetworkFee, nativeDecimals)
|
|
4584
|
-
);
|
|
4585
|
-
const requiredNative = estimatedGas > 0 ? estimatedGas : reserveFallback;
|
|
4586
|
-
const amountNum = amount ?? 0;
|
|
4587
|
-
let hasEnoughGas = true;
|
|
4588
|
-
if (isNativeSelected) {
|
|
4589
|
-
hasEnoughGas = nativeBalance - amountNum >= requiredNative;
|
|
4590
|
-
} else {
|
|
4591
|
-
hasEnoughGas = nativeBalance >= requiredNative;
|
|
4592
|
-
}
|
|
4593
|
-
return {
|
|
4594
|
-
estimatedGas,
|
|
4595
|
-
nativeBalance,
|
|
4596
|
-
requiredNative,
|
|
4597
|
-
hasEnoughGas,
|
|
4598
|
-
nativeSym,
|
|
4599
|
-
isNativeSelected
|
|
4600
|
-
};
|
|
4601
|
-
}
|
|
4602
5118
|
validateSteps(steps) {
|
|
4603
5119
|
if (!steps || steps.length === 0) {
|
|
4604
5120
|
throw new InvalidStepsError("ton", "No transaction steps provided");
|
|
@@ -4675,11 +5191,11 @@ class TonChainStrategy {
|
|
|
4675
5191
|
}
|
|
4676
5192
|
async waitForCompletion(txHash) {
|
|
4677
5193
|
try {
|
|
4678
|
-
const
|
|
5194
|
+
const result = await this.checkTonTransaction(
|
|
4679
5195
|
txHash,
|
|
4680
5196
|
TON_CONFIG.timeout
|
|
4681
5197
|
);
|
|
4682
|
-
if (!confirmed) {
|
|
5198
|
+
if (!result.confirmed) {
|
|
4683
5199
|
const error = new TransactionFailedError(
|
|
4684
5200
|
"ton",
|
|
4685
5201
|
"Transaction not confirmed on-chain",
|
|
@@ -4691,7 +5207,9 @@ class TonChainStrategy {
|
|
|
4691
5207
|
};
|
|
4692
5208
|
}
|
|
4693
5209
|
return {
|
|
4694
|
-
completed: true
|
|
5210
|
+
completed: true,
|
|
5211
|
+
actualFeeValue: result.fee,
|
|
5212
|
+
actualFeeSymbol: "TON"
|
|
4695
5213
|
};
|
|
4696
5214
|
} catch (error) {
|
|
4697
5215
|
const chainError = toChainStrategyError(
|
|
@@ -4734,7 +5252,7 @@ class TonChainStrategy {
|
|
|
4734
5252
|
"Expected external-in message, got:",
|
|
4735
5253
|
inMessage.info.type
|
|
4736
5254
|
);
|
|
4737
|
-
return false;
|
|
5255
|
+
return { confirmed: false };
|
|
4738
5256
|
}
|
|
4739
5257
|
accountAddress = inMessage.info.dest;
|
|
4740
5258
|
targetMessageHash = this.getNormalizedExtMessageHash(inMessage);
|
|
@@ -4742,7 +5260,7 @@ class TonChainStrategy {
|
|
|
4742
5260
|
targetMessageHash = Buffer.from(hashOrBoc, "hex");
|
|
4743
5261
|
if (!this.config.tonAddress) {
|
|
4744
5262
|
console.debug("No wallet address available for hex hash lookup");
|
|
4745
|
-
return false;
|
|
5263
|
+
return { confirmed: false };
|
|
4746
5264
|
}
|
|
4747
5265
|
accountAddress = core.Address.parse(this.config.tonAddress);
|
|
4748
5266
|
}
|
|
@@ -4769,7 +5287,13 @@ class TonChainStrategy {
|
|
|
4769
5287
|
);
|
|
4770
5288
|
if (txInMessageHash.equals(targetMessageHash)) {
|
|
4771
5289
|
console.debug("Transaction found by in-message hash");
|
|
4772
|
-
|
|
5290
|
+
const totalFees = tx.totalFees;
|
|
5291
|
+
const feeInTon = Number(totalFees) / 1e9;
|
|
5292
|
+
console.log(`TON transaction fee: ${feeInTon} TON`);
|
|
5293
|
+
return {
|
|
5294
|
+
confirmed: true,
|
|
5295
|
+
fee: feeInTon.toString()
|
|
5296
|
+
};
|
|
4773
5297
|
}
|
|
4774
5298
|
}
|
|
4775
5299
|
}
|
|
@@ -4784,10 +5308,10 @@ class TonChainStrategy {
|
|
|
4784
5308
|
hash = void 0;
|
|
4785
5309
|
}
|
|
4786
5310
|
}
|
|
4787
|
-
return false;
|
|
5311
|
+
return { confirmed: false };
|
|
4788
5312
|
} catch (error) {
|
|
4789
5313
|
console.debug("Error parsing BOC or checking transaction:", error);
|
|
4790
|
-
return false;
|
|
5314
|
+
return { confirmed: false };
|
|
4791
5315
|
}
|
|
4792
5316
|
}
|
|
4793
5317
|
}
|
|
@@ -4841,7 +5365,7 @@ class TronChainStrategy {
|
|
|
4841
5365
|
return t("wallets.connectTronWallet");
|
|
4842
5366
|
}
|
|
4843
5367
|
async getBalances(address, tokens) {
|
|
4844
|
-
const tronWeb = this.
|
|
5368
|
+
const tronWeb = this.getClient();
|
|
4845
5369
|
if (!tronWeb) return {};
|
|
4846
5370
|
return await getTronBalances(tronWeb, address, tokens);
|
|
4847
5371
|
}
|
|
@@ -4849,35 +5373,6 @@ class TronChainStrategy {
|
|
|
4849
5373
|
if (!address) return false;
|
|
4850
5374
|
return /^T[1-9A-HJ-NP-Za-km-z]{33}$/.test(address);
|
|
4851
5375
|
}
|
|
4852
|
-
async estimateGasRequirement(params) {
|
|
4853
|
-
const {
|
|
4854
|
-
selectedToken,
|
|
4855
|
-
nativeTokenSymbol,
|
|
4856
|
-
amount,
|
|
4857
|
-
balances,
|
|
4858
|
-
reserveFallback
|
|
4859
|
-
} = params;
|
|
4860
|
-
const nativeSym = nativeTokenSymbol.toUpperCase();
|
|
4861
|
-
const isNativeSelected = nativeSym === (selectedToken?.symbol ?? "").toUpperCase();
|
|
4862
|
-
const nativeBalance = Number(balances[nativeSym]?.balance ?? 0);
|
|
4863
|
-
const estimatedGas = null;
|
|
4864
|
-
const requiredNative = reserveFallback;
|
|
4865
|
-
const amountNum = amount ?? 0;
|
|
4866
|
-
let hasEnoughGas = true;
|
|
4867
|
-
if (isNativeSelected) {
|
|
4868
|
-
hasEnoughGas = nativeBalance - amountNum >= requiredNative;
|
|
4869
|
-
} else {
|
|
4870
|
-
hasEnoughGas = nativeBalance >= requiredNative;
|
|
4871
|
-
}
|
|
4872
|
-
return {
|
|
4873
|
-
estimatedGas,
|
|
4874
|
-
nativeBalance,
|
|
4875
|
-
requiredNative,
|
|
4876
|
-
hasEnoughGas,
|
|
4877
|
-
nativeSym,
|
|
4878
|
-
isNativeSelected
|
|
4879
|
-
};
|
|
4880
|
-
}
|
|
4881
5376
|
validateSteps(steps) {
|
|
4882
5377
|
console.log("validateSteps");
|
|
4883
5378
|
if (!steps?.length) {
|
|
@@ -4894,8 +5389,7 @@ class TronChainStrategy {
|
|
|
4894
5389
|
}
|
|
4895
5390
|
}
|
|
4896
5391
|
async executeSteps(steps, _context, onFirstHash) {
|
|
4897
|
-
|
|
4898
|
-
const tronWeb = this.getTronWeb();
|
|
5392
|
+
const tronWeb = this.getClient();
|
|
4899
5393
|
if (!tronWeb) {
|
|
4900
5394
|
throw new ProviderNotAvailableError("tron");
|
|
4901
5395
|
}
|
|
@@ -4989,7 +5483,7 @@ class TronChainStrategy {
|
|
|
4989
5483
|
}
|
|
4990
5484
|
async waitForCompletion(txHash) {
|
|
4991
5485
|
try {
|
|
4992
|
-
const tronWeb = this.
|
|
5486
|
+
const tronWeb = this.getClient();
|
|
4993
5487
|
if (!tronWeb) {
|
|
4994
5488
|
throw new ProviderNotAvailableError("tron");
|
|
4995
5489
|
}
|
|
@@ -4998,6 +5492,7 @@ class TronChainStrategy {
|
|
|
4998
5492
|
);
|
|
4999
5493
|
const deadline = Date.now() + TRON_CONFIG.timeout;
|
|
5000
5494
|
let txBlockNumber = null;
|
|
5495
|
+
let actualFeeTrx = null;
|
|
5001
5496
|
while (Date.now() < deadline && !txBlockNumber) {
|
|
5002
5497
|
try {
|
|
5003
5498
|
const info = await tronWeb.trx.getTransactionInfo(txHash);
|
|
@@ -5032,7 +5527,9 @@ class TronChainStrategy {
|
|
|
5032
5527
|
};
|
|
5033
5528
|
}
|
|
5034
5529
|
txBlockNumber = info.blockNumber;
|
|
5035
|
-
|
|
5530
|
+
const feeSun = info.fee || 0;
|
|
5531
|
+
actualFeeTrx = feeSun / 1e6;
|
|
5532
|
+
console.log(`TRON transaction found in block ${txBlockNumber}, fee: ${actualFeeTrx} TRX`);
|
|
5036
5533
|
}
|
|
5037
5534
|
} catch (e) {
|
|
5038
5535
|
console.debug("TRON getTransactionInfo error:", e);
|
|
@@ -5060,7 +5557,9 @@ class TronChainStrategy {
|
|
|
5060
5557
|
`TRON transaction confirmed in block ${txBlockNumber} with ${confirmations} confirmations`
|
|
5061
5558
|
);
|
|
5062
5559
|
return {
|
|
5063
|
-
completed: true
|
|
5560
|
+
completed: true,
|
|
5561
|
+
actualFeeValue: actualFeeTrx?.toString(),
|
|
5562
|
+
actualFeeSymbol: "TRX"
|
|
5064
5563
|
};
|
|
5065
5564
|
}
|
|
5066
5565
|
console.log(
|
|
@@ -5089,7 +5588,7 @@ class TronChainStrategy {
|
|
|
5089
5588
|
};
|
|
5090
5589
|
}
|
|
5091
5590
|
}
|
|
5092
|
-
|
|
5591
|
+
getClient() {
|
|
5093
5592
|
return typeof window !== "undefined" ? window.tronWeb : void 0;
|
|
5094
5593
|
}
|
|
5095
5594
|
/**
|
|
@@ -5406,7 +5905,7 @@ class TronChainStrategy {
|
|
|
5406
5905
|
*/
|
|
5407
5906
|
async checkSolidified(txHash) {
|
|
5408
5907
|
try {
|
|
5409
|
-
const tronWeb = this.
|
|
5908
|
+
const tronWeb = this.getClient();
|
|
5410
5909
|
if (!tronWeb) {
|
|
5411
5910
|
return false;
|
|
5412
5911
|
}
|
|
@@ -5552,7 +6051,7 @@ const SettingsModal = ({ isOpen, onClose }) => {
|
|
|
5552
6051
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center", children: [
|
|
5553
6052
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
5554
6053
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-sm font-medium leading-4", children: t("settings.gasOnDestination") }),
|
|
5555
|
-
/* @__PURE__ */ jsxRuntime.jsx(Tip, { text: t("settings.gasOnDestination"), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
6054
|
+
/* @__PURE__ */ jsxRuntime.jsx(Tip, { text: t("settings.gasOnDestination"), children: /* @__PURE__ */ jsxRuntime.jsx(InfoIcon, { className: "w-4 h-4 text-muted-foreground" }) })
|
|
5556
6055
|
] }),
|
|
5557
6056
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-foreground text-sm font-medium leading-4", children: formatUsd(gasUsdValue) })
|
|
5558
6057
|
] }),
|
|
@@ -5590,7 +6089,7 @@ const SettingsModal = ({ isOpen, onClose }) => {
|
|
|
5590
6089
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center", children: [
|
|
5591
6090
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
5592
6091
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-sm font-medium leading-4", children: t("settings.slippageTolerance") }),
|
|
5593
|
-
/* @__PURE__ */ jsxRuntime.jsx(Tip, { text: t("settings.slippageTolerance"), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
6092
|
+
/* @__PURE__ */ jsxRuntime.jsx(Tip, { text: t("settings.slippageTolerance"), children: /* @__PURE__ */ jsxRuntime.jsx(InfoIcon, { className: "w-4 h-4 text-muted-foreground" }) })
|
|
5594
6093
|
] }),
|
|
5595
6094
|
slippageBps >= 500 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-destructive text-xs font-medium", children: t("settings.highSlippageWarning", {
|
|
5596
6095
|
defaultValue: "High slippage warning"
|
|
@@ -5620,7 +6119,7 @@ const SettingsModal = ({ isOpen, onClose }) => {
|
|
|
5620
6119
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-5", children: [
|
|
5621
6120
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-between items-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
5622
6121
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-sm font-medium leading-4", children: t("settings.routePriority") }),
|
|
5623
|
-
/* @__PURE__ */ jsxRuntime.jsx(Tip, { text: t("settings.routePriority"), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
6122
|
+
/* @__PURE__ */ jsxRuntime.jsx(Tip, { text: t("settings.routePriority"), children: /* @__PURE__ */ jsxRuntime.jsx(InfoIcon, { className: "w-4 h-4 text-muted-foreground" }) })
|
|
5624
6123
|
] }) }),
|
|
5625
6124
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-end gap-2", children: routePresets.map((r) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
5626
6125
|
badge.Badge,
|
|
@@ -5653,7 +6152,7 @@ const TokenRow = ({
|
|
|
5653
6152
|
button.Button,
|
|
5654
6153
|
{
|
|
5655
6154
|
onClick: onPick,
|
|
5656
|
-
className: `w-full rounded-
|
|
6155
|
+
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" : ""}`,
|
|
5657
6156
|
children: [
|
|
5658
6157
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
5659
6158
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -5674,7 +6173,7 @@ const TokenRow = ({
|
|
|
5674
6173
|
/* @__PURE__ */ jsxRuntime.jsx(skeleton.Skeleton, { className: "h-3 w-12 rounded-md" })
|
|
5675
6174
|
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
5676
6175
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-semibold text-foreground text-lg leading-4 truncate", children: hasAnyWallet ? formatBalance(balance) : "—" }),
|
|
5677
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs leading-3 text-muted-foreground", children: hasAnyWallet && balance > 0 && usdValue > 0 ? formatUsd(usdValue) : "—" })
|
|
6176
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs leading-3 text-muted-foreground/50", children: hasAnyWallet && balance > 0 && usdValue > 0 ? formatUsd(usdValue) : "—" })
|
|
5678
6177
|
] }) })
|
|
5679
6178
|
]
|
|
5680
6179
|
}
|
|
@@ -5817,7 +6316,7 @@ const TokenSelectModal = ({
|
|
|
5817
6316
|
[groupedTokens.willChangeSrcChain]
|
|
5818
6317
|
);
|
|
5819
6318
|
const hasNoResults = tokensToRender.length === 0 && willChangeSrcTokens.length === 0;
|
|
5820
|
-
return /* @__PURE__ */ jsxRuntime.jsx(dialog.Dialog, { open: isOpen, onOpenChange: (open) => !open && handleClose(), children: /* @__PURE__ */ jsxRuntime.jsxs(dialog.DialogContent, { className: "max-h-[90dvh] h-[90dvh] overflow-hidden flex flex-col", children: [
|
|
6319
|
+
return /* @__PURE__ */ jsxRuntime.jsx(dialog.Dialog, { open: isOpen, onOpenChange: (open) => !open && handleClose(), children: /* @__PURE__ */ jsxRuntime.jsxs(dialog.DialogContent, { className: "md:max-h-[90dvh] md:h-[90dvh] overflow-hidden flex flex-col", children: [
|
|
5821
6320
|
/* @__PURE__ */ jsxRuntime.jsx(dialog.DialogHeader, { className: "text-left", children: /* @__PURE__ */ jsxRuntime.jsx(dialog.DialogTitle, { children: t("bridge.selectToken") }) }),
|
|
5822
6321
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5823
6322
|
SearchInput,
|
|
@@ -5831,24 +6330,26 @@ const TokenSelectModal = ({
|
|
|
5831
6330
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5832
6331
|
button.Button,
|
|
5833
6332
|
{
|
|
5834
|
-
variant: tab === "my" ? "default" : "
|
|
6333
|
+
variant: tab === "my" ? "default" : "ghost",
|
|
5835
6334
|
onClick: () => setTab("my"),
|
|
5836
6335
|
size: "sm",
|
|
6336
|
+
className: utils.cn(tab !== "my" && "bg-muted hover:bg-accent"),
|
|
5837
6337
|
children: t("bridge.myTokens")
|
|
5838
6338
|
}
|
|
5839
6339
|
),
|
|
5840
6340
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5841
6341
|
button.Button,
|
|
5842
6342
|
{
|
|
5843
|
-
variant: tab === "all" ? "default" : "
|
|
6343
|
+
variant: tab === "all" ? "default" : "ghost",
|
|
5844
6344
|
onClick: () => setTab("all"),
|
|
5845
6345
|
size: "sm",
|
|
6346
|
+
className: utils.cn(tab !== "all" && "bg-muted hover:bg-accent"),
|
|
5846
6347
|
children: t("bridge.allTokens")
|
|
5847
6348
|
}
|
|
5848
6349
|
)
|
|
5849
6350
|
] }),
|
|
5850
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-auto -
|
|
5851
|
-
effectiveTab === "my" && myTokens.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "leading-4 text-base font-semibold text-muted-foreground uppercase
|
|
6351
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-auto px-1", children: hasNoResults ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground px-5 py-4", children: t("bridge.noResults") }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
6352
|
+
effectiveTab === "my" && myTokens.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "leading-4 text-base font-semibold text-muted-foreground uppercase py-2", children: t("bridge.noBalancesFound") }),
|
|
5852
6353
|
tokensToRender.map(({ token, willChangeSrc }) => {
|
|
5853
6354
|
const bal = getBalance(token.symbol);
|
|
5854
6355
|
const usd = getTokenUsdValue(token.symbol, token.price?.usd);
|
|
@@ -5899,6 +6400,7 @@ const TokenSelectModal = ({
|
|
|
5899
6400
|
function useBridgeRefresh() {
|
|
5900
6401
|
const qc = reactQuery.useQueryClient();
|
|
5901
6402
|
const { srcAddress, dstAddress } = useAddresses();
|
|
6403
|
+
const { fromChain, toChain } = useChainsStore();
|
|
5902
6404
|
const { setLoading: setQLoading, triggerRefetch } = useBridgeQuoteStore();
|
|
5903
6405
|
const { hasAnyWallet } = useConnectedWalletsStore();
|
|
5904
6406
|
const [spinning, setSpinning] = react.useState(false);
|
|
@@ -5911,14 +6413,30 @@ function useBridgeRefresh() {
|
|
|
5911
6413
|
qc.invalidateQueries({ queryKey: ["chains"] })
|
|
5912
6414
|
];
|
|
5913
6415
|
if (hasAnyWallet()) {
|
|
5914
|
-
if (srcAddress)
|
|
6416
|
+
if (srcAddress) {
|
|
5915
6417
|
queries.push(
|
|
5916
6418
|
qc.invalidateQueries({ queryKey: ["srcTokens", srcAddress] })
|
|
5917
6419
|
);
|
|
5918
|
-
|
|
6420
|
+
if (fromChain?.chainKey) {
|
|
6421
|
+
queries.push(
|
|
6422
|
+
qc.invalidateQueries({
|
|
6423
|
+
queryKey: ["balances", fromChain.chainKey, srcAddress]
|
|
6424
|
+
})
|
|
6425
|
+
);
|
|
6426
|
+
}
|
|
6427
|
+
}
|
|
6428
|
+
if (dstAddress) {
|
|
5919
6429
|
queries.push(
|
|
5920
6430
|
qc.invalidateQueries({ queryKey: ["dstTokens", dstAddress] })
|
|
5921
6431
|
);
|
|
6432
|
+
if (toChain?.chainKey) {
|
|
6433
|
+
queries.push(
|
|
6434
|
+
qc.invalidateQueries({
|
|
6435
|
+
queryKey: ["balances", toChain.chainKey, dstAddress]
|
|
6436
|
+
})
|
|
6437
|
+
);
|
|
6438
|
+
}
|
|
6439
|
+
}
|
|
5922
6440
|
}
|
|
5923
6441
|
await Promise.all(queries);
|
|
5924
6442
|
triggerRefetch();
|
|
@@ -5929,6 +6447,8 @@ function useBridgeRefresh() {
|
|
|
5929
6447
|
qc,
|
|
5930
6448
|
srcAddress,
|
|
5931
6449
|
dstAddress,
|
|
6450
|
+
fromChain?.chainKey,
|
|
6451
|
+
toChain?.chainKey,
|
|
5932
6452
|
hasAnyWallet,
|
|
5933
6453
|
setQLoading,
|
|
5934
6454
|
triggerRefetch
|
|
@@ -5948,7 +6468,7 @@ const RefreshButton = () => {
|
|
|
5948
6468
|
onClick: handleRefresh,
|
|
5949
6469
|
disabled: spinning,
|
|
5950
6470
|
variant: "secondary",
|
|
5951
|
-
className: "
|
|
6471
|
+
className: "h-9 w-11",
|
|
5952
6472
|
size: "sm",
|
|
5953
6473
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5954
6474
|
ReloadIcon,
|
|
@@ -5976,7 +6496,7 @@ const SelectTokenButton = ({
|
|
|
5976
6496
|
onClick,
|
|
5977
6497
|
size: "sm",
|
|
5978
6498
|
variant: "secondary",
|
|
5979
|
-
className: "shrink-0 gap-2
|
|
6499
|
+
className: "shrink-0 gap-2 h-9 !pl-2",
|
|
5980
6500
|
type: "button",
|
|
5981
6501
|
"aria-label": label,
|
|
5982
6502
|
children: [
|
|
@@ -6024,7 +6544,7 @@ const Toolbar = () => {
|
|
|
6024
6544
|
{
|
|
6025
6545
|
onClick: onOpenSettings,
|
|
6026
6546
|
size: "sm",
|
|
6027
|
-
className: "
|
|
6547
|
+
className: "h-9 w-11",
|
|
6028
6548
|
variant: "secondary",
|
|
6029
6549
|
children: /* @__PURE__ */ jsxRuntime.jsx(BoltIcon, { stroke: "currentColor" })
|
|
6030
6550
|
}
|
|
@@ -6304,11 +6824,16 @@ exports.DEFAULT_SLIPPAGE_BPS = DEFAULT_SLIPPAGE_BPS;
|
|
|
6304
6824
|
exports.EvaaBridge = EvaaBridge;
|
|
6305
6825
|
exports.RoutePriority = RoutePriority;
|
|
6306
6826
|
exports.RouteType = RouteType;
|
|
6827
|
+
exports.addEvmNetworkFee = addEvmNetworkFee;
|
|
6307
6828
|
exports.addTonNetworkFee = addTonNetworkFee;
|
|
6829
|
+
exports.addTronNetworkFee = addTronNetworkFee;
|
|
6308
6830
|
exports.addrForApi = addrForApi;
|
|
6309
6831
|
exports.buildAssetMatrix = buildAssetMatrix;
|
|
6310
6832
|
exports.calculateMinReceived = calculateMinReceived;
|
|
6311
6833
|
exports.computeFeesUsdFromArray = computeFeesUsdFromArray;
|
|
6834
|
+
exports.estimateEvmNetworkFee = estimateEvmNetworkFee;
|
|
6835
|
+
exports.estimateTonNetworkFee = estimateTonNetworkFee;
|
|
6836
|
+
exports.estimateTronNetworkFee = estimateTronNetworkFee;
|
|
6312
6837
|
exports.findNativeMeta = findNativeMeta;
|
|
6313
6838
|
exports.formatAddress = formatAddress;
|
|
6314
6839
|
exports.formatBalance = formatBalance;
|