@rash2x/bridge-widget 0.1.3 → 0.1.5
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/dist/evaa-bridge.cjs +703 -405
- package/dist/evaa-bridge.cjs.map +1 -1
- package/dist/evaa-bridge.mjs +708 -410
- package/dist/evaa-bridge.mjs.map +1 -1
- package/dist/index.d.ts +7 -3
- package/package.json +7 -11
package/dist/evaa-bridge.mjs
CHANGED
|
@@ -13,19 +13,20 @@ import { twMerge } from "tailwind-merge";
|
|
|
13
13
|
import { createPortal } from "react-dom";
|
|
14
14
|
import { AnimatePresence, motion } from "framer-motion";
|
|
15
15
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
16
|
-
import { useAccount, useDisconnect, useWalletClient } from "wagmi";
|
|
16
|
+
import { useAccount, useConnect, useDisconnect, useWalletClient, usePublicClient } from "wagmi";
|
|
17
17
|
import { useWallet } from "@tronweb3/tronwallet-adapter-react-hooks";
|
|
18
18
|
import { useTonAddress, useTonConnectUI } from "@tonconnect/ui-react";
|
|
19
19
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
20
|
-
import { Address, Cell } from "@ton/core";
|
|
20
|
+
import { Address, beginCell as beginCell$1, storeMessage, loadMessage, Cell } from "@ton/core";
|
|
21
21
|
import { VariableSizeList } from "react-window";
|
|
22
22
|
import * as SwitchPrimitive from "@radix-ui/react-switch";
|
|
23
23
|
import { X, ChevronDownIcon } from "lucide-react";
|
|
24
24
|
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
|
25
|
-
import { ConnectKitButton } from "connectkit";
|
|
26
25
|
import { t } from "i18next";
|
|
27
26
|
import { toast, Toaster } from "sonner";
|
|
28
|
-
import {
|
|
27
|
+
import { BrowserProvider, Contract, parseUnits } from "ethers";
|
|
28
|
+
import { isAddress, formatUnits } from "viem";
|
|
29
|
+
import { TonClient, Address as Address$1, beginCell } from "@ton/ton";
|
|
29
30
|
import { TronLinkAdapterName } from "@tronweb3/tronwallet-adapters";
|
|
30
31
|
const norm = (s) => (s ?? "").toUpperCase().replace(/₮/g, "T").replace(/[^A-Z0-9]/g, "");
|
|
31
32
|
const POPULAR_ORDER = [
|
|
@@ -509,7 +510,7 @@ function cn(...inputs) {
|
|
|
509
510
|
return twMerge(clsx(inputs));
|
|
510
511
|
}
|
|
511
512
|
const buttonVariants = cva(
|
|
512
|
-
"inline-flex items-center rounded-full justify-center gap-2 whitespace-nowrap text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
513
|
+
"inline-flex items-center rounded-full text-lg justify-center gap-2 whitespace-nowrap text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
513
514
|
{
|
|
514
515
|
variants: {
|
|
515
516
|
variant: {
|
|
@@ -782,19 +783,6 @@ const Tip = (props) => {
|
|
|
782
783
|
/* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { children: text }) })
|
|
783
784
|
] });
|
|
784
785
|
};
|
|
785
|
-
function toLD(human, decimals) {
|
|
786
|
-
const [i = "0", f = ""] = human.replace(",", ".").split(".");
|
|
787
|
-
const frac = (f + "0".repeat(decimals)).slice(0, decimals);
|
|
788
|
-
return BigInt(i + frac).toString();
|
|
789
|
-
}
|
|
790
|
-
function fromLD(ld, decimals) {
|
|
791
|
-
const bi = BigInt(ld || "0");
|
|
792
|
-
if (decimals === 0) return Number(bi);
|
|
793
|
-
const base = BigInt(10) ** BigInt(decimals);
|
|
794
|
-
const int = bi / base;
|
|
795
|
-
const frac = Number(bi % base) / Number(base);
|
|
796
|
-
return Number(int) + frac;
|
|
797
|
-
}
|
|
798
786
|
async function getChains() {
|
|
799
787
|
const res = await fetch("https://stargate.finance/api/v1/chains", {
|
|
800
788
|
credentials: "same-origin"
|
|
@@ -842,49 +830,16 @@ async function getDestTokens(srcChainKey, srcTokenAddr) {
|
|
|
842
830
|
});
|
|
843
831
|
return unique;
|
|
844
832
|
}
|
|
845
|
-
|
|
846
|
-
function normalizeTickerSymbol(s) {
|
|
833
|
+
function normalizeTickerSymbol$1(s) {
|
|
847
834
|
return s.toUpperCase().replace(/₮/g, "T").replace(/[^A-Z0-9]/g, "");
|
|
848
835
|
}
|
|
849
|
-
async function getSwapBalances(accountFriendly) {
|
|
850
|
-
if (!isTonFriendly(accountFriendly)) throw new Error("Invalid TON address");
|
|
851
|
-
const accRes = await fetch(
|
|
852
|
-
`https://tonapi.io/v2/accounts/${accountFriendly}`
|
|
853
|
-
);
|
|
854
|
-
if (!accRes.ok) throw new Error(`TON account fetch failed: ${accRes.status}`);
|
|
855
|
-
const acc = await accRes.json();
|
|
856
|
-
const ton = fromLD(String(acc?.balance ?? "0"), 9);
|
|
857
|
-
const result = {
|
|
858
|
-
TON: { balance: ton, address: "ton-native" }
|
|
859
|
-
};
|
|
860
|
-
const jetsRes = await fetch(
|
|
861
|
-
`https://tonapi.io/v2/accounts/${accountFriendly}/jettons?limit=200`
|
|
862
|
-
);
|
|
863
|
-
if (!jetsRes.ok) return result;
|
|
864
|
-
const jets = await jetsRes.json();
|
|
865
|
-
const items = jets?.balances ?? jets?.jettons ?? jets?.items ?? [];
|
|
866
|
-
for (const it of items) {
|
|
867
|
-
const rawSym = (it?.jetton?.symbol ?? it?.symbol ?? "").toString();
|
|
868
|
-
if (!rawSym) continue;
|
|
869
|
-
const symUpper = rawSym.toUpperCase();
|
|
870
|
-
const symNorm = normalizeTickerSymbol(symUpper);
|
|
871
|
-
const master = (it?.jetton?.address ?? it?.address ?? "").toString();
|
|
872
|
-
const decimals = Number(it?.jetton?.decimals ?? it?.decimals ?? 9) || 9;
|
|
873
|
-
const raw = (it?.balance ?? "0").toString();
|
|
874
|
-
const human = fromLD(raw, decimals);
|
|
875
|
-
const entry = { balance: human, address: master };
|
|
876
|
-
result[symUpper] = entry;
|
|
877
|
-
if (symNorm !== symUpper) result[symNorm] = entry;
|
|
878
|
-
}
|
|
879
|
-
return result;
|
|
880
|
-
}
|
|
881
836
|
const BASE_URL$1 = "https://icons-ckg.pages.dev/stargate-light/tokens";
|
|
882
837
|
const TokenSymbol = ({
|
|
883
838
|
symbol,
|
|
884
839
|
className = "w-4 h-4",
|
|
885
840
|
alt
|
|
886
841
|
}) => {
|
|
887
|
-
const normalizedSymbol = normalizeTickerSymbol(symbol).toLowerCase();
|
|
842
|
+
const normalizedSymbol = normalizeTickerSymbol$1(symbol).toLowerCase();
|
|
888
843
|
const src = `${BASE_URL$1}/${normalizedSymbol}.svg`;
|
|
889
844
|
return /* @__PURE__ */ jsx("img", { src, alt: alt ?? symbol, className });
|
|
890
845
|
};
|
|
@@ -1298,6 +1253,19 @@ function useChainStrategies() {
|
|
|
1298
1253
|
}
|
|
1299
1254
|
return context;
|
|
1300
1255
|
}
|
|
1256
|
+
function toLD(human, decimals) {
|
|
1257
|
+
const [i = "0", f = ""] = human.replace(",", ".").split(".");
|
|
1258
|
+
const frac = (f + "0".repeat(decimals)).slice(0, decimals);
|
|
1259
|
+
return BigInt(i + frac).toString();
|
|
1260
|
+
}
|
|
1261
|
+
function fromLD(ld, decimals) {
|
|
1262
|
+
const bi = BigInt(ld || "0");
|
|
1263
|
+
if (decimals === 0) return Number(bi);
|
|
1264
|
+
const base = BigInt(10) ** BigInt(decimals);
|
|
1265
|
+
const int = bi / base;
|
|
1266
|
+
const frac = Number(bi % base) / Number(base);
|
|
1267
|
+
return Number(int) + frac;
|
|
1268
|
+
}
|
|
1301
1269
|
function resolveTokenOnChainFromMatrix$2(assetMatrix, assetSymbol, chainKey) {
|
|
1302
1270
|
if (!assetMatrix || !assetSymbol || !chainKey) return void 0;
|
|
1303
1271
|
const byChain = assetMatrix[assetSymbol.toUpperCase()];
|
|
@@ -1479,18 +1447,19 @@ function useBalances(chainKey, address) {
|
|
|
1479
1447
|
const data = query.data;
|
|
1480
1448
|
if (data) {
|
|
1481
1449
|
for (const [sum, v] of Object.entries(data)) {
|
|
1482
|
-
map.set(normalizeTickerSymbol(sum), Number(v.balance ?? 0));
|
|
1450
|
+
map.set(normalizeTickerSymbol$1(sum), Number(v.balance ?? 0));
|
|
1483
1451
|
}
|
|
1484
1452
|
}
|
|
1485
1453
|
return map;
|
|
1486
1454
|
}, [query.data]);
|
|
1487
1455
|
const getBalance = useCallback(
|
|
1488
|
-
(symbol) => balanceBySymbol.get(normalizeTickerSymbol(symbol)) ?? 0,
|
|
1456
|
+
(symbol) => balanceBySymbol.get(normalizeTickerSymbol$1(symbol)) ?? 0,
|
|
1489
1457
|
[balanceBySymbol]
|
|
1490
1458
|
);
|
|
1491
1459
|
const isLoading = query.isLoading || query.isFetching;
|
|
1460
|
+
const balances = useMemo(() => query.data || {}, [query.data]);
|
|
1492
1461
|
return {
|
|
1493
|
-
balances
|
|
1462
|
+
balances,
|
|
1494
1463
|
getBalance,
|
|
1495
1464
|
isLoading,
|
|
1496
1465
|
query
|
|
@@ -1944,17 +1913,17 @@ const RefreshButton = () => {
|
|
|
1944
1913
|
return /* @__PURE__ */ jsx(
|
|
1945
1914
|
Button,
|
|
1946
1915
|
{
|
|
1947
|
-
className: `cursor-pointer py-1.5 h-8.5 hover:scale-110 shadow-none px-8.5 hover:bg-secondary bg-secondary !rounded-40 ${spinning ? "opacity-70" : ""}`,
|
|
1948
1916
|
onClick: handleRefresh,
|
|
1949
1917
|
disabled: spinning,
|
|
1918
|
+
variant: "secondary",
|
|
1919
|
+
size: "sm",
|
|
1950
1920
|
children: /* @__PURE__ */ jsx(
|
|
1951
1921
|
ReloadIcon,
|
|
1952
1922
|
{
|
|
1953
1923
|
style: {
|
|
1954
1924
|
transform: `rotate(${turns * 180}deg)`,
|
|
1955
1925
|
transition: "transform 300ms linear"
|
|
1956
|
-
}
|
|
1957
|
-
className: "size-4 text-foreground m-1 will-change-transform"
|
|
1926
|
+
}
|
|
1958
1927
|
}
|
|
1959
1928
|
)
|
|
1960
1929
|
}
|
|
@@ -1962,7 +1931,6 @@ const RefreshButton = () => {
|
|
|
1962
1931
|
};
|
|
1963
1932
|
const SelectTokenButton = ({
|
|
1964
1933
|
onClick,
|
|
1965
|
-
className,
|
|
1966
1934
|
token
|
|
1967
1935
|
}) => {
|
|
1968
1936
|
const { t: t2 } = useTranslation();
|
|
@@ -1973,7 +1941,8 @@ const SelectTokenButton = ({
|
|
|
1973
1941
|
Button,
|
|
1974
1942
|
{
|
|
1975
1943
|
onClick,
|
|
1976
|
-
|
|
1944
|
+
size: "sm",
|
|
1945
|
+
variant: "secondary",
|
|
1977
1946
|
type: "button",
|
|
1978
1947
|
"aria-label": label,
|
|
1979
1948
|
children: [
|
|
@@ -1993,6 +1962,75 @@ const SelectTokenButton = ({
|
|
|
1993
1962
|
}
|
|
1994
1963
|
);
|
|
1995
1964
|
};
|
|
1965
|
+
function Card({ className, ...props }) {
|
|
1966
|
+
return /* @__PURE__ */ jsx(
|
|
1967
|
+
"div",
|
|
1968
|
+
{
|
|
1969
|
+
"data-slot": "card",
|
|
1970
|
+
className: cn(
|
|
1971
|
+
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
|
1972
|
+
className
|
|
1973
|
+
),
|
|
1974
|
+
...props
|
|
1975
|
+
}
|
|
1976
|
+
);
|
|
1977
|
+
}
|
|
1978
|
+
function CardHeader({ className, ...props }) {
|
|
1979
|
+
return /* @__PURE__ */ jsx(
|
|
1980
|
+
"div",
|
|
1981
|
+
{
|
|
1982
|
+
"data-slot": "card-header",
|
|
1983
|
+
className: cn(
|
|
1984
|
+
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
|
1985
|
+
className
|
|
1986
|
+
),
|
|
1987
|
+
...props
|
|
1988
|
+
}
|
|
1989
|
+
);
|
|
1990
|
+
}
|
|
1991
|
+
function CardTitle({ className, ...props }) {
|
|
1992
|
+
return /* @__PURE__ */ jsx(
|
|
1993
|
+
"div",
|
|
1994
|
+
{
|
|
1995
|
+
"data-slot": "card-title",
|
|
1996
|
+
className: cn("leading-none font-semibold", className),
|
|
1997
|
+
...props
|
|
1998
|
+
}
|
|
1999
|
+
);
|
|
2000
|
+
}
|
|
2001
|
+
function CardAction({ className, ...props }) {
|
|
2002
|
+
return /* @__PURE__ */ jsx(
|
|
2003
|
+
"div",
|
|
2004
|
+
{
|
|
2005
|
+
"data-slot": "card-action",
|
|
2006
|
+
className: cn(
|
|
2007
|
+
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
2008
|
+
className
|
|
2009
|
+
),
|
|
2010
|
+
...props
|
|
2011
|
+
}
|
|
2012
|
+
);
|
|
2013
|
+
}
|
|
2014
|
+
function CardContent({ className, ...props }) {
|
|
2015
|
+
return /* @__PURE__ */ jsx(
|
|
2016
|
+
"div",
|
|
2017
|
+
{
|
|
2018
|
+
"data-slot": "card-content",
|
|
2019
|
+
className: cn("px-6", className),
|
|
2020
|
+
...props
|
|
2021
|
+
}
|
|
2022
|
+
);
|
|
2023
|
+
}
|
|
2024
|
+
function CardFooter({ className, ...props }) {
|
|
2025
|
+
return /* @__PURE__ */ jsx(
|
|
2026
|
+
"div",
|
|
2027
|
+
{
|
|
2028
|
+
"data-slot": "card-footer",
|
|
2029
|
+
className: cn("flex items-center px-6 [.border-t]:pt-6", className),
|
|
2030
|
+
...props
|
|
2031
|
+
}
|
|
2032
|
+
);
|
|
2033
|
+
}
|
|
1996
2034
|
const FormHeaderComponent = ({ modalContainer }) => {
|
|
1997
2035
|
const { t: t2 } = useTranslation();
|
|
1998
2036
|
const { isOpen, onClose, onOpen } = useModal();
|
|
@@ -2009,29 +2047,14 @@ const FormHeaderComponent = ({ modalContainer }) => {
|
|
|
2009
2047
|
const sum = selectedAssetSymbol.toUpperCase();
|
|
2010
2048
|
return assets.find((a) => a.symbol.toUpperCase() === sum) ?? assets[0];
|
|
2011
2049
|
}, [assets, selectedAssetSymbol]);
|
|
2012
|
-
return /* @__PURE__ */ jsxs(
|
|
2013
|
-
/* @__PURE__ */ jsxs(
|
|
2014
|
-
/* @__PURE__ */
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
/* @__PURE__ */
|
|
2019
|
-
|
|
2020
|
-
/* @__PURE__ */ jsx(
|
|
2021
|
-
Button,
|
|
2022
|
-
{
|
|
2023
|
-
className: "cursor-pointer py-1.5 h-8.5 hover:scale-110 shadow-none px-8.5 hover:bg-secondary bg-secondary !rounded-40",
|
|
2024
|
-
onClick: onOpenSettings,
|
|
2025
|
-
children: /* @__PURE__ */ jsx(
|
|
2026
|
-
BoltIcon,
|
|
2027
|
-
{
|
|
2028
|
-
className: "size-4 text-foreground m-1",
|
|
2029
|
-
stroke: "currentColor"
|
|
2030
|
-
}
|
|
2031
|
-
)
|
|
2032
|
-
}
|
|
2033
|
-
)
|
|
2034
|
-
] })
|
|
2050
|
+
return /* @__PURE__ */ jsxs(CardHeader, { className: "gap-y-0", children: [
|
|
2051
|
+
/* @__PURE__ */ jsxs(CardTitle, { className: "flex items-center gap-2.5", children: [
|
|
2052
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-normal leading-3.5 text-muted-foreground", children: t2("bridge.selectToken") }),
|
|
2053
|
+
/* @__PURE__ */ jsx(SelectTokenButton, { token: current, onClick: onOpen })
|
|
2054
|
+
] }),
|
|
2055
|
+
/* @__PURE__ */ jsxs(CardAction, { className: "flex items-center gap-2.5", children: [
|
|
2056
|
+
/* @__PURE__ */ jsx(RefreshButton, {}),
|
|
2057
|
+
/* @__PURE__ */ jsx(Button, { onClick: onOpenSettings, size: "sm", variant: "secondary", children: /* @__PURE__ */ jsx(BoltIcon, { stroke: "currentColor" }) })
|
|
2035
2058
|
] }),
|
|
2036
2059
|
/* @__PURE__ */ jsx(
|
|
2037
2060
|
TokenSelectModal,
|
|
@@ -3721,7 +3744,9 @@ function useBridgeTransaction() {
|
|
|
3721
3744
|
function useGasEstimate(amountNum) {
|
|
3722
3745
|
const { fromChain } = useChainsStore();
|
|
3723
3746
|
const { selectedAssetSymbol, tokens } = useTokensStore();
|
|
3724
|
-
const
|
|
3747
|
+
const getSourceGasReserveHuman = useSettingsStore(
|
|
3748
|
+
(state) => state.getSourceGasReserveHuman
|
|
3749
|
+
);
|
|
3725
3750
|
const { srcAddress } = useAddresses();
|
|
3726
3751
|
const { balances, isLoading: balancesLoading } = useBalances(
|
|
3727
3752
|
fromChain?.chainKey,
|
|
@@ -3729,59 +3754,44 @@ function useGasEstimate(amountNum) {
|
|
|
3729
3754
|
);
|
|
3730
3755
|
const { chainRegistry } = useChainStrategies();
|
|
3731
3756
|
const balancesKnown = !balancesLoading;
|
|
3732
|
-
const
|
|
3733
|
-
null
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
}
|
|
3771
|
-
}
|
|
3772
|
-
estimateGas();
|
|
3773
|
-
return () => {
|
|
3774
|
-
cancelled = true;
|
|
3775
|
-
};
|
|
3776
|
-
}, [
|
|
3777
|
-
fromChain,
|
|
3778
|
-
selectedAssetSymbol,
|
|
3779
|
-
tokens,
|
|
3780
|
-
amountNum,
|
|
3781
|
-
balances,
|
|
3782
|
-
chainRegistry,
|
|
3783
|
-
getSourceGasReserveHuman
|
|
3784
|
-
]);
|
|
3757
|
+
const strategy = useMemo(() => {
|
|
3758
|
+
if (!fromChain) return null;
|
|
3759
|
+
return chainRegistry.getStrategy(fromChain.chainKey);
|
|
3760
|
+
}, [fromChain, chainRegistry]);
|
|
3761
|
+
const { data: gasRequirement } = useQuery({
|
|
3762
|
+
queryKey: [
|
|
3763
|
+
"gas-estimate",
|
|
3764
|
+
fromChain?.chainKey,
|
|
3765
|
+
selectedAssetSymbol,
|
|
3766
|
+
amountNum,
|
|
3767
|
+
balances
|
|
3768
|
+
],
|
|
3769
|
+
queryFn: async () => {
|
|
3770
|
+
if (!fromChain || !strategy) {
|
|
3771
|
+
return null;
|
|
3772
|
+
}
|
|
3773
|
+
const selectedToken = tokens?.find(
|
|
3774
|
+
(t2) => t2.symbol.toUpperCase() === selectedAssetSymbol?.toUpperCase()
|
|
3775
|
+
) || null;
|
|
3776
|
+
const nativeTokenSymbol = fromChain.nativeCurrency?.symbol ?? "";
|
|
3777
|
+
const nativeDecimals = fromChain.nativeCurrency?.decimals || 18;
|
|
3778
|
+
const reserveFallback = getSourceGasReserveHuman(fromChain.chainKey);
|
|
3779
|
+
const result = await strategy.estimateGasRequirement({
|
|
3780
|
+
selectedToken,
|
|
3781
|
+
nativeTokenSymbol,
|
|
3782
|
+
amount: amountNum,
|
|
3783
|
+
balances,
|
|
3784
|
+
nativeDecimals,
|
|
3785
|
+
reserveFallback
|
|
3786
|
+
});
|
|
3787
|
+
return result;
|
|
3788
|
+
},
|
|
3789
|
+
enabled: !!fromChain && !!strategy,
|
|
3790
|
+
staleTime: 3e4,
|
|
3791
|
+
gcTime: 5 * 6e4,
|
|
3792
|
+
refetchOnWindowFocus: false,
|
|
3793
|
+
retry: 1
|
|
3794
|
+
});
|
|
3785
3795
|
return {
|
|
3786
3796
|
nativeSym: gasRequirement?.nativeSym || "",
|
|
3787
3797
|
nativeBalance: gasRequirement?.nativeBalance || 0,
|
|
@@ -3930,7 +3940,7 @@ function useSilentValidations(amountString) {
|
|
|
3930
3940
|
]);
|
|
3931
3941
|
return validationResult;
|
|
3932
3942
|
}
|
|
3933
|
-
|
|
3943
|
+
const SubmitButton = () => {
|
|
3934
3944
|
const { t: t2 } = useTranslation();
|
|
3935
3945
|
const { chainRegistry } = useChainStrategies();
|
|
3936
3946
|
const { srcAddress, dstAddress } = useAddresses();
|
|
@@ -4006,7 +4016,7 @@ function useTransferAction() {
|
|
|
4006
4016
|
maximumAmountFormatted,
|
|
4007
4017
|
chainRegistry
|
|
4008
4018
|
]);
|
|
4009
|
-
const
|
|
4019
|
+
const handleClick = async () => {
|
|
4010
4020
|
if (isBusy) return;
|
|
4011
4021
|
if (missingSrc && srcChainKey) {
|
|
4012
4022
|
onOpen("src");
|
|
@@ -4026,24 +4036,7 @@ function useTransferAction() {
|
|
|
4026
4036
|
}
|
|
4027
4037
|
};
|
|
4028
4038
|
const disabled = isBusy || amountNum <= 0 || status === "loading" || isBalanceLoading || hasInsufficientBalance || hasAmountTooLarge || !gas.hasEnoughGas || noRoute || !isValidForTransfer;
|
|
4029
|
-
return {
|
|
4030
|
-
// состояния
|
|
4031
|
-
isBusy,
|
|
4032
|
-
canTransfer,
|
|
4033
|
-
missingSrc,
|
|
4034
|
-
missingDst,
|
|
4035
|
-
hasEnoughGas: gas.hasEnoughGas,
|
|
4036
|
-
noRoute,
|
|
4037
|
-
// представление
|
|
4038
|
-
label,
|
|
4039
|
-
disabled,
|
|
4040
|
-
// действие
|
|
4041
|
-
onClick
|
|
4042
|
-
};
|
|
4043
|
-
}
|
|
4044
|
-
const SubmitButton = () => {
|
|
4045
|
-
const { label, disabled, onClick } = useTransferAction();
|
|
4046
|
-
return /* @__PURE__ */ jsx(Button, { onClick, disabled, size: "lg", children: label });
|
|
4039
|
+
return /* @__PURE__ */ jsx(Button, { onClick: handleClick, disabled, className: "w-full", children: label });
|
|
4047
4040
|
};
|
|
4048
4041
|
function short(addr) {
|
|
4049
4042
|
return addr.slice(0, 4) + "…" + addr.slice(-4);
|
|
@@ -4053,6 +4046,7 @@ const WalletSelectModal = ({
|
|
|
4053
4046
|
}) => {
|
|
4054
4047
|
const { t: t2 } = useTranslation();
|
|
4055
4048
|
const { isOpen, onClose } = useWalletSelectModal();
|
|
4049
|
+
const { connect, connectors, isPending } = useConnect();
|
|
4056
4050
|
const { chainRegistry } = useChainStrategies();
|
|
4057
4051
|
const tonWallet = chainRegistry.getStrategyByType("ton");
|
|
4058
4052
|
const metaMaskWallet = chainRegistry.getStrategyByType("evm");
|
|
@@ -4085,7 +4079,13 @@ const WalletSelectModal = ({
|
|
|
4085
4079
|
onDisconnect: () => tronWallet.disconnect()
|
|
4086
4080
|
});
|
|
4087
4081
|
}
|
|
4088
|
-
const isWalletConnected = (walletId) =>
|
|
4082
|
+
const isWalletConnected = (walletId) => {
|
|
4083
|
+
const isEvmConnector = connectors.some((c) => c.id === walletId);
|
|
4084
|
+
if (isEvmConnector && metaMaskWallet?.isConnected()) {
|
|
4085
|
+
return true;
|
|
4086
|
+
}
|
|
4087
|
+
return connectedWallets.some((w) => w.id === walletId);
|
|
4088
|
+
};
|
|
4089
4089
|
const tonWallets = [
|
|
4090
4090
|
{
|
|
4091
4091
|
id: "ton",
|
|
@@ -4094,14 +4094,15 @@ const WalletSelectModal = ({
|
|
|
4094
4094
|
enabled: true
|
|
4095
4095
|
}
|
|
4096
4096
|
];
|
|
4097
|
-
const evmWallets =
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4097
|
+
const evmWallets = connectors.filter(
|
|
4098
|
+
(connector) => connector.id === "walletConnect" || connector.id === "metaMaskSDK"
|
|
4099
|
+
).map((connector) => ({
|
|
4100
|
+
id: connector.id,
|
|
4101
|
+
name: connector.name,
|
|
4102
|
+
icon: MetaMaskIcon,
|
|
4103
|
+
// You can add a WalletConnect icon here
|
|
4104
|
+
enabled: true
|
|
4105
|
+
}));
|
|
4105
4106
|
const tronWallets = [
|
|
4106
4107
|
{
|
|
4107
4108
|
id: "tronlink",
|
|
@@ -4131,9 +4132,6 @@ const WalletSelectModal = ({
|
|
|
4131
4132
|
case "ton":
|
|
4132
4133
|
await tonWallet?.connect();
|
|
4133
4134
|
break;
|
|
4134
|
-
case "metamask":
|
|
4135
|
-
await metaMaskWallet?.connect();
|
|
4136
|
-
break;
|
|
4137
4135
|
case "tronlink":
|
|
4138
4136
|
await tronWallet?.connect();
|
|
4139
4137
|
break;
|
|
@@ -4158,56 +4156,47 @@ const WalletSelectModal = ({
|
|
|
4158
4156
|
/* @__PURE__ */ jsx("div", { className: "px-5 py-2 leading-4 text-base font-semibold text-muted-foreground uppercase", children: t2("wallets.connected") }),
|
|
4159
4157
|
/* @__PURE__ */ jsx("div", { className: "", children: connectedWallets.map((wallet) => {
|
|
4160
4158
|
const IconComponent = wallet.icon;
|
|
4161
|
-
return /* @__PURE__ */ jsx("div", { className: "", children: /* @__PURE__ */ jsxs(
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
/* @__PURE__ */ jsx("div", { className: "
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
/* @__PURE__ */ jsx("div", { onClick: () => {
|
|
4159
|
+
return /* @__PURE__ */ jsx("div", { className: "", children: /* @__PURE__ */ jsxs(Button, { className: "w-full cursor-pointer bg-transparent flex shadow-none items-center justify-between gap-2.5 px-5 py-2.5 hover:bg-muted h-auto rounded-12 transition-[300]", children: [
|
|
4160
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-3 min-w-0", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
|
|
4161
|
+
/* @__PURE__ */ jsx("div", { className: "w-7.5 h-7.5 flex items-center justify-center", children: /* @__PURE__ */ jsx(IconComponent, { className: "size-7.5" }) }),
|
|
4162
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start min-w-0", children: [
|
|
4163
|
+
/* @__PURE__ */ jsx("p", { className: "font-extrabold text-foreground text-sm leading-4 truncate", children: short(wallet.address) }),
|
|
4164
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs leading-3 font-semibold text-muted-foreground", children: wallet.name })
|
|
4165
|
+
] })
|
|
4166
|
+
] }) }),
|
|
4167
|
+
/* @__PURE__ */ jsx(
|
|
4168
|
+
"div",
|
|
4169
|
+
{
|
|
4170
|
+
onClick: () => {
|
|
4174
4171
|
wallet.onDisconnect();
|
|
4175
4172
|
onClose();
|
|
4176
|
-
},
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4173
|
+
},
|
|
4174
|
+
className: "text-sm font-medium text-muted-foreground",
|
|
4175
|
+
children: /* @__PURE__ */ jsx(ExitIcon, { className: "text-[#808080] size-6" })
|
|
4176
|
+
}
|
|
4177
|
+
)
|
|
4178
|
+
] }) }, wallet.id);
|
|
4180
4179
|
}) })
|
|
4181
4180
|
] }),
|
|
4182
4181
|
categories.map((category) => /* @__PURE__ */ jsxs("div", { children: [
|
|
4183
4182
|
/* @__PURE__ */ jsx("div", { className: "px-5 py-2 leading-4 text-base font-semibold text-muted-foreground uppercase", children: category.title }),
|
|
4184
4183
|
/* @__PURE__ */ jsx("div", { className: "", children: category.wallets.map((wallet) => {
|
|
4185
4184
|
const IconComponent = wallet.icon;
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
return /* @__PURE__ */ jsx(
|
|
4189
|
-
Button,
|
|
4190
|
-
{
|
|
4191
|
-
type: "button",
|
|
4192
|
-
onClick: show,
|
|
4193
|
-
disabled: isConnecting,
|
|
4194
|
-
className: "w-full cursor-pointer bg-transparent flex shadow-none items-center justify-between gap-2.5 px-5 py-2.5 hover:bg-muted h-auto rounded-12 transition-[300] disabled:opacity-50 disabled:cursor-not-allowed",
|
|
4195
|
-
children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
|
|
4196
|
-
/* @__PURE__ */ jsx("div", { className: "w-7.5 h-7.5 flex items-center justify-center", children: /* @__PURE__ */ jsx(IconComponent, { className: "size-7.5" }) }),
|
|
4197
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start min-w-0", children: [
|
|
4198
|
-
/* @__PURE__ */ jsx("p", { className: "font-extrabold text-foreground text-sm leading-4 truncate", children: wallet.name }),
|
|
4199
|
-
wallet.comingSoon ? /* @__PURE__ */ jsx("div", { className: "text-xs leading-3 font-semibold text-muted-foreground", children: t2("wallets.comingSoon") }) : /* @__PURE__ */ jsx("div", { className: "text-xs leading-3 font-semibold text-muted-foreground", children: t2("wallets.connect") })
|
|
4200
|
-
] })
|
|
4201
|
-
] })
|
|
4202
|
-
}
|
|
4203
|
-
);
|
|
4204
|
-
} }) }, wallet.id);
|
|
4205
|
-
}
|
|
4185
|
+
const isEvmConnector = category.title === t2("wallets.evmWallets");
|
|
4186
|
+
const connector = isEvmConnector ? connectors.find((c) => c.id === wallet.id) : null;
|
|
4206
4187
|
return /* @__PURE__ */ jsx("div", { className: "", children: /* @__PURE__ */ jsx(
|
|
4207
4188
|
Button,
|
|
4208
4189
|
{
|
|
4209
|
-
|
|
4210
|
-
|
|
4190
|
+
type: "button",
|
|
4191
|
+
onClick: () => {
|
|
4192
|
+
if (connector) {
|
|
4193
|
+
connect({ connector });
|
|
4194
|
+
onClose();
|
|
4195
|
+
} else {
|
|
4196
|
+
handleWalletSelect(wallet.id);
|
|
4197
|
+
}
|
|
4198
|
+
},
|
|
4199
|
+
disabled: isEvmConnector ? isPending : !wallet.enabled,
|
|
4211
4200
|
className: "w-full cursor-pointer bg-transparent flex shadow-none items-center justify-between gap-2.5 px-5 py-2.5 hover:bg-muted h-auto rounded-12 transition-[300] disabled:opacity-50 disabled:cursor-not-allowed",
|
|
4212
4201
|
children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
|
|
4213
4202
|
/* @__PURE__ */ jsx("div", { className: "w-7.5 h-7.5 flex items-center justify-center", children: /* @__PURE__ */ jsx(IconComponent, { className: "size-7.5" }) }),
|
|
@@ -4223,39 +4212,6 @@ const WalletSelectModal = ({
|
|
|
4223
4212
|
] })
|
|
4224
4213
|
] }) });
|
|
4225
4214
|
};
|
|
4226
|
-
function Card({ className, ...props }) {
|
|
4227
|
-
return /* @__PURE__ */ jsx(
|
|
4228
|
-
"div",
|
|
4229
|
-
{
|
|
4230
|
-
"data-slot": "card",
|
|
4231
|
-
className: cn(
|
|
4232
|
-
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
|
4233
|
-
className
|
|
4234
|
-
),
|
|
4235
|
-
...props
|
|
4236
|
-
}
|
|
4237
|
-
);
|
|
4238
|
-
}
|
|
4239
|
-
function CardContent({ className, ...props }) {
|
|
4240
|
-
return /* @__PURE__ */ jsx(
|
|
4241
|
-
"div",
|
|
4242
|
-
{
|
|
4243
|
-
"data-slot": "card-content",
|
|
4244
|
-
className: cn("px-6", className),
|
|
4245
|
-
...props
|
|
4246
|
-
}
|
|
4247
|
-
);
|
|
4248
|
-
}
|
|
4249
|
-
function CardFooter({ className, ...props }) {
|
|
4250
|
-
return /* @__PURE__ */ jsx(
|
|
4251
|
-
"div",
|
|
4252
|
-
{
|
|
4253
|
-
"data-slot": "card-footer",
|
|
4254
|
-
className: cn("flex items-center px-6 [.border-t]:pt-6", className),
|
|
4255
|
-
...props
|
|
4256
|
-
}
|
|
4257
|
-
);
|
|
4258
|
-
}
|
|
4259
4215
|
const TransactionProgressVector = (props) => {
|
|
4260
4216
|
return /* @__PURE__ */ jsxs("svg", { width: "200", height: "200", viewBox: "0 0 200 200", fill: "none", xmlns: "http://www.w3.org/2000/svg", ...props, children: [
|
|
4261
4217
|
/* @__PURE__ */ jsx(
|
|
@@ -6319,6 +6275,49 @@ class ChainStrategyRegistry {
|
|
|
6319
6275
|
await strategy.disconnect();
|
|
6320
6276
|
}
|
|
6321
6277
|
}
|
|
6278
|
+
const EVM_CONFIG = {
|
|
6279
|
+
usdtAddress: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
|
6280
|
+
gasEstimates: {
|
|
6281
|
+
approve: 65000n,
|
|
6282
|
+
bridge: 300000n
|
|
6283
|
+
},
|
|
6284
|
+
gasBuffer: 1.2,
|
|
6285
|
+
// 20% buffer
|
|
6286
|
+
timeout: 3e5,
|
|
6287
|
+
// 5 minutes (increased for slower networks)
|
|
6288
|
+
requiredConfirmations: 3
|
|
6289
|
+
// Wait for 3 confirmations for reorg protection
|
|
6290
|
+
};
|
|
6291
|
+
const TON_CONFIG = {
|
|
6292
|
+
apiUrl: "https://toncenter.com/api/v2",
|
|
6293
|
+
timeout: 36e4,
|
|
6294
|
+
// 6 minutes
|
|
6295
|
+
validUntil: 600
|
|
6296
|
+
// 10 minutes
|
|
6297
|
+
};
|
|
6298
|
+
const TRON_CONFIG = {
|
|
6299
|
+
timeout: 12e4,
|
|
6300
|
+
// 2 minutes (for 19 confirmations)
|
|
6301
|
+
feeLimit: 1e8,
|
|
6302
|
+
// 100 TRX in sun
|
|
6303
|
+
requiredConfirmations: 19,
|
|
6304
|
+
// TRON standard: 19 blocks for confirmation
|
|
6305
|
+
pollingInterval: 3e3
|
|
6306
|
+
// 3 seconds between checks
|
|
6307
|
+
};
|
|
6308
|
+
let tonClientInstance = null;
|
|
6309
|
+
function getTonClient(customClient, apiKey) {
|
|
6310
|
+
if (customClient) {
|
|
6311
|
+
return customClient;
|
|
6312
|
+
}
|
|
6313
|
+
if (!tonClientInstance) {
|
|
6314
|
+
tonClientInstance = new TonClient({
|
|
6315
|
+
endpoint: `${TON_CONFIG.apiUrl}/jsonRPC`,
|
|
6316
|
+
apiKey
|
|
6317
|
+
});
|
|
6318
|
+
}
|
|
6319
|
+
return tonClientInstance;
|
|
6320
|
+
}
|
|
6322
6321
|
function isNativeAddress(addr) {
|
|
6323
6322
|
if (!addr) return false;
|
|
6324
6323
|
const a = addr.toLowerCase();
|
|
@@ -6344,44 +6343,72 @@ function formatUnitsFromBigIntStr(valueStr, decimals) {
|
|
|
6344
6343
|
const tail = s.slice(s.length - decimals).replace(/0+$/, "");
|
|
6345
6344
|
return Number(tail ? `${head}.${tail}` : head);
|
|
6346
6345
|
}
|
|
6347
|
-
|
|
6346
|
+
function isTonFriendlyAddress(address) {
|
|
6347
|
+
return !!address && /^[A-Za-z0-9_-]{48,}$/.test(address);
|
|
6348
|
+
}
|
|
6349
|
+
function normalizeTickerSymbol(symbol) {
|
|
6350
|
+
return symbol.toUpperCase().replace(/₮/g, "T").replace(/[^A-Z0-9]/g, "");
|
|
6351
|
+
}
|
|
6352
|
+
function parseTonAddress(address) {
|
|
6353
|
+
if (address.startsWith("0x")) {
|
|
6354
|
+
const hex = address.slice(2);
|
|
6355
|
+
return Address$1.parseRaw(`0:${hex}`);
|
|
6356
|
+
}
|
|
6357
|
+
if (address.includes(":")) {
|
|
6358
|
+
return Address$1.parseRaw(address);
|
|
6359
|
+
}
|
|
6360
|
+
return Address$1.parse(address);
|
|
6361
|
+
}
|
|
6362
|
+
async function getEvmBalances(publicClient, address, tokens) {
|
|
6348
6363
|
const balances = {};
|
|
6349
6364
|
try {
|
|
6350
|
-
|
|
6365
|
+
console.log("start getEvmBalances");
|
|
6366
|
+
console.log("publicClient:", publicClient);
|
|
6367
|
+
console.log("isAddress:", isAddress(address));
|
|
6368
|
+
console.log("tokens:", tokens);
|
|
6369
|
+
if (!address || !isAddress(address)) {
|
|
6351
6370
|
console.warn(`Invalid EVM address provided: ${address}`);
|
|
6352
6371
|
return balances;
|
|
6353
6372
|
}
|
|
6354
|
-
if (
|
|
6355
|
-
throw new Error("No
|
|
6373
|
+
if (!publicClient) {
|
|
6374
|
+
throw new Error("No public client provided");
|
|
6356
6375
|
}
|
|
6357
|
-
const provider = new ethers.BrowserProvider(window.ethereum);
|
|
6358
6376
|
for (const token of tokens) {
|
|
6359
6377
|
try {
|
|
6360
6378
|
let balance = 0;
|
|
6361
6379
|
const isNative = isNativeAddress(token.address);
|
|
6362
6380
|
if (isNative) {
|
|
6363
|
-
const ethBalance = await
|
|
6364
|
-
|
|
6381
|
+
const ethBalance = await publicClient.getBalance({
|
|
6382
|
+
address
|
|
6383
|
+
});
|
|
6384
|
+
balance = parseFloat(formatUnits(ethBalance, token.decimals));
|
|
6365
6385
|
} else {
|
|
6366
|
-
if (!
|
|
6386
|
+
if (!isAddress(token.address)) {
|
|
6367
6387
|
continue;
|
|
6368
6388
|
}
|
|
6369
|
-
const
|
|
6370
|
-
|
|
6389
|
+
const bytecode = await publicClient.getBytecode({
|
|
6390
|
+
address: token.address
|
|
6391
|
+
});
|
|
6392
|
+
if (!bytecode || bytecode === "0x") {
|
|
6371
6393
|
continue;
|
|
6372
6394
|
}
|
|
6373
|
-
const contract = new ethers.Contract(
|
|
6374
|
-
token.address,
|
|
6375
|
-
[
|
|
6376
|
-
"function balanceOf(address owner) view returns (uint256)",
|
|
6377
|
-
"function decimals() view returns (uint8)"
|
|
6378
|
-
],
|
|
6379
|
-
provider
|
|
6380
|
-
);
|
|
6381
6395
|
try {
|
|
6382
|
-
const tokenBalance = await
|
|
6396
|
+
const tokenBalance = await publicClient.readContract({
|
|
6397
|
+
address: token.address,
|
|
6398
|
+
abi: [
|
|
6399
|
+
{
|
|
6400
|
+
name: "balanceOf",
|
|
6401
|
+
type: "function",
|
|
6402
|
+
stateMutability: "view",
|
|
6403
|
+
inputs: [{ name: "owner", type: "address" }],
|
|
6404
|
+
outputs: [{ name: "balance", type: "uint256" }]
|
|
6405
|
+
}
|
|
6406
|
+
],
|
|
6407
|
+
functionName: "balanceOf",
|
|
6408
|
+
args: [address]
|
|
6409
|
+
});
|
|
6383
6410
|
balance = parseFloat(
|
|
6384
|
-
|
|
6411
|
+
formatUnits(tokenBalance, token.decimals)
|
|
6385
6412
|
);
|
|
6386
6413
|
} catch {
|
|
6387
6414
|
continue;
|
|
@@ -6404,6 +6431,68 @@ async function getEvmBalances(address, tokens) {
|
|
|
6404
6431
|
}
|
|
6405
6432
|
return balances;
|
|
6406
6433
|
}
|
|
6434
|
+
async function getTonBalances(address, tokens, customTonClient, tonApiKey) {
|
|
6435
|
+
const balances = {};
|
|
6436
|
+
try {
|
|
6437
|
+
if (!isTonFriendlyAddress(address)) {
|
|
6438
|
+
console.warn(`Invalid TON address provided: ${address}`);
|
|
6439
|
+
return balances;
|
|
6440
|
+
}
|
|
6441
|
+
const client = getTonClient(customTonClient, tonApiKey);
|
|
6442
|
+
const accountAddress = Address$1.parse(address);
|
|
6443
|
+
console.log(address);
|
|
6444
|
+
console.log(tokens);
|
|
6445
|
+
try {
|
|
6446
|
+
const balance = await client.getBalance(accountAddress);
|
|
6447
|
+
const tonBalance = Number(balance) / 1e9;
|
|
6448
|
+
console.log("tonBalance", tonBalance);
|
|
6449
|
+
if (tonBalance > 0) {
|
|
6450
|
+
balances.TON = { balance: tonBalance, address: "ton-native" };
|
|
6451
|
+
}
|
|
6452
|
+
} catch (error) {
|
|
6453
|
+
console.warn("Failed to get native TON balance:", error);
|
|
6454
|
+
}
|
|
6455
|
+
for (const token of tokens) {
|
|
6456
|
+
try {
|
|
6457
|
+
if (isNativeAddress(token.address) || token.symbol.toUpperCase() === "TON") {
|
|
6458
|
+
continue;
|
|
6459
|
+
}
|
|
6460
|
+
const jettonMasterAddress = parseTonAddress(token.address);
|
|
6461
|
+
const jettonWalletAddress = await client.runMethod(
|
|
6462
|
+
jettonMasterAddress,
|
|
6463
|
+
"get_wallet_address",
|
|
6464
|
+
[
|
|
6465
|
+
{
|
|
6466
|
+
type: "slice",
|
|
6467
|
+
cell: beginCell().storeAddress(accountAddress).endCell()
|
|
6468
|
+
}
|
|
6469
|
+
]
|
|
6470
|
+
);
|
|
6471
|
+
const jettonWalletAddr = jettonWalletAddress.stack.readAddress();
|
|
6472
|
+
const jettonData = await client.runMethod(
|
|
6473
|
+
jettonWalletAddr,
|
|
6474
|
+
"get_wallet_data"
|
|
6475
|
+
);
|
|
6476
|
+
const jettonBalance = jettonData.stack.readBigNumber();
|
|
6477
|
+
const humanBalance = Number(jettonBalance) / Math.pow(10, token.decimals);
|
|
6478
|
+
if (humanBalance > 0) {
|
|
6479
|
+
const symbolUpper = token.symbol.toUpperCase();
|
|
6480
|
+
const symbolNorm = normalizeTickerSymbol(symbolUpper);
|
|
6481
|
+
const entry = { balance: humanBalance, address: token.address };
|
|
6482
|
+
balances[symbolUpper] = entry;
|
|
6483
|
+
if (symbolNorm !== symbolUpper) {
|
|
6484
|
+
balances[symbolNorm] = entry;
|
|
6485
|
+
}
|
|
6486
|
+
}
|
|
6487
|
+
} catch (error) {
|
|
6488
|
+
console.debug(`Failed to get balance for ${token.symbol}:`, error);
|
|
6489
|
+
}
|
|
6490
|
+
}
|
|
6491
|
+
} catch (error) {
|
|
6492
|
+
console.error("Failed to get TON balances:", error);
|
|
6493
|
+
}
|
|
6494
|
+
return balances;
|
|
6495
|
+
}
|
|
6407
6496
|
async function getTronBalances(tronWeb, address, tokens) {
|
|
6408
6497
|
const balances = {};
|
|
6409
6498
|
try {
|
|
@@ -6447,30 +6536,6 @@ async function getTronBalances(tronWeb, address, tokens) {
|
|
|
6447
6536
|
}
|
|
6448
6537
|
return balances;
|
|
6449
6538
|
}
|
|
6450
|
-
const EVM_CONFIG = {
|
|
6451
|
-
usdtAddress: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
|
6452
|
-
gasEstimates: {
|
|
6453
|
-
approve: 65000n,
|
|
6454
|
-
bridge: 300000n
|
|
6455
|
-
},
|
|
6456
|
-
gasBuffer: 1.2,
|
|
6457
|
-
// 20% buffer
|
|
6458
|
-
timeout: 12e4
|
|
6459
|
-
// 2 minutes
|
|
6460
|
-
};
|
|
6461
|
-
const TON_CONFIG = {
|
|
6462
|
-
apiUrl: "https://toncenter.com/api/v2",
|
|
6463
|
-
timeout: 36e4,
|
|
6464
|
-
// 6 minutes
|
|
6465
|
-
validUntil: 600
|
|
6466
|
-
// 10 minutes
|
|
6467
|
-
};
|
|
6468
|
-
const TRON_CONFIG = {
|
|
6469
|
-
timeout: 6e4,
|
|
6470
|
-
// 1 minute
|
|
6471
|
-
feeLimit: 1e8
|
|
6472
|
-
// 100 TRX in sun
|
|
6473
|
-
};
|
|
6474
6539
|
const ERC20_ABI = [
|
|
6475
6540
|
"function approve(address spender, uint256 amount) returns (bool)",
|
|
6476
6541
|
"function allowance(address owner, address spender) view returns (uint256)",
|
|
@@ -6481,12 +6546,13 @@ class EvmChainStrategy {
|
|
|
6481
6546
|
constructor(config) {
|
|
6482
6547
|
__publicField(this, "config");
|
|
6483
6548
|
__publicField(this, "provider");
|
|
6549
|
+
__publicField(this, "publicClient");
|
|
6484
6550
|
this.config = config;
|
|
6551
|
+
this.publicClient = config.publicClient;
|
|
6485
6552
|
if (config.walletClient) {
|
|
6486
6553
|
this.provider = new BrowserProvider(config.walletClient.transport);
|
|
6487
6554
|
}
|
|
6488
6555
|
}
|
|
6489
|
-
// ========== Identity ==========
|
|
6490
6556
|
canHandle(chainKey) {
|
|
6491
6557
|
const key = chainKey.toLowerCase();
|
|
6492
6558
|
return key !== "ton" && key !== "tron";
|
|
@@ -6497,7 +6563,6 @@ class EvmChainStrategy {
|
|
|
6497
6563
|
getName() {
|
|
6498
6564
|
return "EVM Chain Strategy";
|
|
6499
6565
|
}
|
|
6500
|
-
// ========== Wallet Management ==========
|
|
6501
6566
|
async connect() {
|
|
6502
6567
|
}
|
|
6503
6568
|
async disconnect() {
|
|
@@ -6521,15 +6586,17 @@ class EvmChainStrategy {
|
|
|
6521
6586
|
getConnectLabel(t2) {
|
|
6522
6587
|
return t2("wallets.connectEvmWallet");
|
|
6523
6588
|
}
|
|
6524
|
-
// ========== Balance & Validation ==========
|
|
6525
6589
|
async getBalances(address, tokens) {
|
|
6526
|
-
|
|
6590
|
+
if (!this.publicClient) {
|
|
6591
|
+
console.warn("No publicClient available for balance query");
|
|
6592
|
+
return {};
|
|
6593
|
+
}
|
|
6594
|
+
return await getEvmBalances(this.publicClient, address, tokens);
|
|
6527
6595
|
}
|
|
6528
6596
|
isAddressValid(address) {
|
|
6529
6597
|
if (!address) return false;
|
|
6530
6598
|
return /^0x[0-9a-fA-F]{40}$/.test(address);
|
|
6531
6599
|
}
|
|
6532
|
-
// ========== Gas Estimation ==========
|
|
6533
6600
|
async estimateGasRequirement(params) {
|
|
6534
6601
|
const provider = this.provider;
|
|
6535
6602
|
const {
|
|
@@ -6553,8 +6620,8 @@ class EvmChainStrategy {
|
|
|
6553
6620
|
const totalGas = approveGas + bridgeGas;
|
|
6554
6621
|
const bufferMultiplier = BigInt(Math.floor(EVM_CONFIG.gasBuffer * 100));
|
|
6555
6622
|
const requiredWei = gasPrice * totalGas * bufferMultiplier / 100n;
|
|
6556
|
-
const { formatUnits } = await import("ethers");
|
|
6557
|
-
estimatedGas = Number(
|
|
6623
|
+
const { formatUnits: formatUnits2 } = await import("ethers");
|
|
6624
|
+
estimatedGas = Number(formatUnits2(requiredWei, nativeDecimals));
|
|
6558
6625
|
} catch {
|
|
6559
6626
|
estimatedGas = null;
|
|
6560
6627
|
}
|
|
@@ -6576,7 +6643,6 @@ class EvmChainStrategy {
|
|
|
6576
6643
|
isNativeSelected
|
|
6577
6644
|
};
|
|
6578
6645
|
}
|
|
6579
|
-
// ========== Transaction Execution ==========
|
|
6580
6646
|
validateSteps(steps) {
|
|
6581
6647
|
if (!steps || steps.length === 0) {
|
|
6582
6648
|
throw new InvalidStepsError("evm", "No transaction steps provided");
|
|
@@ -6638,17 +6704,14 @@ class EvmChainStrategy {
|
|
|
6638
6704
|
if (!provider) {
|
|
6639
6705
|
throw new ProviderNotAvailableError("evm");
|
|
6640
6706
|
}
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
}
|
|
6650
|
-
await new Promise((r) => setTimeout(r, 2500));
|
|
6651
|
-
}
|
|
6707
|
+
console.log(
|
|
6708
|
+
`Waiting for ${EVM_CONFIG.requiredConfirmations} confirmations for tx: ${txHash}`
|
|
6709
|
+
);
|
|
6710
|
+
const receipt = await provider.waitForTransaction(
|
|
6711
|
+
txHash,
|
|
6712
|
+
EVM_CONFIG.requiredConfirmations,
|
|
6713
|
+
EVM_CONFIG.timeout
|
|
6714
|
+
);
|
|
6652
6715
|
if (!receipt) {
|
|
6653
6716
|
const error = new TransactionTimeoutError("evm", txHash);
|
|
6654
6717
|
return {
|
|
@@ -6663,11 +6726,35 @@ class EvmChainStrategy {
|
|
|
6663
6726
|
error: error.message
|
|
6664
6727
|
};
|
|
6665
6728
|
}
|
|
6666
|
-
console.log(
|
|
6729
|
+
console.log(
|
|
6730
|
+
`EVM transaction confirmed in block ${receipt.blockNumber} with ${EVM_CONFIG.requiredConfirmations} confirmations`
|
|
6731
|
+
);
|
|
6667
6732
|
return {
|
|
6668
6733
|
completed: true
|
|
6669
6734
|
};
|
|
6670
6735
|
} catch (error) {
|
|
6736
|
+
if (error && typeof error === "object" && "code" in error && error.code === "TRANSACTION_REPLACED") {
|
|
6737
|
+
console.log(
|
|
6738
|
+
`Transaction was replaced: ${"reason" in error ? String(error.reason) : "unknown"}`
|
|
6739
|
+
);
|
|
6740
|
+
if ("receipt" in error && error.receipt) {
|
|
6741
|
+
const replacementReceipt = error.receipt;
|
|
6742
|
+
if (replacementReceipt.status === 1) {
|
|
6743
|
+
console.log(
|
|
6744
|
+
`Replacement transaction succeeded in block ${replacementReceipt.blockNumber}`
|
|
6745
|
+
);
|
|
6746
|
+
return {
|
|
6747
|
+
completed: true
|
|
6748
|
+
};
|
|
6749
|
+
} else {
|
|
6750
|
+
const chainError2 = new TransactionRevertedError("evm", txHash);
|
|
6751
|
+
return {
|
|
6752
|
+
completed: false,
|
|
6753
|
+
error: chainError2.message
|
|
6754
|
+
};
|
|
6755
|
+
}
|
|
6756
|
+
}
|
|
6757
|
+
}
|
|
6671
6758
|
const chainError = toChainStrategyError(
|
|
6672
6759
|
error,
|
|
6673
6760
|
"evm",
|
|
@@ -6679,7 +6766,6 @@ class EvmChainStrategy {
|
|
|
6679
6766
|
};
|
|
6680
6767
|
}
|
|
6681
6768
|
}
|
|
6682
|
-
// ========== Private Helper Methods ==========
|
|
6683
6769
|
async executeTransaction(step) {
|
|
6684
6770
|
const provider = this.provider;
|
|
6685
6771
|
const signer = await provider?.getSigner();
|
|
@@ -6782,13 +6868,52 @@ class EvmChainStrategy {
|
|
|
6782
6868
|
return false;
|
|
6783
6869
|
}
|
|
6784
6870
|
}
|
|
6871
|
+
/**
|
|
6872
|
+
* Check if a block has reached finality status (optional, for critical transactions)
|
|
6873
|
+
* This is more stringent than confirmations and protects against long-range reorgs
|
|
6874
|
+
*
|
|
6875
|
+
* Usage: Call this method after waitForCompletion if you need additional security
|
|
6876
|
+
* for high-value transactions. The method checks if the block has been marked as
|
|
6877
|
+
* "finalized" by the Ethereum consensus layer (2/3 of validators).
|
|
6878
|
+
*
|
|
6879
|
+
* @param blockNumber - The block number to check for finality
|
|
6880
|
+
* @returns true if the block is finalized, false otherwise
|
|
6881
|
+
*/
|
|
6882
|
+
async checkFinality(blockNumber) {
|
|
6883
|
+
try {
|
|
6884
|
+
const provider = this.provider;
|
|
6885
|
+
if (!provider) {
|
|
6886
|
+
return false;
|
|
6887
|
+
}
|
|
6888
|
+
const finalizedBlock = await provider.getBlock("finalized");
|
|
6889
|
+
if (!finalizedBlock) {
|
|
6890
|
+
console.debug(
|
|
6891
|
+
"Finalized block not available (pre-merge or unsupported)"
|
|
6892
|
+
);
|
|
6893
|
+
return false;
|
|
6894
|
+
}
|
|
6895
|
+
const isFinalized = blockNumber <= finalizedBlock.number;
|
|
6896
|
+
if (isFinalized) {
|
|
6897
|
+
console.log(
|
|
6898
|
+
`Block ${blockNumber} has reached finality (finalized block: ${finalizedBlock.number})`
|
|
6899
|
+
);
|
|
6900
|
+
} else {
|
|
6901
|
+
console.debug(
|
|
6902
|
+
`Block ${blockNumber} not yet finalized (finalized block: ${finalizedBlock.number})`
|
|
6903
|
+
);
|
|
6904
|
+
}
|
|
6905
|
+
return isFinalized;
|
|
6906
|
+
} catch (error) {
|
|
6907
|
+
console.debug("Error checking finality:", error);
|
|
6908
|
+
return false;
|
|
6909
|
+
}
|
|
6910
|
+
}
|
|
6785
6911
|
}
|
|
6786
6912
|
class TonChainStrategy {
|
|
6787
6913
|
constructor(config) {
|
|
6788
6914
|
__publicField(this, "config");
|
|
6789
6915
|
this.config = config;
|
|
6790
6916
|
}
|
|
6791
|
-
// ========== Identity ==========
|
|
6792
6917
|
canHandle(chainKey) {
|
|
6793
6918
|
return chainKey.toLowerCase() === "ton";
|
|
6794
6919
|
}
|
|
@@ -6798,7 +6923,6 @@ class TonChainStrategy {
|
|
|
6798
6923
|
getName() {
|
|
6799
6924
|
return "TON Chain Strategy";
|
|
6800
6925
|
}
|
|
6801
|
-
// ========== Wallet Management ==========
|
|
6802
6926
|
async connect() {
|
|
6803
6927
|
await this.config.tonConnectUI?.openModal();
|
|
6804
6928
|
}
|
|
@@ -6823,17 +6947,26 @@ class TonChainStrategy {
|
|
|
6823
6947
|
getConnectLabel(t2) {
|
|
6824
6948
|
return t2("wallets.connectTonWallet");
|
|
6825
6949
|
}
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6950
|
+
async getBalances(address, tokens) {
|
|
6951
|
+
return await getTonBalances(
|
|
6952
|
+
address,
|
|
6953
|
+
tokens,
|
|
6954
|
+
this.config.tonClient,
|
|
6955
|
+
this.config.tonApiKey
|
|
6956
|
+
);
|
|
6829
6957
|
}
|
|
6830
6958
|
isAddressValid(address) {
|
|
6831
6959
|
if (!address) return false;
|
|
6832
6960
|
return true;
|
|
6833
6961
|
}
|
|
6834
|
-
// ========== Gas Estimation ==========
|
|
6835
6962
|
async estimateGasRequirement(params) {
|
|
6836
|
-
const {
|
|
6963
|
+
const {
|
|
6964
|
+
selectedToken,
|
|
6965
|
+
nativeTokenSymbol,
|
|
6966
|
+
amount,
|
|
6967
|
+
balances,
|
|
6968
|
+
reserveFallback
|
|
6969
|
+
} = params;
|
|
6837
6970
|
const nativeSym = nativeTokenSymbol.toUpperCase();
|
|
6838
6971
|
const isNativeSelected = nativeSym === (selectedToken?.symbol ?? "").toUpperCase();
|
|
6839
6972
|
const nativeBalance = Number(balances[nativeSym]?.balance ?? 0);
|
|
@@ -6855,7 +6988,6 @@ class TonChainStrategy {
|
|
|
6855
6988
|
isNativeSelected
|
|
6856
6989
|
};
|
|
6857
6990
|
}
|
|
6858
|
-
// ========== Transaction Execution ==========
|
|
6859
6991
|
validateSteps(steps) {
|
|
6860
6992
|
if (!steps || steps.length === 0) {
|
|
6861
6993
|
throw new InvalidStepsError("ton", "No transaction steps provided");
|
|
@@ -6908,19 +7040,17 @@ class TonChainStrategy {
|
|
|
6908
7040
|
const result = await this.config.tonConnectUI.sendTransaction(
|
|
6909
7041
|
transaction
|
|
6910
7042
|
);
|
|
6911
|
-
const hash = this.getTxHash(result.boc);
|
|
6912
7043
|
return {
|
|
6913
7044
|
chainKey: "ton",
|
|
6914
|
-
hash
|
|
7045
|
+
hash: result.boc
|
|
6915
7046
|
};
|
|
6916
7047
|
} catch (error) {
|
|
6917
7048
|
throw toChainStrategyError(error, "ton", "transaction");
|
|
6918
7049
|
}
|
|
6919
7050
|
}
|
|
6920
|
-
async waitForCompletion(txHash
|
|
7051
|
+
async waitForCompletion(txHash) {
|
|
6921
7052
|
try {
|
|
6922
7053
|
const confirmed = await this.checkTonTransaction(
|
|
6923
|
-
context.srcAddress,
|
|
6924
7054
|
txHash,
|
|
6925
7055
|
TON_CONFIG.timeout
|
|
6926
7056
|
);
|
|
@@ -6950,39 +7080,78 @@ class TonChainStrategy {
|
|
|
6950
7080
|
};
|
|
6951
7081
|
}
|
|
6952
7082
|
}
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
7083
|
+
getNormalizedExtMessageHash(message) {
|
|
7084
|
+
if (message.info.type !== "external-in") {
|
|
7085
|
+
throw new Error(`Expected external-in message, got ${message.info.type}`);
|
|
7086
|
+
}
|
|
7087
|
+
const normalizedInfo = {
|
|
7088
|
+
...message.info,
|
|
7089
|
+
src: void 0,
|
|
7090
|
+
importFee: 0n
|
|
7091
|
+
};
|
|
7092
|
+
const normalizedMessage = {
|
|
7093
|
+
...message,
|
|
7094
|
+
info: normalizedInfo,
|
|
7095
|
+
init: null
|
|
7096
|
+
};
|
|
7097
|
+
return beginCell$1().store(storeMessage(normalizedMessage, { forceRef: true })).endCell().hash();
|
|
6958
7098
|
}
|
|
6959
|
-
async checkTonTransaction(
|
|
7099
|
+
async checkTonTransaction(bocBase64, timeoutMs = 36e4) {
|
|
6960
7100
|
const deadline = Date.now() + timeoutMs;
|
|
6961
|
-
const
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
7101
|
+
const client = getTonClient(this.config.tonClient, this.config.tonApiKey);
|
|
7102
|
+
try {
|
|
7103
|
+
const inMessage = loadMessage(Cell.fromBase64(bocBase64).beginParse());
|
|
7104
|
+
if (inMessage.info.type !== "external-in") {
|
|
7105
|
+
console.debug(
|
|
7106
|
+
"Expected external-in message, got:",
|
|
7107
|
+
inMessage.info.type
|
|
6966
7108
|
);
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
const
|
|
6976
|
-
|
|
6977
|
-
|
|
7109
|
+
return false;
|
|
7110
|
+
}
|
|
7111
|
+
const accountAddress = inMessage.info.dest;
|
|
7112
|
+
const targetMessageHash = this.getNormalizedExtMessageHash(inMessage);
|
|
7113
|
+
let lt = void 0;
|
|
7114
|
+
let hash = void 0;
|
|
7115
|
+
while (Date.now() < deadline) {
|
|
7116
|
+
try {
|
|
7117
|
+
const transactions = await client.getTransactions(accountAddress, {
|
|
7118
|
+
lt,
|
|
7119
|
+
hash,
|
|
7120
|
+
limit: 10,
|
|
7121
|
+
archival: true
|
|
7122
|
+
});
|
|
7123
|
+
if (transactions.length === 0) {
|
|
7124
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
7125
|
+
lt = void 0;
|
|
7126
|
+
hash = void 0;
|
|
7127
|
+
continue;
|
|
7128
|
+
}
|
|
7129
|
+
for (const tx of transactions) {
|
|
7130
|
+
if (tx.inMessage?.info.type === "external-in") {
|
|
7131
|
+
const txInMessageHash = this.getNormalizedExtMessageHash(
|
|
7132
|
+
tx.inMessage
|
|
7133
|
+
);
|
|
7134
|
+
if (txInMessageHash.equals(targetMessageHash)) {
|
|
7135
|
+
console.debug("Transaction found by in-message hash");
|
|
7136
|
+
return true;
|
|
7137
|
+
}
|
|
7138
|
+
}
|
|
6978
7139
|
}
|
|
7140
|
+
const lastTx = transactions[transactions.length - 1];
|
|
7141
|
+
lt = lastTx.lt.toString();
|
|
7142
|
+
hash = lastTx.hash().toString("base64");
|
|
7143
|
+
} catch (error) {
|
|
7144
|
+
console.debug("Error fetching transactions:", error);
|
|
7145
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
7146
|
+
lt = void 0;
|
|
7147
|
+
hash = void 0;
|
|
6979
7148
|
}
|
|
6980
|
-
} catch (e) {
|
|
6981
|
-
console.debug("TonCenter polling error:", e);
|
|
6982
7149
|
}
|
|
6983
|
-
|
|
7150
|
+
return false;
|
|
7151
|
+
} catch (error) {
|
|
7152
|
+
console.debug("Error parsing BOC or checking transaction:", error);
|
|
7153
|
+
return false;
|
|
6984
7154
|
}
|
|
6985
|
-
return false;
|
|
6986
7155
|
}
|
|
6987
7156
|
}
|
|
6988
7157
|
class TronChainStrategy {
|
|
@@ -7043,7 +7212,13 @@ class TronChainStrategy {
|
|
|
7043
7212
|
}
|
|
7044
7213
|
// ========== Gas Estimation ==========
|
|
7045
7214
|
async estimateGasRequirement(params) {
|
|
7046
|
-
const {
|
|
7215
|
+
const {
|
|
7216
|
+
selectedToken,
|
|
7217
|
+
nativeTokenSymbol,
|
|
7218
|
+
amount,
|
|
7219
|
+
balances,
|
|
7220
|
+
reserveFallback
|
|
7221
|
+
} = params;
|
|
7047
7222
|
const nativeSym = nativeTokenSymbol.toUpperCase();
|
|
7048
7223
|
const isNativeSelected = nativeSym === (selectedToken?.symbol ?? "").toUpperCase();
|
|
7049
7224
|
const nativeBalance = Number(balances[nativeSym]?.balance ?? 0);
|
|
@@ -7065,7 +7240,6 @@ class TronChainStrategy {
|
|
|
7065
7240
|
isNativeSelected
|
|
7066
7241
|
};
|
|
7067
7242
|
}
|
|
7068
|
-
// ========== Transaction Execution ==========
|
|
7069
7243
|
validateSteps(steps) {
|
|
7070
7244
|
console.log("validateSteps");
|
|
7071
7245
|
if (!steps?.length) {
|
|
@@ -7100,7 +7274,10 @@ class TronChainStrategy {
|
|
|
7100
7274
|
if (String(step.chainKey).toLowerCase() !== "tron") continue;
|
|
7101
7275
|
const tx = step.transaction;
|
|
7102
7276
|
if (!tx) {
|
|
7103
|
-
throw new InvalidTransactionDataError(
|
|
7277
|
+
throw new InvalidTransactionDataError(
|
|
7278
|
+
"tron",
|
|
7279
|
+
"Missing transaction data"
|
|
7280
|
+
);
|
|
7104
7281
|
}
|
|
7105
7282
|
const hexData = typeof tx?.data === "string" ? tx.data : "";
|
|
7106
7283
|
const parsed = this.parseTronStep(step, tx, hexData);
|
|
@@ -7151,13 +7328,19 @@ class TronChainStrategy {
|
|
|
7151
7328
|
break;
|
|
7152
7329
|
}
|
|
7153
7330
|
default:
|
|
7154
|
-
throw new InvalidStepsError(
|
|
7331
|
+
throw new InvalidStepsError(
|
|
7332
|
+
"tron",
|
|
7333
|
+
"Unsupported TRON parsed tx kind"
|
|
7334
|
+
);
|
|
7155
7335
|
}
|
|
7156
7336
|
const { txid } = await this.signAndBroadcast(tronWeb, unsigned);
|
|
7157
7337
|
lastTxId = txid;
|
|
7158
7338
|
}
|
|
7159
7339
|
if (!lastTxId) {
|
|
7160
|
-
throw new TransactionFailedError(
|
|
7340
|
+
throw new TransactionFailedError(
|
|
7341
|
+
"tron",
|
|
7342
|
+
"No TRON transaction was executed"
|
|
7343
|
+
);
|
|
7161
7344
|
}
|
|
7162
7345
|
return { hash: lastTxId, chainKey: "tron" };
|
|
7163
7346
|
}
|
|
@@ -7167,47 +7350,84 @@ class TronChainStrategy {
|
|
|
7167
7350
|
if (!tronWeb) {
|
|
7168
7351
|
throw new ProviderNotAvailableError("tron");
|
|
7169
7352
|
}
|
|
7353
|
+
console.log(
|
|
7354
|
+
`Waiting for ${TRON_CONFIG.requiredConfirmations} confirmations for TRON tx: ${txHash}`
|
|
7355
|
+
);
|
|
7170
7356
|
const deadline = Date.now() + TRON_CONFIG.timeout;
|
|
7171
|
-
|
|
7357
|
+
let txBlockNumber = null;
|
|
7358
|
+
while (Date.now() < deadline && !txBlockNumber) {
|
|
7172
7359
|
try {
|
|
7173
7360
|
const info = await tronWeb.trx.getTransactionInfo(txHash);
|
|
7174
|
-
|
|
7175
|
-
if (info) {
|
|
7361
|
+
if (info && info.blockNumber) {
|
|
7176
7362
|
const result = info.receipt?.result;
|
|
7177
|
-
if (result
|
|
7178
|
-
|
|
7363
|
+
if (result !== "SUCCESS") {
|
|
7364
|
+
const msg = this.hexToAscii(info.resMessage) || null;
|
|
7365
|
+
let reason = msg;
|
|
7366
|
+
const cr = info.contractResult?.[0];
|
|
7367
|
+
if (cr && /^0x?08c379a0/i.test(cr)) {
|
|
7368
|
+
try {
|
|
7369
|
+
const clean = cr.replace(/^0x/, "");
|
|
7370
|
+
const len = parseInt(clean.slice(8 + 64, 8 + 64 + 64), 16);
|
|
7371
|
+
const strHex = clean.slice(
|
|
7372
|
+
8 + 64 + 64,
|
|
7373
|
+
8 + 64 + 64 + len * 2
|
|
7374
|
+
);
|
|
7375
|
+
const str = this.hexToAscii("0x" + strHex);
|
|
7376
|
+
if (str) reason = str;
|
|
7377
|
+
} catch (e) {
|
|
7378
|
+
console.debug("TRON revert string decode error", e);
|
|
7379
|
+
}
|
|
7380
|
+
}
|
|
7381
|
+
const error2 = new TransactionRevertedError(
|
|
7382
|
+
"tron",
|
|
7383
|
+
txHash,
|
|
7384
|
+
reason || "Unknown error"
|
|
7385
|
+
);
|
|
7179
7386
|
return {
|
|
7180
|
-
completed:
|
|
7387
|
+
completed: false,
|
|
7388
|
+
error: error2.message
|
|
7181
7389
|
};
|
|
7182
7390
|
}
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7391
|
+
txBlockNumber = info.blockNumber;
|
|
7392
|
+
console.log(`TRON transaction found in block ${txBlockNumber}`);
|
|
7393
|
+
}
|
|
7394
|
+
} catch (e) {
|
|
7395
|
+
console.debug("TRON getTransactionInfo error:", e);
|
|
7396
|
+
}
|
|
7397
|
+
if (!txBlockNumber) {
|
|
7398
|
+
await new Promise((r) => setTimeout(r, TRON_CONFIG.pollingInterval));
|
|
7399
|
+
}
|
|
7400
|
+
}
|
|
7401
|
+
if (!txBlockNumber) {
|
|
7402
|
+
const error2 = new TransactionTimeoutError("tron", txHash);
|
|
7403
|
+
return {
|
|
7404
|
+
completed: false,
|
|
7405
|
+
error: error2.message
|
|
7406
|
+
};
|
|
7407
|
+
}
|
|
7408
|
+
let confirmations = 0;
|
|
7409
|
+
while (Date.now() < deadline) {
|
|
7410
|
+
try {
|
|
7411
|
+
const currentBlock = await tronWeb.trx.getCurrentBlock();
|
|
7412
|
+
const currentBlockNumber = currentBlock?.block_header?.raw_data?.number;
|
|
7413
|
+
if (currentBlockNumber) {
|
|
7414
|
+
confirmations = currentBlockNumber - txBlockNumber;
|
|
7415
|
+
if (confirmations >= TRON_CONFIG.requiredConfirmations) {
|
|
7416
|
+
console.log(
|
|
7417
|
+
`TRON transaction confirmed in block ${txBlockNumber} with ${confirmations} confirmations`
|
|
7418
|
+
);
|
|
7419
|
+
return {
|
|
7420
|
+
completed: true
|
|
7421
|
+
};
|
|
7196
7422
|
}
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
txHash,
|
|
7200
|
-
reason || "Unknown error"
|
|
7423
|
+
console.log(
|
|
7424
|
+
`TRON transaction confirmations: ${confirmations}/${TRON_CONFIG.requiredConfirmations}`
|
|
7201
7425
|
);
|
|
7202
|
-
return {
|
|
7203
|
-
completed: false,
|
|
7204
|
-
error: error2.message
|
|
7205
|
-
};
|
|
7206
7426
|
}
|
|
7207
7427
|
} catch (e) {
|
|
7208
|
-
console.debug("TRON
|
|
7428
|
+
console.debug("TRON getCurrentBlock error:", e);
|
|
7209
7429
|
}
|
|
7210
|
-
await new Promise((r) => setTimeout(r,
|
|
7430
|
+
await new Promise((r) => setTimeout(r, TRON_CONFIG.pollingInterval));
|
|
7211
7431
|
}
|
|
7212
7432
|
const error = new TransactionTimeoutError("tron", txHash);
|
|
7213
7433
|
return {
|
|
@@ -7215,14 +7435,17 @@ class TronChainStrategy {
|
|
|
7215
7435
|
error: error.message
|
|
7216
7436
|
};
|
|
7217
7437
|
} catch (error) {
|
|
7218
|
-
const chainError = toChainStrategyError(
|
|
7438
|
+
const chainError = toChainStrategyError(
|
|
7439
|
+
error,
|
|
7440
|
+
"tron",
|
|
7441
|
+
"waitForCompletion"
|
|
7442
|
+
);
|
|
7219
7443
|
return {
|
|
7220
7444
|
completed: false,
|
|
7221
7445
|
error: chainError.message
|
|
7222
7446
|
};
|
|
7223
7447
|
}
|
|
7224
7448
|
}
|
|
7225
|
-
// ========== Private Helper Methods ==========
|
|
7226
7449
|
getTronWeb() {
|
|
7227
7450
|
return typeof window !== "undefined" ? window.tronWeb : void 0;
|
|
7228
7451
|
}
|
|
@@ -7275,7 +7498,10 @@ class TronChainStrategy {
|
|
|
7275
7498
|
amount: this.extractAmountFromTxData(tx.data)
|
|
7276
7499
|
};
|
|
7277
7500
|
}
|
|
7278
|
-
throw new InvalidTransactionDataError(
|
|
7501
|
+
throw new InvalidTransactionDataError(
|
|
7502
|
+
"tron",
|
|
7503
|
+
"Cannot parse approve transaction"
|
|
7504
|
+
);
|
|
7279
7505
|
}
|
|
7280
7506
|
if (step?.type === "transfer" || step?.type === "bridge") {
|
|
7281
7507
|
const s = step;
|
|
@@ -7305,7 +7531,10 @@ class TronChainStrategy {
|
|
|
7305
7531
|
feeLimit: raw2.feeLimit
|
|
7306
7532
|
};
|
|
7307
7533
|
}
|
|
7308
|
-
throw new InvalidTransactionDataError(
|
|
7534
|
+
throw new InvalidTransactionDataError(
|
|
7535
|
+
"tron",
|
|
7536
|
+
"Cannot parse transfer/bridge transaction"
|
|
7537
|
+
);
|
|
7309
7538
|
}
|
|
7310
7539
|
if (tx?.to && tx?.value && !tx?.data) {
|
|
7311
7540
|
const v = BigInt(tx.value.toString());
|
|
@@ -7325,19 +7554,28 @@ class TronChainStrategy {
|
|
|
7325
7554
|
feeLimit: raw.feeLimit
|
|
7326
7555
|
};
|
|
7327
7556
|
}
|
|
7328
|
-
throw new InvalidTransactionDataError(
|
|
7557
|
+
throw new InvalidTransactionDataError(
|
|
7558
|
+
"tron",
|
|
7559
|
+
`Unsupported TRON step type: ${step?.type ?? "unknown"}`
|
|
7560
|
+
);
|
|
7329
7561
|
}
|
|
7330
7562
|
extractSpenderFromTxData(data) {
|
|
7331
7563
|
const clean = data.replace(/^0x/, "");
|
|
7332
7564
|
if (clean.length < 74) {
|
|
7333
|
-
throw new InvalidTransactionDataError(
|
|
7565
|
+
throw new InvalidTransactionDataError(
|
|
7566
|
+
"tron",
|
|
7567
|
+
"Invalid transaction data length"
|
|
7568
|
+
);
|
|
7334
7569
|
}
|
|
7335
7570
|
return "41" + clean.slice(32, 72);
|
|
7336
7571
|
}
|
|
7337
7572
|
extractAmountFromTxData(data) {
|
|
7338
7573
|
const clean = data.replace(/^0x/, "");
|
|
7339
7574
|
if (clean.length < 138) {
|
|
7340
|
-
throw new InvalidTransactionDataError(
|
|
7575
|
+
throw new InvalidTransactionDataError(
|
|
7576
|
+
"tron",
|
|
7577
|
+
"Invalid transaction data length"
|
|
7578
|
+
);
|
|
7341
7579
|
}
|
|
7342
7580
|
const amountHex = clean.slice(72, 136);
|
|
7343
7581
|
return BigInt("0x" + amountHex).toString();
|
|
@@ -7345,7 +7583,10 @@ class TronChainStrategy {
|
|
|
7345
7583
|
extractRecipientFromTxData(data) {
|
|
7346
7584
|
const clean = data.replace(/^0x/, "");
|
|
7347
7585
|
if (clean.length < 74) {
|
|
7348
|
-
throw new InvalidTransactionDataError(
|
|
7586
|
+
throw new InvalidTransactionDataError(
|
|
7587
|
+
"tron",
|
|
7588
|
+
"Invalid transaction data length"
|
|
7589
|
+
);
|
|
7349
7590
|
}
|
|
7350
7591
|
return "41" + clean.slice(32, 72);
|
|
7351
7592
|
}
|
|
@@ -7420,7 +7661,6 @@ class TronChainStrategy {
|
|
|
7420
7661
|
return null;
|
|
7421
7662
|
}
|
|
7422
7663
|
}
|
|
7423
|
-
// ========== Transaction Builders ==========
|
|
7424
7664
|
async buildApprove(tronWeb, p) {
|
|
7425
7665
|
const res = await tronWeb.transactionBuilder.triggerSmartContract(
|
|
7426
7666
|
p.token,
|
|
@@ -7433,7 +7673,10 @@ class TronChainStrategy {
|
|
|
7433
7673
|
p.from
|
|
7434
7674
|
);
|
|
7435
7675
|
if (!res?.transaction) {
|
|
7436
|
-
throw new TransactionFailedError(
|
|
7676
|
+
throw new TransactionFailedError(
|
|
7677
|
+
"tron",
|
|
7678
|
+
"Failed to build approve transaction"
|
|
7679
|
+
);
|
|
7437
7680
|
}
|
|
7438
7681
|
return res.transaction;
|
|
7439
7682
|
}
|
|
@@ -7449,7 +7692,10 @@ class TronChainStrategy {
|
|
|
7449
7692
|
p.from
|
|
7450
7693
|
);
|
|
7451
7694
|
if (!res?.transaction) {
|
|
7452
|
-
throw new TransactionFailedError(
|
|
7695
|
+
throw new TransactionFailedError(
|
|
7696
|
+
"tron",
|
|
7697
|
+
"Failed to build transfer transaction"
|
|
7698
|
+
);
|
|
7453
7699
|
}
|
|
7454
7700
|
return res.transaction;
|
|
7455
7701
|
}
|
|
@@ -7469,12 +7715,13 @@ class TronChainStrategy {
|
|
|
7469
7715
|
p.from
|
|
7470
7716
|
);
|
|
7471
7717
|
if (!res?.transaction) {
|
|
7472
|
-
throw new TransactionFailedError(
|
|
7718
|
+
throw new TransactionFailedError(
|
|
7719
|
+
"tron",
|
|
7720
|
+
"Failed to build raw call transaction"
|
|
7721
|
+
);
|
|
7473
7722
|
}
|
|
7474
7723
|
return res.transaction;
|
|
7475
7724
|
}
|
|
7476
|
-
// ========== Transaction Sender ==========
|
|
7477
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
7478
7725
|
async signAndBroadcast(tronWeb, unsignedTx) {
|
|
7479
7726
|
const signed = await tronWeb.trx.sign(unsignedTx);
|
|
7480
7727
|
const sent = await tronWeb.trx.sendRawTransaction(signed);
|
|
@@ -7486,33 +7733,77 @@ class TronChainStrategy {
|
|
|
7486
7733
|
}
|
|
7487
7734
|
return { txid: sent.txid };
|
|
7488
7735
|
}
|
|
7736
|
+
/**
|
|
7737
|
+
* Check if a transaction has been solidified (confirmed by solidityNode)
|
|
7738
|
+
* This is an additional check for critical transactions to ensure they are
|
|
7739
|
+
* truly confirmed and available through the solidityNode API.
|
|
7740
|
+
*
|
|
7741
|
+
* Usage: Call this method after waitForCompletion for high-value transactions
|
|
7742
|
+
* to get additional assurance that the transaction is solidified.
|
|
7743
|
+
*
|
|
7744
|
+
* @param txHash - Transaction hash to check
|
|
7745
|
+
* @returns true if the transaction is solidified, false otherwise
|
|
7746
|
+
*/
|
|
7747
|
+
async checkSolidified(txHash) {
|
|
7748
|
+
try {
|
|
7749
|
+
const tronWeb = this.getTronWeb();
|
|
7750
|
+
if (!tronWeb) {
|
|
7751
|
+
return false;
|
|
7752
|
+
}
|
|
7753
|
+
const info = await tronWeb.trx.getTransactionInfo(txHash);
|
|
7754
|
+
if (!info || !info.blockNumber) {
|
|
7755
|
+
console.debug(
|
|
7756
|
+
"Transaction not yet solidified (no blockNumber in info)"
|
|
7757
|
+
);
|
|
7758
|
+
return false;
|
|
7759
|
+
}
|
|
7760
|
+
const result = info.receipt?.result;
|
|
7761
|
+
if (result === "SUCCESS") {
|
|
7762
|
+
console.log(
|
|
7763
|
+
`Transaction ${txHash} is solidified in block ${info.blockNumber}`
|
|
7764
|
+
);
|
|
7765
|
+
return true;
|
|
7766
|
+
}
|
|
7767
|
+
console.debug(`Transaction solidified but result is: ${result}`);
|
|
7768
|
+
return false;
|
|
7769
|
+
} catch (error) {
|
|
7770
|
+
console.debug("Error checking solidified status:", error);
|
|
7771
|
+
return false;
|
|
7772
|
+
}
|
|
7773
|
+
}
|
|
7489
7774
|
}
|
|
7490
7775
|
function ChainStrategyProvider({
|
|
7491
7776
|
children,
|
|
7492
7777
|
evmWallet,
|
|
7493
7778
|
tonWallet,
|
|
7494
|
-
tronWallet
|
|
7779
|
+
tronWallet,
|
|
7780
|
+
tonClient,
|
|
7781
|
+
tonApiKey
|
|
7495
7782
|
}) {
|
|
7496
7783
|
const evmStrategy = useMemo(
|
|
7497
7784
|
() => new EvmChainStrategy({
|
|
7498
7785
|
evmAddress: evmWallet.address,
|
|
7499
7786
|
evmIsConnected: evmWallet.isConnected,
|
|
7500
7787
|
evmDisconnect: evmWallet.disconnect,
|
|
7501
|
-
walletClient: evmWallet.walletClient
|
|
7788
|
+
walletClient: evmWallet.walletClient,
|
|
7789
|
+
publicClient: evmWallet.publicClient
|
|
7502
7790
|
}),
|
|
7503
7791
|
[
|
|
7504
7792
|
evmWallet.address,
|
|
7505
7793
|
evmWallet.isConnected,
|
|
7506
7794
|
evmWallet.disconnect,
|
|
7507
|
-
evmWallet.walletClient
|
|
7795
|
+
evmWallet.walletClient,
|
|
7796
|
+
evmWallet.publicClient
|
|
7508
7797
|
]
|
|
7509
7798
|
);
|
|
7510
7799
|
const tonStrategy = useMemo(
|
|
7511
7800
|
() => new TonChainStrategy({
|
|
7512
7801
|
tonConnectUI: tonWallet.tonConnectUI,
|
|
7513
|
-
tonAddress: tonWallet.address
|
|
7802
|
+
tonAddress: tonWallet.address,
|
|
7803
|
+
tonClient,
|
|
7804
|
+
tonApiKey
|
|
7514
7805
|
}),
|
|
7515
|
-
[tonWallet.tonConnectUI, tonWallet.address]
|
|
7806
|
+
[tonWallet.tonConnectUI, tonWallet.address, tonClient, tonApiKey]
|
|
7516
7807
|
);
|
|
7517
7808
|
const tronStrategy = useMemo(
|
|
7518
7809
|
() => new TronChainStrategy({
|
|
@@ -7534,9 +7825,12 @@ function ChainStrategyProvider({
|
|
|
7534
7825
|
() => new ChainStrategyRegistry([evmStrategy, tonStrategy, tronStrategy]),
|
|
7535
7826
|
[evmStrategy, tonStrategy, tronStrategy]
|
|
7536
7827
|
);
|
|
7537
|
-
const value =
|
|
7538
|
-
|
|
7539
|
-
|
|
7828
|
+
const value = useMemo(
|
|
7829
|
+
() => ({
|
|
7830
|
+
chainRegistry
|
|
7831
|
+
}),
|
|
7832
|
+
[chainRegistry]
|
|
7833
|
+
);
|
|
7540
7834
|
return /* @__PURE__ */ jsx(ChainStrategyContext.Provider, { value, children });
|
|
7541
7835
|
}
|
|
7542
7836
|
const EvaaBridgeWithProviders = (props) => {
|
|
@@ -7545,6 +7839,7 @@ const EvaaBridgeWithProviders = (props) => {
|
|
|
7545
7839
|
const { address: evmAddress, isConnected: evmIsConnected } = useAccount();
|
|
7546
7840
|
const { disconnect: evmDisconnect } = useDisconnect();
|
|
7547
7841
|
const { data: walletClient } = useWalletClient();
|
|
7842
|
+
const publicClient = usePublicClient();
|
|
7548
7843
|
const {
|
|
7549
7844
|
address: tronAddress,
|
|
7550
7845
|
connected: tronConnected,
|
|
@@ -7569,7 +7864,8 @@ const EvaaBridgeWithProviders = (props) => {
|
|
|
7569
7864
|
address: evmAddress,
|
|
7570
7865
|
isConnected: evmIsConnected,
|
|
7571
7866
|
disconnect: evmDisconnect,
|
|
7572
|
-
walletClient
|
|
7867
|
+
walletClient,
|
|
7868
|
+
publicClient
|
|
7573
7869
|
},
|
|
7574
7870
|
tonWallet: {
|
|
7575
7871
|
tonConnectUI,
|
|
@@ -7582,6 +7878,8 @@ const EvaaBridgeWithProviders = (props) => {
|
|
|
7582
7878
|
connect: tronConnect,
|
|
7583
7879
|
disconnect: tronDisconnect
|
|
7584
7880
|
},
|
|
7881
|
+
tonClient: props.tonClient,
|
|
7882
|
+
tonApiKey: props.tonApiKey,
|
|
7585
7883
|
children: /* @__PURE__ */ jsx(EvaaBridgeContent, { ...props })
|
|
7586
7884
|
}
|
|
7587
7885
|
);
|
|
@@ -7686,16 +7984,16 @@ const EvaaBridgeContent = ({
|
|
|
7686
7984
|
}, [chains, assetMatrix, allowedFromChains]);
|
|
7687
7985
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
7688
7986
|
/* @__PURE__ */ jsxs(
|
|
7689
|
-
|
|
7987
|
+
Card,
|
|
7690
7988
|
{
|
|
7691
7989
|
className: cn(
|
|
7692
|
-
"
|
|
7990
|
+
"max-w-md w-full mx-auto flex flex-col relative",
|
|
7693
7991
|
className
|
|
7694
7992
|
),
|
|
7695
7993
|
ref: modalContainerRef,
|
|
7696
7994
|
children: [
|
|
7697
7995
|
/* @__PURE__ */ jsx(FormHeader, { modalContainer: modalContainerRef.current }),
|
|
7698
|
-
/* @__PURE__ */ jsxs(
|
|
7996
|
+
/* @__PURE__ */ jsxs(CardContent, { className: "space-y-[1px]", children: [
|
|
7699
7997
|
/* @__PURE__ */ jsx(
|
|
7700
7998
|
SwapSection,
|
|
7701
7999
|
{
|
|
@@ -7733,10 +8031,10 @@ const EvaaBridgeContent = ({
|
|
|
7733
8031
|
enabled: sendToAnother,
|
|
7734
8032
|
onToggle: () => setSendToAnother((v) => !v)
|
|
7735
8033
|
}
|
|
7736
|
-
)
|
|
8034
|
+
),
|
|
8035
|
+
/* @__PURE__ */ jsx(SubmitButton, {})
|
|
7737
8036
|
] }),
|
|
7738
|
-
/* @__PURE__ */ jsx(
|
|
7739
|
-
/* @__PURE__ */ jsx(ReceiveRow, {})
|
|
8037
|
+
/* @__PURE__ */ jsx(CardFooter, { children: /* @__PURE__ */ jsx(ReceiveRow, {}) })
|
|
7740
8038
|
]
|
|
7741
8039
|
}
|
|
7742
8040
|
),
|
|
@@ -7825,8 +8123,8 @@ export {
|
|
|
7825
8123
|
getQuoteDetails,
|
|
7826
8124
|
getQuoteFees,
|
|
7827
8125
|
getQuotesByPriority,
|
|
7828
|
-
getSwapBalances,
|
|
7829
8126
|
getTokens,
|
|
8127
|
+
getTonBalances,
|
|
7830
8128
|
getTronBalances,
|
|
7831
8129
|
isAddressValidForChain,
|
|
7832
8130
|
isEvmAddress,
|
|
@@ -7835,7 +8133,7 @@ export {
|
|
|
7835
8133
|
isZeroAddr,
|
|
7836
8134
|
listAssetsForSelect,
|
|
7837
8135
|
lookupTokenMeta,
|
|
7838
|
-
normalizeTickerSymbol,
|
|
8136
|
+
normalizeTickerSymbol$1 as normalizeTickerSymbol,
|
|
7839
8137
|
pollUntilDelivered,
|
|
7840
8138
|
resolveTokenOnChain,
|
|
7841
8139
|
resolveTokenOnChainFromMatrix$2 as resolveTokenOnChainFromMatrix,
|