diff --git a/index.html b/index.html index e6b1cc0a6..2a070db6e 100644 --- a/index.html +++ b/index.html @@ -38,6 +38,7 @@ diff --git a/package.json b/package.json index 5f08dac19..10954f815 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@oraichain/orai-bitcoin": "2.0.0", "@oraichain/oraidex-common-ui": "1.0.11", "@oraichain/oraidex-contracts-sdk": "1.0.55", - "@oraichain/oraidex-universal-swap": "1.1.23", + "@oraichain/oraidex-universal-swap": "1.1.23-beta.1", "@react-spring/web": "^9.7.5", "@reduxjs/toolkit": "^1.9.3", "@sentry/react": "7.99.0", @@ -37,8 +37,17 @@ "@solana/wallet-adapter-wallets": "^0.19.32", "@solana/web3.js": "^1.95.5", "@tanstack/react-query": "^4.32.6", + "@oraichain/ton-bridge-contracts": "^0.15.8", + "@oraichain/tonbridge-contracts-sdk": "^1.3.1", + "@oraichain/tonbridge-sdk": "^1.3.6", + "@orbs-network/ton-access": "^2.3.3", "@tharsis/proto": "^0.1.17", "@tippyjs/react": "^4.2.0", + "@ton/core": "^0.56.3", + "@ton/crypto": "^3.3.0", + "@ton/ton": "^14.0.0", + "@tonconnect/protocol": "^2.2.6", + "@tonconnect/ui-react": "^2.0.6", "@visx/curve": "^2.17.0", "@visx/gradient": "^3.3.0", "@visx/responsive": "^2.17.0", @@ -126,7 +135,7 @@ "scripts": { "analyze": "source-map-explorer 'build/static/js/*.js'", "start": "vite", - "build": "vite build", + "build": "NODE_OPTIONS='--max-old-space-size=12288' vite build", "serve": "vite serve", "ts-check": "tsc --noEmit", "postinstall": "patch-package", @@ -157,7 +166,7 @@ "bitcoinjs-lib": "5.2.0", "axios": "0.26.1", "@sentry/react": "7.99.0", - "@oraichain/oraidex-common": "1.1.39" + "@oraichain/oraidex-common": "1.1.40-beta.1" }, "engines": { "node": "^18 || ^20" diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 000000000..2005f7544 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,5 @@ +{ + "url": "https://oraidex.io", + "name": "OraiDex", + "iconUrl": "https://oraidex.io/favicon.svg" +} diff --git a/src/components/CheckBox.module.scss b/src/components/CheckBox.module.scss index 7fec28f6c..bb7a11d69 100644 --- a/src/components/CheckBox.module.scss +++ b/src/components/CheckBox.module.scss @@ -30,8 +30,11 @@ left: 0; height: 16px; width: 16px; - border: 1px solid $blue-color; transform: translateY(-50%); + + @include theme { + border: 1px solid theme-get('primary-surface-default-dark-3'); + } } .radioBox { @@ -40,14 +43,16 @@ /* On mouse-over, add a grey background color */ .container:hover input ~ .checkmark { - border-color: $blue-color; + @include theme { + border-color: theme-get('primary-surface-default-dark-3'); + } } /* When the checkbox is checked, add a blue background */ .container input:checked ~ .checkmark { - border-color: $blue-color; - @include theme() { - background-color: theme-get('bg-color'); + @include theme { + border-color: theme-get('primary-surface-default-dark-3'); + background-color: theme-get('neutral-6'); } } @@ -69,9 +74,11 @@ top: 1px; width: 5px; height: 10px; - border: solid $blue-color; - border-width: 0 2px 2px 0; -webkit-transform: rotate(45deg); -ms-transform: rotate(45deg); transform: rotate(45deg); + @include theme { + border: solid theme-get('primary-surface-default-dark-3'); + border-width: 0 2px 2px 0; + } } diff --git a/src/components/Modals/SelectTokenModal/SelectTokenModal.module.scss b/src/components/Modals/SelectTokenModal/SelectTokenModal.module.scss index 4c6e1aa08..263019a4b 100644 --- a/src/components/Modals/SelectTokenModal/SelectTokenModal.module.scss +++ b/src/components/Modals/SelectTokenModal/SelectTokenModal.module.scss @@ -1,5 +1,5 @@ @import 'src/styles/themes'; -@import "src/styles/mixins"; +@import 'src/styles/mixins'; .select { background: #141416; @@ -28,7 +28,7 @@ /* aiRight/White */ &.light { - color: #6F767E; + color: #6f767e; } } @@ -42,11 +42,10 @@ .item { &.light { &.light { - color: #6F767E; - } + color: #6f767e; + } } - border-radius: 8px; display: flex; gap: 10px; @@ -69,12 +68,13 @@ &:hover { cursor: pointer; - background: #333642; + background: #232521; + opacity: 0.7; } &.light:hover { - background-color: $blue-color; - color: #fff; + background-color: #f7f7f7; + opacity: 0.7; } .logo { @@ -99,4 +99,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/components/Modals/SelectTokenModal/SelectTokenModal.tsx b/src/components/Modals/SelectTokenModal/SelectTokenModal.tsx index 48221efe9..7e0a8effd 100644 --- a/src/components/Modals/SelectTokenModal/SelectTokenModal.tsx +++ b/src/components/Modals/SelectTokenModal/SelectTokenModal.tsx @@ -45,63 +45,80 @@ export const SelectTokenModal: FC = ({
{type === 'token' ? 'Select a token' : 'Select a network'}
- {items?.map((item: TokenItemType | CustomChainInfo) => { - let key: string, title: string, balance: string; - let tokenAndChainIcons; - if (type === 'token') { - const token = item as TokenItemType; - key = token.denom; - title = token.name; - let sumAmountDetails: AmountDetails = {}; - // by default, we only display the amount that matches the token denom - sumAmountDetails[token.denom] = amounts[token.denom]; - let sumAmount: number = toSumDisplay(sumAmountDetails); - // if there are sub-denoms, we get sub amounts & calculate sum display of both sub & main amount - if (token.evmDenoms) { - const subAmounts = getSubAmountDetails(amounts, token); - sumAmountDetails = { ...sumAmountDetails, ...subAmounts }; - sumAmount = toSumDisplay(sumAmountDetails); + {items + ?.map((item: TokenItemType | CustomChainInfo) => { + let key: string, title: string, balance: string, rawBalance: string; + let tokenAndChainIcons; + if (type === 'token') { + const token = item as TokenItemType; + key = token.denom; + title = token.name; + let sumAmountDetails: AmountDetails = {}; + // by default, we only display the amount that matches the token denom + sumAmountDetails[token.denom] = amounts[token.denom]; + let sumAmount: number = toSumDisplay(sumAmountDetails); + // if there are sub-denoms, we get sub amounts & calculate sum display of both sub & main amount + if (token.evmDenoms) { + const subAmounts = getSubAmountDetails(amounts, token); + sumAmountDetails = { ...sumAmountDetails, ...subAmounts }; + sumAmount = toSumDisplay(sumAmountDetails); + } + tokenAndChainIcons = tokensIcon.find((tokenIcon) => tokenIcon.coinGeckoId === token.coinGeckoId); + balance = sumAmount > 0 ? sumAmount.toFixed(truncDecimals) : '0'; + rawBalance = balance; + } else { + const network = item as CustomChainInfo; + key = network.chainId.toString(); + title = network.chainName; + const subAmounts = Object.fromEntries( + Object.entries(amounts).filter( + ([denom]) => tokenMap[denom] && tokenMap[denom].chainId === network.chainId + ) + ); + const totalUsd = getTotalUsd(subAmounts, prices); + tokenAndChainIcons = chainIcons.find((chainIcon) => chainIcon.chainId === network.chainId); + rawBalance = totalUsd > 0 ? totalUsd.toFixed(2) : '0'; + balance = '$' + rawBalance; } - tokenAndChainIcons = tokensIcon.find((tokenIcon) => tokenIcon.coinGeckoId === token.coinGeckoId); - balance = sumAmount > 0 ? sumAmount.toFixed(truncDecimals) : '0'; - } else { - const network = item as CustomChainInfo; - key = network.chainId.toString(); - title = network.chainName; - const subAmounts = Object.fromEntries( - Object.entries(amounts).filter(([denom]) => tokenMap[denom] && tokenMap[denom].chainId === network.chainId) - ); - const totalUsd = getTotalUsd(subAmounts, prices); - tokenAndChainIcons = chainIcons.find((chainIcon) => chainIcon.chainId === network.chainId); - balance = '$' + (totalUsd > 0 ? totalUsd.toFixed(2) : '0'); - } - const icon = - tokenAndChainIcons && theme === 'light' ? ( - - ) : ( - - ); + const icon = + tokenAndChainIcons && theme === 'light' ? ( + + ) : ( + + ); - return ( -
{ - setToken(key, type === 'token' && (item as TokenItemType).contractAddress); - if (setSymbol) { - setSymbol(title); - } - close(); - }} - > - {icon} -
-
{title}
+ return { + ...item, + key, + title, + balance, + rawBalance, + icon + }; + }) + .sort((a, b) => Number(b.rawBalance || 0) - Number(a.rawBalance || 0)) + .map((item, idx) => { + const { key, title, balance, icon } = item; + return ( +
{ + setToken(key, type === 'token' && (item as TokenItemType).contractAddress); + if (setSymbol) { + setSymbol(title); + } + close(); + }} + > + {icon} +
+
{title}
+
+
{balance}
-
{balance}
-
- ); - })} + ); + })}
diff --git a/src/components/Modals/SlippageModal/SlippageModal.module.scss b/src/components/Modals/SlippageModal/SlippageModal.module.scss index 45c67e4bb..3246454a6 100644 --- a/src/components/Modals/SlippageModal/SlippageModal.module.scss +++ b/src/components/Modals/SlippageModal/SlippageModal.module.scss @@ -1,9 +1,9 @@ -@import "src/styles/mixins"; +@import 'src/styles/mixins'; .setting { border-radius: 8px; padding: 20px 24px 24px; - border: 1px solid #A871DF; + border: 1px solid #a871df; .header { display: flex; @@ -21,7 +21,7 @@ line-height: 100%; /* identical to box height, or 18px */ text-align: center; - color: #F0B90B; + color: #f0b90b; img { margin-right: 10px; @@ -34,14 +34,13 @@ height: 18px; path { - fill: #777E91; + fill: #777e91; } } } - .subtitle { - color: #A871DF; + color: #a871df; font-size: 16px; font-style: normal; font-weight: 500; @@ -70,7 +69,7 @@ border-radius: 4px; padding: 6px 14px; margin: 0 4px; - color: #A871DF; + color: #a871df; .input { max-width: 40px; @@ -88,7 +87,7 @@ } .isChosen { - border: 1px solid #A871DF; + border: 1px solid #a871df; opacity: 1; } } @@ -115,22 +114,22 @@ } .light-modal { - background-color: #FCFCFC; + background-color: #fcfcfc; box-shadow: 0px 4px 24px rgba(0, 0, 0, 0.05); .options { .item { - background: #EEE7FF; + background: #eee7ff; } } } .dark-modal { - background-color: #2B2D3B; + background-color: #232521; .options { .item { - background: #423A56; + background: #423a56; } } -} \ No newline at end of file +} diff --git a/src/components/WalletManagement/ModalChooseWallet/WalletByNetwork/WalletByNetwork.tsx b/src/components/WalletManagement/ModalChooseWallet/WalletByNetwork/WalletByNetwork.tsx index 21fe3d415..17c396d17 100644 --- a/src/components/WalletManagement/ModalChooseWallet/WalletByNetwork/WalletByNetwork.tsx +++ b/src/components/WalletManagement/ModalChooseWallet/WalletByNetwork/WalletByNetwork.tsx @@ -8,7 +8,7 @@ import useTheme from 'hooks/useTheme'; import useWalletReducer from 'hooks/useWalletReducer'; import Keplr from 'libs/keplr'; import { initClient } from 'libs/utils'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { WalletItem } from '../WalletItem'; import styles from './WalletByNetwork.module.scss'; import { useInactiveConnect } from 'hooks/useMetamask'; @@ -17,6 +17,8 @@ import DefaultIcon from 'assets/icons/tokens.svg?react'; import { ChainEnableByNetwork, triggerUnlockOwalletInEvmNetwork } from 'components/WalletManagement/wallet-helper'; import { useWallet } from '@solana/wallet-adapter-react'; import { useWalletModal } from '@solana/wallet-adapter-react-ui'; +import { useTonConnectModal, useTonConnectUI } from '@tonconnect/ui-react'; +import useTonConnectAddress from 'hooks/useTonConnectAddress'; export type ConnectStatus = 'init' | 'confirming-switch' | 'confirming-disconnect' | 'loading' | 'failed' | 'success'; export const WalletByNetwork = ({ walletProvider }: { walletProvider: WalletProvider }) => { @@ -27,6 +29,7 @@ export const WalletByNetwork = ({ walletProvider }: { walletProvider: WalletProv const [, setOraiAddress] = useConfigReducer('address'); const [, setTronAddress] = useConfigReducer('tronAddress'); const [, setBtcAddress] = useConfigReducer('btcAddress'); + const [, setTonAddress] = useConfigReducer('tonAddress'); const [, setMetamaskAddress] = useConfigReducer('metamaskAddress'); const [solAddress, setSolanaAddress] = useConfigReducer('solAddress'); const [, setCosmosAddress] = useConfigReducer('cosmosAddress'); @@ -35,6 +38,7 @@ export const WalletByNetwork = ({ walletProvider }: { walletProvider: WalletProv const solanaWallet = useWallet(); const { visible, setVisible } = useWalletModal(); + const { handleConnectTon, handleDisconnectTon } = useTonConnectAddress(); const handleConfirmSwitch = async () => { setConnectStatus('loading'); @@ -120,6 +124,14 @@ export const WalletByNetwork = ({ walletProvider }: { walletProvider: WalletProv ); }; + const handleConnectWalletInTONNetwork = async (walletType: WalletType) => { + if (walletType === 'owallet') { + // TODO: need check when use multi wallet support bitcoin + } + + handleConnectTon(); + }; + const handleConnectWalletByNetwork = async (wallet: WalletNetwork) => { try { setConnectStatus('loading'); @@ -139,14 +151,20 @@ export const WalletByNetwork = ({ walletProvider }: { walletProvider: WalletProv case 'solana': await handleConnectWalletInSolanaNetwork(wallet.nameRegistry); break; + case 'ton': + await handleConnectWalletInTONNetwork(wallet.nameRegistry); + break; default: setConnectStatus('init'); break; } - setWalletByNetworks({ - ...walletByNetworks, - [networkType]: wallet.nameRegistry - }); + + if (networkType !== 'ton') { + setWalletByNetworks({ + ...walletByNetworks, + [networkType]: wallet.nameRegistry + }); + } setCurrentWalletConnecting(null); setConnectStatus('init'); } catch (error) { @@ -198,6 +216,10 @@ export const WalletByNetwork = ({ walletProvider }: { walletProvider: WalletProv case 'solana': setSolanaAddress(undefined); break; + case 'ton': + setTonAddress(undefined); + handleDisconnectTon(); + break; default: break; } diff --git a/src/components/WalletManagement/ModalChooseWallet/WalletItem/WalletItem.module.scss b/src/components/WalletManagement/ModalChooseWallet/WalletItem/WalletItem.module.scss index 20bfc2787..c4aa48472 100644 --- a/src/components/WalletManagement/ModalChooseWallet/WalletItem/WalletItem.module.scss +++ b/src/components/WalletManagement/ModalChooseWallet/WalletItem/WalletItem.module.scss @@ -1,74 +1,77 @@ .walletItem { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 8px; + padding: 10px; + border-radius: 8px; + cursor: pointer; + + .loadingIcon { + flex: 1; display: flex; - flex-direction: column; - justify-content: center; align-items: center; - gap: 8px; - padding: 10px; - border-radius: 8px; - cursor: pointer; - - .loadingIcon { - flex: 1; - display: flex; - align-items: center; - justify-content: center; + justify-content: center; - div { - background-color: #aee67f; - padding: 8px; - border-radius: 50%; - width: 40px; - height: 40px; - } + div { + background-color: #aee67f; + padding: 8px; + border-radius: 50%; + width: 40px; + height: 40px; } + } - &:hover { - background-color: #DFE0DE; - } + &:hover { + background-color: #dfe0de; + } - &>button { - padding: 5px 10px; - font-size: 14px; - } + & > button { + padding: 5px 10px; + font-size: 14px; + } - .walletIcon { - display: flex; - width: 40px; - height: 40px; - } + .walletIcon { + display: flex; + width: 40px; + height: 40px; - .walletName { - color: #232521; - font-size: 14px; - font-weight: 500; - line-height: 150%; - text-align: center; + svg { + width: 40px; + height: 40px; } + } + + .walletName { + color: #232521; + font-size: 14px; + font-weight: 500; + line-height: 150%; + text-align: center; + } } .dark { - &:hover { - background-color: #494949; - } + &:hover { + background-color: #494949; + } - .walletName { - color: #f7f7f7; - } + .walletName { + color: #f7f7f7; + } } .disabled { - opacity: 0.5; - cursor: not-allowed; - pointer-events: none; + opacity: 0.5; + cursor: not-allowed; + pointer-events: none; } .connecting { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - width: 100%; - - -} \ No newline at end of file + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + width: 100%; +} diff --git a/src/components/WalletManagement/ModalDisconnect/ModalDisconnect.tsx b/src/components/WalletManagement/ModalDisconnect/ModalDisconnect.tsx index e5cdebfa9..41f176c3a 100644 --- a/src/components/WalletManagement/ModalDisconnect/ModalDisconnect.tsx +++ b/src/components/WalletManagement/ModalDisconnect/ModalDisconnect.tsx @@ -8,6 +8,7 @@ import styles from './ModalDisconnect.module.scss'; import useWalletReducer from 'hooks/useWalletReducer'; import { reduceString } from 'libs/utils'; import { useCopyClipboard } from 'hooks/useCopyClipboard'; +import useTonConnectAddress from 'hooks/useTonConnectAddress'; import SuccessIcon from 'assets/icons/toast_success.svg?react'; const cx = cn.bind(styles); @@ -21,10 +22,12 @@ export const ModalDisconnect: React.FC<{ const [walletByNetworks, setWalletByNetworks] = useWalletReducer('walletsByNetwork'); const [oraiAddress, setOraiAddress] = useConfigReducer('address'); const [tronAddress, setTronAddress] = useConfigReducer('tronAddress'); + const [tonAddress, setTonAddress] = useConfigReducer('tonAddress'); const [btcAddress, setBtcAddress] = useConfigReducer('btcAddress'); const [solAddress, setSolAddress] = useConfigReducer('solAddress'); const [metamaskAddress, setMetamaskAddress] = useConfigReducer('metamaskAddress'); const { isCopied, copiedValue, handleCopy } = useCopyClipboard(); + const { handleDisconnectTon } = useTonConnectAddress(); const chains = walletProvider.find((provider) => provider.networkType === currentDisconnectingNetwork)?.networks || []; @@ -51,6 +54,9 @@ export const ModalDisconnect: React.FC<{ case 'solana': choosedAddressDisplayByNetwork = solAddress; break; + case 'ton': + choosedAddressDisplayByNetwork = tonAddress; + break; default: break; } @@ -82,6 +88,9 @@ export const ModalDisconnect: React.FC<{ case 'solana': setSolAddress(undefined); break; + case 'ton': + handleDisconnectTon(); + break; default: break; } diff --git a/src/components/WalletManagement/MyWallet/MyWallet.tsx b/src/components/WalletManagement/MyWallet/MyWallet.tsx index cd1c6e1c3..d6369437a 100644 --- a/src/components/WalletManagement/MyWallet/MyWallet.tsx +++ b/src/components/WalletManagement/MyWallet/MyWallet.tsx @@ -13,7 +13,8 @@ import { btcWallets, type NetworkType, WalletNetwork, - solanaWallets + solanaWallets, + tonWallets } from 'components/WalletManagement/walletConfig'; import { tronNetworksWithIcon, @@ -21,7 +22,8 @@ import { evmNetworksIconWithoutTron, getListAddressCosmos, btcNetworksWithIcon, - solanaNetworksWithIcon + solanaNetworksWithIcon, + tonNetworksWithIcon } from 'helper'; import { useCoinGeckoPrices } from 'hooks/useCoingecko'; import useConfigReducer from 'hooks/useConfigReducer'; @@ -49,6 +51,7 @@ export const MyWallet: React.FC<{ const [tronAddress] = useConfigReducer('tronAddress'); const [btcAddress] = useConfigReducer('btcAddress'); const [solAddress] = useConfigReducer('solAddress'); + const [tonAddress] = useConfigReducer('tonAddress'); const [metamaskAddress] = useConfigReducer('metamaskAddress'); const [cosmosAddresses, setCosmosAddress] = useConfigReducer('cosmosAddress'); @@ -181,6 +184,16 @@ export const MyWallet: React.FC<{ return renderWalletAddress(solNetworks, solWalletConnected, (_network) => solAddress); }; + const renderTonAddresses = () => { + if (!tonAddress) return null; + const tonWalletConnected = tonWallets.find((item) => item.nameRegistry === walletByNetworks.ton); + + if (!tonWalletConnected) return <>; + + const tonNetworks = tonNetworksWithIcon.map((evm) => ({ ...evm, typeChain: 'ton' })); + return renderWalletAddress(tonNetworks, tonWalletConnected, (_network) => tonAddress); + }; + return (
diff --git a/src/components/WalletManagement/WalletManagement.tsx b/src/components/WalletManagement/WalletManagement.tsx index fde1238f9..eb0ff1fa0 100644 --- a/src/components/WalletManagement/WalletManagement.tsx +++ b/src/components/WalletManagement/WalletManagement.tsx @@ -22,6 +22,7 @@ export const WalletManagement: FC<{}> = () => { const [theme] = useConfigReducer('theme'); const [oraiAddress] = useConfigReducer('address'); const [tronAddress] = useConfigReducer('tronAddress'); + const [tonAddress] = useConfigReducer('tonAddress'); const [btcAddress] = useConfigReducer('btcAddress'); const [solAddress] = useConfigReducer('solAddress'); const [metamaskAddress] = useConfigReducer('metamaskAddress'); @@ -66,6 +67,9 @@ export const WalletManagement: FC<{}> = () => { case 'bitcoin': isActive = isCheckOwallet; break; + case 'ton': + isActive = true; + break; } return { ...wallet, isActive }; } @@ -86,7 +90,7 @@ export const WalletManagement: FC<{}> = () => { // load balance every time change address useEffect(() => { - const addresses = { oraiAddress, tronAddress, metamaskAddress, btcAddress, solAddress }; + const addresses = { oraiAddress, tronAddress, metamaskAddress, btcAddress, solAddress, tonAddress }; const filteredAddresses = {}; for (const key in addresses) { @@ -98,7 +102,7 @@ export const WalletManagement: FC<{}> = () => { loadTokenAmounts(filteredAddresses); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [oraiAddress, tronAddress, metamaskAddress, btcAddress, solAddress]); + }, [oraiAddress, tronAddress, metamaskAddress, btcAddress, solAddress, tonAddress]); // reset balance when disconnect useEffect(() => { @@ -109,10 +113,11 @@ export const WalletManagement: FC<{}> = () => { if (!oraiAddress) arrResetBalance.push('keplr'); if (!btcAddress) arrResetBalance.push('bitcoin'); if (!solAddress) arrResetBalance.push('phantom'); + if (!tonAddress) arrResetBalance.push('ton'); arrResetBalance.length && handleResetBalance(arrResetBalance); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [oraiAddress, tronAddress, metamaskAddress, btcAddress, solAddress]); + }, [oraiAddress, tronAddress, metamaskAddress, btcAddress, solAddress, tonAddress]); const isAnyWalletConnected = Object.values(walletByNetworks).some((wallet) => wallet !== null); useEffect(() => { diff --git a/src/components/WalletManagement/useResetBalance.ts b/src/components/WalletManagement/useResetBalance.ts index 109ca95ab..05432d763 100644 --- a/src/components/WalletManagement/useResetBalance.ts +++ b/src/components/WalletManagement/useResetBalance.ts @@ -3,7 +3,7 @@ import { btcTokens, solTokens } from 'config/bridgeTokens'; import { useDispatch } from 'react-redux'; import { updateAmounts } from 'reducer/token'; -export type Wallet = WalletType | 'metamask' | 'tron' | 'bitcoin' | 'phantom'; +export type Wallet = WalletType | 'metamask' | 'tron' | 'bitcoin' | 'ton' | 'phantom'; export const useResetBalance = () => { const dispatch = useDispatch(); @@ -39,6 +39,9 @@ export const useResetBalance = () => { case 'phantom': updatedAmounts = resetBalanceSol(); break; + case 'ton': + updatedAmounts = resetBalanceTon(); + break; default: break; } @@ -70,5 +73,10 @@ export const useResetBalance = () => { return Object.fromEntries(solTokens.map((t) => [t.denom, '0'])); }; + const resetBalanceTon = () => { + const tronTokens = flattenTokens.filter((token) => token.chainId === 'ton'); + return Object.fromEntries(tronTokens.map((t) => [t.denom, '0'])); + }; + return { handleResetBalance }; }; diff --git a/src/components/WalletManagement/walletConfig.ts b/src/components/WalletManagement/walletConfig.ts index 29e6398a9..d5b94d547 100644 --- a/src/components/WalletManagement/walletConfig.ts +++ b/src/components/WalletManagement/walletConfig.ts @@ -4,16 +4,18 @@ import MetamaskIcon from 'assets/icons/metamask-icon.svg?react'; import OwalletIcon from 'assets/icons/owallet-icon.svg?react'; import PhantomIcon from 'assets/icons/phantom.svg?react'; import TronIcon from 'assets/icons/tron-icon.svg?react'; +import TonIcon from 'assets/icons/ton.svg?react'; import { cosmosNetworksWithIcon, evmNetworksIconWithoutTron, tronNetworksWithIcon, btcNetworksWithIcon, - solanaNetworksWithIcon + solanaNetworksWithIcon, + tonNetworksWithIcon } from 'helper'; -export type NetworkType = 'cosmos' | 'evm' | 'tron' | 'bitcoin' | 'solana'; -export type WalletType = WalletCosmosType | 'metamask' | 'tronLink' | 'eip191' | 'bitcoin' | 'phantom'; +export type NetworkType = 'cosmos' | 'evm' | 'tron' | 'bitcoin' | 'solana' | 'ton'; +export type WalletType = WalletCosmosType | 'metamask' | 'tronLink' | 'eip191' | 'bitcoin' | 'phantom' | 'ton'; export type WalletNetwork = { icon: React.FunctionComponent< React.SVGProps & { @@ -116,12 +118,22 @@ export const solanaWallets: WalletNetwork[] = [ } ]; +export const tonWallets: WalletNetwork[] = [ + { + icon: TonIcon, + name: 'TonConnect', + nameRegistry: 'ton', + isActive: true + } +]; + export const allWallets: WalletNetwork[] = [ ...cosmosWallets, ...tronWallets, ...evmWallets, ...btcWallets, - ...solanaWallets + ...solanaWallets, + ...tonWallets ]; export const walletProvider: WalletProvider[] = [ @@ -149,5 +161,10 @@ export const walletProvider: WalletProvider[] = [ networkType: 'solana', networks: solanaNetworksWithIcon, wallets: solanaWallets + }, + { + networkType: 'ton', + networks: tonNetworksWithIcon, + wallets: tonWallets } ]; diff --git a/src/config/chainInfos.ts b/src/config/chainInfos.ts index 1ed6faeb5..755d5acda 100644 --- a/src/config/chainInfos.ts +++ b/src/config/chainInfos.ts @@ -6,13 +6,23 @@ import { BridgeAppCurrency, CustomChainInfo, defaultBech32Config, + getTokensFromNetwork, + TON_ORAICHAIN_DENOM, + HMSTR_ORAICHAIN_DENOM, + TON_ALL_OSMOSIS_CONTRACT, + TON_OSMOSIS_CONTRACT, solChainId } from '@oraichain/oraidex-common'; +import HamsterIcon from 'assets/icons/hmstr.svg?react'; import BitcoinIcon from 'assets/icons/bitcoin.svg?react'; import OraiIcon from 'assets/icons/oraichain.svg?react'; import BTCIcon from 'assets/icons/btc-icon.svg?react'; import OraiLightIcon from 'assets/icons/oraichain_light.svg?react'; +import UsdtIcon from 'assets/icons/tether.svg?react'; +import UsdcIcon from 'assets/icons/usd_coin.svg?react'; +import TonIcon from 'assets/icons/ton.svg?react'; import flatten from 'lodash/flatten'; +import { TON_SCAN, TonChainId, TonNetwork } from 'context/ton-provider'; import { chainIconsInfos, tokensIconInfos, mapListWithIcon } from './iconInfos'; import { CWBitcoinFactoryDenom } from 'helper/constants'; @@ -20,6 +30,11 @@ import { CWBitcoinFactoryDenom } from 'helper/constants'; export const tokensIcon = tokensIconInfos; export const chainIcons = chainIconsInfos; +export const TON_ZERO_ADDRESS = 'EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c'; +export const USDT_TON_CONTRACT = 'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs'; +export const jUSDC_TON_CONTRACT = 'EQB-MPwrd1G6WKNkLz_VnV6WqBDd142KMQv-g1O-8QUA3728'; +export const HMSTR_TON_CONTRACT = 'EQAJ8uWd7EBqsmpSWaRdf_I-8R8-XHwh3gsNKhy-UrdrPcUo'; + const [otherChainTokens, oraichainTokens] = tokens; const OraiBTCToken: BridgeAppCurrency = { coinDenom: 'ORAIBTC', @@ -32,6 +47,137 @@ const OraiBTCToken: BridgeAppCurrency = { } }; +export type AlloyedPool = { + poolId: string; + alloyedToken: string; + sourceToken: string; +}; + +export const OsmosisAlloyedPools: AlloyedPool[] = [ + { + poolId: '2161', + alloyedToken: TON_ALL_OSMOSIS_CONTRACT, + sourceToken: TON_OSMOSIS_CONTRACT + } +]; + +export const OsmosisTokenDenom = { + allTon: TON_ALL_OSMOSIS_CONTRACT, + ton: TON_OSMOSIS_CONTRACT +}; + +export const OsmosisTokenList = [ + { + chainId: 'osmosis-1', + bridgeTo: [TonChainId], + coinDenom: 'TON.orai', + name: 'TON', + symbol: 'TON.orai', + Icon: TonIcon, + contractAddress: null, + denom: OsmosisTokenDenom.ton, + coinMinimalDenom: OsmosisTokenDenom.ton, + coinGeckoId: 'the-open-network', + decimal: 9, + coinDecimals: 9 + }, + { + chainId: 'osmosis-1', + bridgeTo: [TonChainId], + coinDenom: 'TON', + name: 'TON', + symbol: 'TON', + Icon: TonIcon, + contractAddress: null, + denom: OsmosisTokenDenom.allTon, + coinMinimalDenom: OsmosisTokenDenom.allTon, + coinGeckoId: 'the-open-network', + decimal: 9, + coinDecimals: 9, + alloyedToken: true + } +]; + +export const tonNetworkMainnet: CustomChainInfo = { + rest: 'https://toncenter.com/api/v2/jsonRPC', + rpc: 'https://toncenter.com/api/v2/jsonRPC', + chainId: TonChainId, + chainName: 'TON' as any, + bip44: { + coinType: 607 as any + }, + coinType: 607, + Icon: TonIcon, + IconLight: TonIcon, + stakeCurrency: { + coinDenom: 'TON', + coinMinimalDenom: 'ton', + coinDecimals: 9, + coinGeckoId: 'the-open-network', + coinImageUrl: 'https://assets.coingecko.com/coins/images/17980/standard/ton_symbol.png' + }, + bech32Config: defaultBech32Config('bc'), + networkType: 'ton' as any, + currencies: [ + { + coinDenom: 'TON', + coinMinimalDenom: 'ton', + coinDecimals: 9, + bridgeTo: ['Oraichain', 'osmosis-1'], + prefixToken: 'ton20_', + contractAddress: TON_ZERO_ADDRESS, + Icon: TonIcon, + coinGeckoId: 'the-open-network', + coinImageUrl: 'https://assets.coingecko.com/coins/images/17980/standard/ton_symbol.png', + gasPriceStep: { + low: 0, + average: 0, + high: 0 + } + }, + { + coinDenom: 'USDT', + coinMinimalDenom: 'ton20_usdt', + coinDecimals: 6, + Icon: UsdtIcon, + bridgeTo: ['Oraichain'], + contractAddress: USDT_TON_CONTRACT, + prefixToken: 'ton20_', + coinGeckoId: 'tether' + }, + { + coinDenom: 'HMSTR', + coinMinimalDenom: 'ton20_hamster_kombat', + coinDecimals: 9, + Icon: HamsterIcon, + bridgeTo: ['Oraichain'], + contractAddress: HMSTR_TON_CONTRACT, + prefixToken: 'ton20_', + coinGeckoId: 'hamster-kombat' + }, + { + coinDenom: 'jUSDC', + coinMinimalDenom: 'ton20_usdc', + coinDecimals: 6, + Icon: UsdcIcon, + bridgeTo: ['Oraichain'], + contractAddress: jUSDC_TON_CONTRACT, + prefixToken: 'ton20_', + coinGeckoId: 'usd-coin' + } + ], + get feeCurrencies() { + return this.currencies; + }, + // features: ['isBtc'], + + txExplorer: { + name: 'BlockStream', + txUrl: `${TON_SCAN}/transaction/{txHash}`, + accountUrl: `${TON_SCAN}/{address}` + } +}; + const oraibtcNetwork = { rpc: 'https://btc.rpc.orai.io', rest: 'https://btc.lcd.orai.io/', @@ -122,13 +268,9 @@ export const bitcoinMainnet: CustomChainInfo = { } }; -export const chainInfosWithIcon = mapListWithIcon( - [...customChainInfos, bitcoinMainnet, oraibtcNetwork], - chainIcons, - 'chainId' -); export const oraichainTokensWithIcon = mapListWithIcon(oraichainTokens, tokensIcon, 'coinGeckoId'); export const otherTokensWithIcon = mapListWithIcon(otherChainTokens, tokensIcon, 'coinGeckoId'); +export const tonNetworkTokens = getTokensFromNetwork(tonNetworkMainnet); export const tokensWithIcon = [otherTokensWithIcon, oraichainTokensWithIcon]; export const flattenTokensWithIcon = flatten(tokensWithIcon); @@ -207,7 +349,12 @@ export const OraiBTCBridgeNetwork = { } }; -export const chainInfosWithSdk = [...customChainInfos, bitcoinMainnet, oraibtcNetwork]; +export const chainInfosWithSdk = [ + ...customChainInfos, + bitcoinMainnet, + oraibtcNetwork + // tonNetworkMainnet +]; export const chainInfos = mapListWithIcon(chainInfosWithSdk, chainIcons, 'chainId'); // exclude kawaiverse subnet and other special evm that has different cointype @@ -215,4 +362,6 @@ export const evmChains = chainInfos.filter( (c) => c.networkType === 'evm' && c.bip44.coinType === 60 && c.chainId !== '0x1ae6' ); +export const chainInfosWithIcon = mapListWithIcon(chainInfosWithSdk, chainIcons, 'chainId'); + export const btcChains = chainInfos.filter((c) => c.networkType === 'bitcoin'); diff --git a/src/config/iconInfos.ts b/src/config/iconInfos.ts index 170106c3b..13b55c42c 100644 --- a/src/config/iconInfos.ts +++ b/src/config/iconInfos.ts @@ -30,8 +30,9 @@ import HmstrIcon from 'assets/icons/hmstr.svg?react'; import DogecoinIcon from 'assets/icons/dogecoin.svg?react'; import SolanaIcon from 'assets/icons/solana.svg?react'; import MaxIcon from 'assets/icons/max.svg?react'; -import { CustomChainInfo, TokenItemType } from '@oraichain/oraidex-common'; +import { CustomChainInfo, TokenItemType, solChainId } from '@oraichain/oraidex-common'; import { bitcoinChainId } from 'helper/constants'; +import { TonChainId } from 'context/ton-provider'; export type TokenIcon = Pick; export type ChainIcon = Pick; @@ -256,9 +257,14 @@ export const chainIconsInfos: ChainIcon[] = [ IconLight: CelestiaIcon }, { - chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' as any, + chainId: solChainId, Icon: SolanaIcon, IconLight: SolanaIcon + }, + { + chainId: TonChainId, + Icon: TonIcon, + IconLight: TonIcon } ]; diff --git a/src/config/networks.ts b/src/config/networks.ts index ba8d01b4e..e0af84e6e 100644 --- a/src/config/networks.ts +++ b/src/config/networks.ts @@ -15,8 +15,10 @@ import { CW20_STAKING_CONTRACT, AMM_V3_CONTRACT } from '@oraichain/oraidex-common'; +import { CW_TON_BRIDGE, TOKEN_FACTORY } from 'context/ton-provider'; -export const network: CustomChainInfo & NetworkConfig & { pool_v3: string; indexer_v3: string } = { +export const network: CustomChainInfo & + NetworkConfig & { pool_v3: string; indexer_v3: string; CW_TON_BRIDGE: string; TOKEN_FACTORY: string } = { ...oraichainNetwork, prefix: oraichainNetwork.bech32Config.bech32PrefixAccAddr, denom: 'orai', @@ -35,6 +37,7 @@ export const network: CustomChainInfo & NetworkConfig & { pool_v3: string; index multicall: MULTICALL_CONTRACT, explorer: 'https://scan.orai.io', pool_v3: AMM_V3_CONTRACT, + CW_TON_BRIDGE: CW_TON_BRIDGE, + TOKEN_FACTORY: TOKEN_FACTORY, indexer_v3: 'https://ammv3-indexer.oraidex.io/' }; - \ No newline at end of file diff --git a/src/context/ton-provider.tsx b/src/context/ton-provider.tsx new file mode 100644 index 000000000..493aaa3fb --- /dev/null +++ b/src/context/ton-provider.tsx @@ -0,0 +1,31 @@ +import { TonConnectUIProvider } from '@tonconnect/ui-react'; + +export const TonChainId: any = 'ton'; + +export enum TonNetwork { + Mainnet = 'mainnet', + Testnet = 'testnet' +} + +export const TonInteractionContract = { + [TonNetwork.Mainnet]: { + lightClient: 'EQDzy_POlimFDyzrHd3OQsb9sZCngyG3O7Za4GRFzM-rrO93', + whitelist: 'EQATDM6mfPZjPDMD9TVa6D9dlbmAKY5w6xOJiTXJ9Nqj_dsu', + bridgeAdapter: 'EQC-aFP0rJXwTgKZQJPbPfTSpBFc8wxOgKHWD9cPvOl_DnaY' + }, + [TonNetwork.Testnet]: { + lightClient: 'EQDzy_POlimFDyzrHd3OQsb9sZCngyG3O7Za4GRFzM-rrO93', + whitelist: 'EQAbJI3NZKGcVu-ec_z_LcmXca9ZOtzkgCW5H9glnWBDpaFg', + bridgeAdapter: 'EQA3ISho4fpW3wmCkKEwsyXulIw7vLf-2jxso40ul3QQJ_O7' + } +}; + +export const TON_SCAN = 'https://tonviewer.com'; +export const MANIFEST_URL = `${window.location?.origin}/manifest.json`; + +export const CW_TON_BRIDGE = 'orai159l8l9c5ckhqpuwdfgs9p4v599nqt3cjlfahalmtrhfuncnec2ms5mz60e'; +export const TOKEN_FACTORY = 'orai1wuvhex9xqs3r539mvc6mtm7n20fcj3qr2m0y9khx6n5vtlngfzes3k0rq9'; + +export const TonProvider = (props: React.PropsWithChildren<{}>) => { + return {props.children}; +}; diff --git a/src/helper/index.tsx b/src/helper/index.tsx index b73b9eef8..0ad4b66b7 100644 --- a/src/helper/index.tsx +++ b/src/helper/index.tsx @@ -13,7 +13,8 @@ import { EVM_CHAIN_ID_COMMON, SOL_SCAN, WalletType as WalletCosmosType, - solChainId + solChainId, + oraichainNetwork } from '@oraichain/oraidex-common'; import { network } from 'config/networks'; import { serializeError } from 'serialize-error'; @@ -31,8 +32,12 @@ import { MetamaskOfflineSigner } from 'libs/eip191'; import Keplr from 'libs/keplr'; import { WalletsByNetwork } from 'reducer/wallet'; import { evmChainInfos } from 'config/evmChainInfos'; +import { TonChainId } from 'context/ton-provider'; +import { toUserFriendlyAddress, useTonAddress } from '@tonconnect/ui-react'; import DefaultIcon from 'assets/icons/tokens.svg?react'; import { numberWithCommas } from './format'; +import { getHttpEndpoint } from '@orbs-network/ton-access'; +import { TonClient } from '@ton/ton'; export interface Tokens { denom?: string; @@ -74,12 +79,17 @@ export const btcNetworksWithIcon = chainInfosWithIcon.filter((c) => c.chainId == export const solanaNetworksWithIcon = chainInfosWithIcon.filter( (c) => c.chainId === 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' ); +export const tonNetworksWithIcon = chainInfosWithIcon.filter((c) => c.chainId === TonChainId); export const filterChainBridge = (token: Tokens, item: CustomChainInfo) => { const tokenCanBridgeTo = token.bridgeTo ?? ['Oraichain']; return tokenCanBridgeTo.includes(item.chainId); }; +export const findChainByChainId = (chainId: string) => { + return networks.find((n) => n.chainId === chainId) || oraichainNetwork; +}; + export const getDenomEvm = (): EvmDenom => { switch (Number(window.ethereumDapp?.chainId)) { case Networks.bsc: @@ -103,6 +113,10 @@ export const getSpecialCoingecko = (fromCoingecko: string, toCoingecko: string) }; export const getTransactionUrl = (chainId: NetworkChainId, transactionHash: string) => { + if (chainId === 'ton') { + return `https://tonscan.org/address/${transactionHash}`; + } + switch (Number(chainId)) { case Networks.bsc: return `${BSC_SCAN}/tx/${transactionHash}`; @@ -410,8 +424,11 @@ export const getAddressTransferForEvm = async (walletByNetworks: WalletsByNetwor export const getAddressTransfer = async (network: CustomChainInfo, walletByNetworks: WalletsByNetwork) => { try { let address = ''; - - if (network.networkType === 'evm') { + if (network.networkType === 'ton') { + address = JSON.parse(JSON.parse(localStorage.getItem('persist:root'))?.config)?.tonAddress; + // address = useTonAddress(); + // address = toUserFriendlyAddress(window.Ton?.account?.address); + } else if (network.networkType === 'evm') { address = await getAddressTransferForEvm(walletByNetworks, network); } else if (network.networkType == ('svm' as any)) { let provider = window?.solana; @@ -678,6 +695,40 @@ export const getIconToken = ({ isLightTheme, denom, width = 18, height = 18 }) = return ; }; +export const retryOrbs = async (fn, retryTimes = 30, delay = 2000) => { + try { + return await fn(); + } catch (error) { + let response = error?.response; + let message = response?.data?.error; + if (message?.includes('No working liteservers')) { + await sleep(delay * 2); + return await retryOrbs(fn, retryTimes, delay); + } + if (retryTimes > 0) { + await sleep(delay * 5); + return await retryOrbs(fn, retryTimes - 1, delay); + } + } +}; + +export const getTonClient = async () => { + try { + const endpoint = await getHttpEndpoint({ + network: 'mainnet' + }); + const client = new TonClient({ + endpoint + }); + return client; + } catch (err) { + return new TonClient({ + endpoint: + 'https://ton.access.orbs.network/55013c0ff5Bd3F8B62C092Ab4D238bEE463E5501/1/mainnet/toncenter-api-v2/jsonRPC' + }); + } +}; + export const handleErrorRateLimit = (errorMsg: string) => { if (!errorMsg) { return; diff --git a/src/hooks/useLoadTokens.ts b/src/hooks/useLoadTokens.ts index 5a9b4b3fc..59b76ab26 100644 --- a/src/hooks/useLoadTokens.ts +++ b/src/hooks/useLoadTokens.ts @@ -27,7 +27,7 @@ import { solChainId } from '@oraichain/oraidex-common'; import { UniversalSwapHelper } from '@oraichain/oraidex-universal-swap'; -import { chainInfos, evmChains } from 'config/chainInfos'; +import { chainInfos, evmChains, TON_ZERO_ADDRESS, tonNetworkMainnet } from 'config/chainInfos'; import { network } from 'config/networks'; import { ethers } from 'ethers'; import axios from 'rest/request'; @@ -35,6 +35,9 @@ import { reduce } from 'lodash'; import { getUtxos } from 'pages/Balance/helpers'; import { bitcoinChainId } from 'helper/constants'; import { clusterApiUrl, Connection, PublicKey } from '@solana/web3.js'; +import { getHttpEndpoint } from '@orbs-network/ton-access'; +import { Address, TonClient } from '@ton/ton'; +import { JettonMinter, JettonWallet } from '@oraichain/ton-bridge-contracts'; export type LoadTokenParams = { refresh?: boolean; @@ -43,6 +46,7 @@ export type LoadTokenParams = { tronAddress?: string; btcAddress?: string; solAddress?: string; + tonAddress?: string; }; async function loadNativeBalance(dispatch: Dispatch, address: string, tokenInfo: { chainId: string; rpc: string }) { @@ -75,7 +79,7 @@ const timer = {}; async function loadTokens( dispatch: Dispatch, - { oraiAddress, metamaskAddress, tronAddress, btcAddress, solAddress }: LoadTokenParams + { oraiAddress, metamaskAddress, tronAddress, btcAddress, solAddress, tonAddress }: LoadTokenParams ) { try { if (oraiAddress) { @@ -146,6 +150,13 @@ async function loadTokens( ); }, 2000); } + + if (tonAddress) { + clearTimeout(timer[tonAddress]); + timer[tonAddress] = setTimeout(() => { + loadAllBalanceTonToken(dispatch, tonAddress); + }, 2000); + } } catch (error) { console.log('error load balance: ', error); } @@ -407,6 +418,93 @@ async function loadKawaiiSubnetAmount(dispatch: Dispatch, kwtAddress: string) { dispatch(updateAmounts(amountDetails)); } +const loadBalanceByToken = async (dispatch: Dispatch, addressTon: string, addressToken?: string) => { + try { + // get the decentralized RPC endpoint + const endpoint = await getHttpEndpoint(); + const client = new TonClient({ + endpoint + }); + if (addressToken === TON_ZERO_ADDRESS) { + const balance = await client.getBalance(Address.parse(addressTon)); + + return { ton: balance || '0' }; + } + + const token = tonNetworkMainnet.currencies.find((e) => e.contractAddress === addressToken); + + const jettonMinter = JettonMinter.createFromAddress(Address.parse(addressToken)); + const jettonMinterContract = client.open(jettonMinter); + const jettonWalletAddress = await jettonMinterContract.getWalletAddress(Address.parse(addressTon)); + const jettonWallet = JettonWallet.createFromAddress(jettonWalletAddress); + const jettonWalletContract = client.open(jettonWallet); + const balance = await jettonWalletContract.getBalance(); + + dispatch(updateAmounts({ [token.coinMinimalDenom]: (balance.amount || '0').toString() })); + return { [token.coinMinimalDenom]: balance.amount || '0' }; + } catch (error) { + console.log('error load ton balance', error); + return {}; + } +}; + +const loadAllBalanceTonToken = async (dispatch: Dispatch, tonAddress: string, listToken?: string[]) => { + if (!tonAddress) return; + + const allTokens = !listToken?.length + ? tonNetworkMainnet.currencies + : tonNetworkMainnet.currencies.filter((e) => listToken.includes(e.contractAddress)); + + const endpoint = await getHttpEndpoint(); + const client = new TonClient({ + endpoint + }); + + const fullData = await Promise.all( + allTokens.map(async (item) => { + if (item.contractAddress === TON_ZERO_ADDRESS) { + // native token: TON + const balance = await client.getBalance(Address.parse(tonAddress)); + + return { + balance: balance, + jettonWalletAddress: '', + token: item + }; + } + const jettonMinter = JettonMinter.createFromAddress(Address.parse(item.contractAddress)); + + const jettonMinterContract = client.open(jettonMinter); + + const jettonWalletAddress = await jettonMinterContract.getWalletAddress(Address.parse(tonAddress)); + + const jettonWallet = JettonWallet.createFromAddress(jettonWalletAddress); + const jettonWalletContract = client.open(jettonWallet); + const balance = await jettonWalletContract.getBalance(); + + return { + balance: balance.amount, + jettonWalletAddress, + token: item + }; + }) + ); + + let amountDetail: AmountDetails = {}; + fullData?.map((data) => { + const token = tonNetworkMainnet.currencies.find((e) => e.contractAddress === data.token.contractAddress); + + amountDetail = { + ...amountDetail, + [token.coinMinimalDenom]: (data.balance || '0').toString() + }; + }); + + dispatch(updateAmounts(amountDetail)); + + return amountDetail; +}; + export default function useLoadTokens(): (params: LoadTokenParams) => Promise { const dispatch = useDispatch(); return loadTokens.bind(null, dispatch); diff --git a/src/hooks/useTokenFee.ts b/src/hooks/useTokenFee.ts index 95b1a3e18..d6e2400cd 100644 --- a/src/hooks/useTokenFee.ts +++ b/src/hooks/useTokenFee.ts @@ -85,6 +85,14 @@ export const useRelayerFeeToken = (originalFromToken: TokenItemType, originalToT () => { const routerClient = new OraiswapRouterQueryClient(window.client, network.router); const oraiToken = oraichainTokens.find((token) => token.coinGeckoId === 'oraichain-token'); + + if (!oraiToken || !originalToToken) { + return { + displayAmount: 0, + amount: '0' + }; + } + return UniversalSwapHelper.handleSimulateSwap({ originalFromInfo: oraiToken, originalToInfo: originalToToken, @@ -146,6 +154,12 @@ export const useUsdtToBtc = (amount) => { const { data } = useQuery( ['convert-btc-to-usdt', originalFromToken, originalToToken], () => { + if (!originalToToken || !originalFromToken) { + return { + displayAmount: 0, + amount: '0' + }; + } return UniversalSwapHelper.handleSimulateSwap({ originalFromInfo: originalToToken, originalToInfo: originalFromToken, diff --git a/src/hooks/useTonConnectAddress.ts b/src/hooks/useTonConnectAddress.ts new file mode 100644 index 000000000..4cc2103ac --- /dev/null +++ b/src/hooks/useTonConnectAddress.ts @@ -0,0 +1,70 @@ +import { useTonAddress, useTonConnectModal, useTonConnectUI, useTonWallet } from '@tonconnect/ui-react'; +import { useEffect } from 'react'; +import useConfigReducer from './useConfigReducer'; +import useWalletReducer from './useWalletReducer'; + +const useTonConnectAddress = () => { + const userFriendlyAddress = useTonAddress(); + const wallet = useTonWallet(); + const { open: openConnect } = useTonConnectModal(); + const [tonConnectUI] = useTonConnectUI(); + const [tonAddress, setTonAddress] = useConfigReducer('tonAddress'); + const [walletByNetworks, setWalletByNetworks] = useWalletReducer('walletsByNetwork'); + + useEffect(() => { + if (!(userFriendlyAddress && wallet)) { + setTonAddress(undefined); + setWalletByNetworks({ + ...walletByNetworks, + ton: null + }); + return; + } + + setTonAddress(userFriendlyAddress); + + setWalletByNetworks({ + ...walletByNetworks, + ton: 'ton' + }); + + // handleSetTonWallet({ + // tonWallet: + // wallet?.["appName"] || + // (wallet?.device?.appName?.toLowerCase() as TonWallet), + // }); + }, [userFriendlyAddress, wallet]); + + const handleDisconnectTon = async () => { + try { + if (tonConnectUI.connected) { + await tonConnectUI.disconnect(); + } + + if (tonAddress && walletByNetworks.ton) { + // && walletType === tonWallet + // handleSetTonAddress({ tonAddress: undefined }); + // handleSetTonWallet({ tonWallet: undefined }); + + setTonAddress(undefined); + } + } catch (error) { + console.log('error disconnect TON :>>', error); + } + }; + + const handleConnectTon = () => { + try { + openConnect(); + } catch (error) { + console.log('error', error); + } + }; + + return { + handleConnectTon, + handleDisconnectTon + }; +}; + +export default useTonConnectAddress; diff --git a/src/index.tsx b/src/index.tsx index e287ec025..36535e1de 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -19,11 +19,7 @@ import { persistor, store } from 'store/configure'; import './index.scss'; import App from './layouts/App'; import ScrollToTop from './layouts/ScrollToTop'; - -// const client = new Client({ -// url: 'http://10.10.20.72:3000/', -// exchanges: [cacheExchange, fetchExchange] -// }); +import { TonProvider } from 'context/ton-provider'; const queryClient = new QueryClient(); @@ -73,7 +69,9 @@ const initApp = async () => { - + + + diff --git a/src/layouts/App.tsx b/src/layouts/App.tsx index f64f38806..8238f60f4 100644 --- a/src/layouts/App.tsx +++ b/src/layouts/App.tsx @@ -30,15 +30,20 @@ import SingletonOraiswapV3 from 'libs/contractSingleton'; import { getCosmWasmClient } from 'libs/cosmjs'; import { SolanaWalletProvider } from 'context/solana-content'; import { useWallet } from '@solana/wallet-adapter-react'; +import { useTonConnectUI } from '@tonconnect/ui-react'; +import { useLoadWalletsTon } from 'pages/Balance/hooks/useLoadWalletsTon'; +import { TonNetwork } from 'context/ton-provider'; const App = () => { const [address, setOraiAddress] = useConfigReducer('address'); const [, setTronAddress] = useConfigReducer('tronAddress'); + const [tonAddress] = useConfigReducer('tonAddress'); const [, setMetamaskAddress] = useConfigReducer('metamaskAddress'); const [, setBtcAddress] = useConfigReducer('btcAddress'); const [, setSolAddress] = useConfigReducer('solAddress'); const [, setStatusChangeAccount] = useConfigReducer('statusChangeAccount'); const loadTokenAmounts = useLoadTokens(); + const [tonConnectUI] = useTonConnectUI(); const [persistVersion, setPersistVersion] = useConfigReducer('persistVersion'); const [theme] = useConfigReducer('theme'); const [walletByNetworks] = useWalletReducer('walletsByNetwork'); @@ -52,6 +57,15 @@ const App = () => { useTronEventListener(); + // init TON + useEffect(() => { + window.Ton = tonConnectUI; + }, [tonConnectUI]); + + useLoadWalletsTon({ + tonNetwork: TonNetwork.Mainnet + }); + useEffect(() => { (async () => { if (address) { @@ -289,6 +303,7 @@ const App = () => { return tronAddress; }; + // TODO: owallet not support TON. need to update in next time const keplrHandler = async () => { try { polyfillForMobileMode(); @@ -303,7 +318,8 @@ const App = () => { metamaskAddress, tronAddress, btcAddress, - solAddress + solAddress, + tonAddress }); } catch (error) { console.log('Error: ', error.message); diff --git a/src/libs/contractSingleton.ts b/src/libs/contractSingleton.ts index 9d7b5c56f..62e4f2b70 100644 --- a/src/libs/contractSingleton.ts +++ b/src/libs/contractSingleton.ts @@ -760,7 +760,7 @@ export function simulateIncentiveAprPosition( // calculate APR for the best, position liquidity is 10% of total liquidity let sumMaxIncentivesApr = 0; - const positionLiquidity = Number(pool.liquidity); + const positionLiquidity = 10 ** 24; // const totalPositionLiquidity = (totalLiquidity * 0.5) / 100; const tick_spacing = poolKey.fee_tier.tick_spacing; @@ -792,7 +792,7 @@ export function simulateIncentiveAprPosition( // calculate APR for the worst, position liquidity is 2% of total liquidity let sumMinIncentivesApr = 0; - const positionLiquidity2 = Number(pool.liquidity); + const positionLiquidity2 = 10 ** 24; const res2 = calculateAmountDelta( pool.current_tick_index, @@ -898,11 +898,15 @@ export async function fetchPoolAprInfo( min: res.min + (minSwapApr ? minSwapApr : 0), max: res.max + (maxSwapApr ? maxSwapApr : 0) }, - incentives: pool.incentives.map((incentive) => { - if (incentive.remaining === '0') return null; - const token = oraichainTokens.find((token) => extractAddress(token) === parseAssetInfo(incentive.reward_token)); - return token.denom.toUpperCase(); - }).filter((incentive) => incentive !== null), + incentives: pool.incentives + .map((incentive) => { + if (incentive.remaining === '0') return null; + const token = oraichainTokens.find( + (token) => extractAddress(token) === parseAssetInfo(incentive.reward_token) + ); + return token.denom.toUpperCase(); + }) + .filter((incentive) => incentive !== null), swapFee: { min: minSwapApr, max: maxSwapApr diff --git a/src/libs/duckdb.ts b/src/libs/duckdb.ts index 21a248dbc..a3f5a3d8c 100644 --- a/src/libs/duckdb.ts +++ b/src/libs/duckdb.ts @@ -1,7 +1,24 @@ import { NetworkChainId } from '@oraichain/oraidex-common'; import * as duckdb from '@duckdb/duckdb-wasm'; +import duckdb_wasm from '@duckdb/duckdb-wasm/dist/duckdb-mvp.wasm?url'; +import mvp_worker from '@duckdb/duckdb-wasm/dist/duckdb-browser-mvp.worker.js?url'; +import duckdb_wasm_next from '@duckdb/duckdb-wasm/dist/duckdb-eh.wasm?url'; +import eh_worker from '@duckdb/duckdb-wasm/dist/duckdb-browser-eh.worker.js?url'; import { get, set } from 'idb-keyval'; -import eh_worker from '@duckdb/duckdb-wasm/dist/duckdb-eh.wasm?url'; + +const MANUAL_BUNDLES = { + mvp: { + mainModule: duckdb_wasm, + mainWorker: mvp_worker + }, + eh: { + mainModule: duckdb_wasm_next, + // mainWorker: new URL('@duckdb/duckdb-wasm/dist/duckdb-browser-eh.worker.js', import.meta.url).toString(), + mainWorker: eh_worker + } +}; +// Select a bundle based on browser checks +const bundle = await duckdb.selectBundle(MANUAL_BUNDLES); export type TransactionHistory = { initialTxHash: string; @@ -48,6 +65,9 @@ const decompress = async (buf: Uint8Array) => { } }; +const worker = new Worker(bundle.mainWorker); +const logger = import.meta.env.NODE_ENV === 'development' ? new duckdb.ConsoleLogger() : new duckdb.VoidLogger(); + export class DuckDb { static instance: DuckDb; diff --git a/src/pages/Balance/Balance.module.scss b/src/pages/Balance/Balance.module.scss index 642f89073..a15f7ad3f 100644 --- a/src/pages/Balance/Balance.module.scss +++ b/src/pages/Balance/Balance.module.scss @@ -94,10 +94,8 @@ width: 100%; } - background: #2b2d3b; - - &.light { - background-color: $light-bg-primary-box; + @include theme { + background: theme-get('neutral-surface-bg-btn-disabled'); } text-align: center; @@ -131,7 +129,7 @@ &_form { width: 420px; - background: #2b2d3b; + background: #232521; border-radius: 8px; padding: 12px; height: 56px; @@ -173,9 +171,9 @@ flex-direction: row; .tfBtn { + background-color: $primary-bg-btn; @include theme() { - background-color: theme-get('bg-btn-primary'); - box-shadow: theme-get('shadow-btn'); + color: theme-get('neutral-text-text-btn-default'); } border-radius: 8px; @@ -273,11 +271,11 @@ &_element { margin-bottom: 20px; - background-color: #191b21; + background-color: #232521; border-radius: 16px; &.light { - background-color: $light-bg-primary-box; + background-color: #f7f7f7; } } } @@ -285,11 +283,11 @@ .balances { display: flex; justify-content: center; - padding-top: 12px; + padding: 12px 8px 0 8px; width: 100%; - .light { - color: #a871df; + @include theme { + color: theme-get('colors-neutral-text-text-token-name'); } .box { @@ -320,7 +318,16 @@ } :hover { - color: #a871df; + text-decoration: underline; + filter: brightness(1.5); + } + + svg { + path { + @include theme { + fill: theme-get('primary-surface-default-dark-3'); + } + } } } } diff --git a/src/pages/Balance/StuckOraib/style.module.scss b/src/pages/Balance/StuckOraib/style.module.scss index 6afd0a774..d76284e25 100644 --- a/src/pages/Balance/StuckOraib/style.module.scss +++ b/src/pages/Balance/StuckOraib/style.module.scss @@ -1,56 +1,56 @@ @import 'src/styles/mixins'; -.icon{ - margin-right: 20px; +.icon { + margin-right: 20px; } .bridgeBalances { - padding: 9px 12px; - border-radius: 8px; - background-color: #2B2D3B; - display: flex; - align-items: center; - justify-content: space-between; - border: 1px solid transparent; - position: sticky; - top: 0; - float: right; - &:hover { - border-color: #A871DF; - } - - .stuckText { - margin: 0 10px; - cursor: pointer; + padding: 9px 12px; + border-radius: 8px; + background-color: #232521; + display: flex; + align-items: center; + justify-content: space-between; + border: 1px solid transparent; + position: sticky; + top: 0; + float: right; + + &:hover { + border-color: #a871df; + } + + .stuckText { + margin: 0 10px; + cursor: pointer; + } + + .loading { + @include desktop { + width: 50%; + height: 180px; } - - .loading { - @include desktop { - width: 50%; - height: 180px; - } - - @include flex(); - } + @include flex(); + } } .stuckToken { - display: flex; - justify-content: space-between; - align-items: center; - padding: 7px 10px; + display: flex; + justify-content: space-between; + align-items: center; + padding: 7px 10px; - &:hover { - background-color: #2B2D3B; - } + &:hover { + background-color: #232521; + } - &>div { - display: flex; - align-items: center; - } + & > div { + display: flex; + align-items: center; + } - .name { - margin-left: 10px; - } -} \ No newline at end of file + .name { + margin-left: 10px; + } +} diff --git a/src/pages/Balance/TokenItem/index.module.scss b/src/pages/Balance/TokenItem/index.module.scss index da2da0dbb..4a0523237 100644 --- a/src/pages/Balance/TokenItem/index.module.scss +++ b/src/pages/Balance/TokenItem/index.module.scss @@ -70,7 +70,9 @@ font-size: 14px; line-height: 150%; text-align: center; - color: #777e90; + @include theme() { + color: theme-get('neutral-8'); + } align-self: flex-start; margin: 8px 8px 8px 0; } @@ -87,13 +89,13 @@ } .tokenWrapper:hover { - background: #2b2d3b; + background: #232521a5; box-sizing: border-box; } .tokenWrapper.light:hover { background-color: #fcfcfc; - border: 1px solid $blue-color; + border: 1px solid $primary-surface-default-light; } .tokenWrapper { @@ -127,11 +129,11 @@ } &.active { - background: #191b21; + background: #131412; &.light { - background-color: #fcfcfc; + background-color: #f7f7f7; } - border: 1px solid #a871df; + border: 1px solid #2f5711; box-sizing: border-box; border-radius: 12px; @@ -187,7 +189,10 @@ font-weight: 600; font-size: 16px; line-height: 100%; - color: #777e90; + + @include theme { + color: theme-get('neutral-8'); + } &.token { align-self: flex-end; @@ -208,24 +213,39 @@ .balanceBtn { cursor: pointer; - background: #191b21; border-radius: 8px; padding: 8px; margin-left: 4px; margin-right: 4px; - color: #a871df; + + @include theme { + border: 1px solid transparent; + background: theme-get('primary-surface-default-subtle'); + color: theme-get('primary-surface-default-dark-3'); + } + + &:hover { + @include theme { + border: 1px solid theme-get('primary-surface-default-dark-3'); + } + } } } .amount { text-align: right; - background: #1e1e21; border-radius: 8px; height: 48px; width: 180px; padding: 12px; box-sizing: border-box; - color: #505665; + // background: #1e1e21; + // color: #505665; + + @include theme { + background: theme-get('neutral-surface-bg-box'); + color: theme-get('colors-neutral-text-text-token-name'); + } font-weight: 600; font-size: 24px; diff --git a/src/pages/Balance/TransferConvertToken/index.module.scss b/src/pages/Balance/TransferConvertToken/index.module.scss index 190bc933e..7623d2375 100644 --- a/src/pages/Balance/TransferConvertToken/index.module.scss +++ b/src/pages/Balance/TransferConvertToken/index.module.scss @@ -17,12 +17,11 @@ margin-top: 20px; .tfBtn { + background-color: $primary-bg-btn; @include theme() { - background-color: theme-get('bg-btn-primary'); - box-shadow: theme-get('shadow-btn'); + color: theme-get('neutral-text-text-btn-default'); } - color: $white-color; border-radius: 8px; padding: 16px; @include flex(); @@ -58,7 +57,9 @@ font-size: 14px; line-height: 150%; text-align: center; - color: #777e90; + @include theme() { + color: theme-get('neutral-8'); + } align-self: flex-start; margin: 8px 8px 8px 0; } @@ -96,7 +97,10 @@ font-weight: 600; font-size: 16px; line-height: 100%; - color: #777e90; + + @include theme { + color: theme-get('neutral-8'); + } &.token { align-self: flex-end; @@ -120,8 +124,19 @@ } .balanceBtn { + @include theme { + border: 1px solid transparent; + background: theme-get('primary-surface-default-subtle'); + color: theme-get('primary-surface-default-dark-3'); + } + + &:hover { + @include theme { + border: 1px solid theme-get('primary-surface-default-dark-3'); + } + } + cursor: pointer; - background: #191b21; border-radius: 8px; width: 66px; padding: 10px; @@ -137,26 +152,22 @@ margin-left: 8px; padding: 6px; } - - color: #a871df; } } .amount { text-align: left; - background: #2b2d3b; border-radius: 8px; height: 48px; width: 180px; padding: 12px; box-sizing: border-box; - color: #ebebeb; font-weight: 600; font-size: 24px; - &.light { - background: #efefef; - color: $light-text-color; + @include theme { + background: theme-get('neutral-surface-bg-btn-disabled'); + color: theme-get('colors-neutral-text-text-token-name'); } @include mobile { @@ -188,14 +199,21 @@ } .balanceBtn { - background: #2e283d; - font-weight: 500; - border-radius: 8px; + @include theme { + border: 1px solid transparent; + background: theme-get('primary-surface-default-subtle'); + color: theme-get('primary-surface-default-dark-3'); + } - &.light { - background-color: #efe6f8; + &:hover { + @include theme { + border: 1px solid theme-get('primary-surface-default-dark-3'); + } } + font-weight: 500; + border-radius: 8px; + @include desktop { width: 66px; font-size: 16px; @@ -207,7 +225,6 @@ } height: 48px; - color: #a871df; text-align: center; } @@ -218,8 +235,9 @@ display: flex; gap: 10px; - &.light { - background-color: $blue-color; + background-color: $primary-bg-btn; + @include theme() { + color: theme-get('neutral-text-text-btn-default'); } } } @@ -270,12 +288,15 @@ .address { margin-left: 10px; border-radius: 8px; - border: 1px solid #777e90; padding: 2px 8px; - color: #777e90; font-size: 13px; display: flex; justify-content: center; + + @include theme { + color: theme-get('neutral-8'); + border: 1px solid theme-get('neutral-8'); + } } } } @@ -286,13 +307,13 @@ cursor: pointer; &_filter { - background: #2b2d3b; text-align: center; border-radius: 8px; padding: 12px; - &.light { - background: #efefef; + @include theme { + background: theme-get('neutral-surface-bg-btn-disabled'); + color: theme-get('neutral-8'); } } @@ -356,8 +377,8 @@ .items { position: absolute; width: 100%; - background: #2b2d3b; - border: 1.5px solid #5a5270; + background: #232521; + box-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.1); z-index: 1; &_chain { @@ -375,13 +396,19 @@ li { padding: 12px 24px 12px 24px; + border-radius: 8px; } li:hover { - background-color: #703cde; + @include theme { + background-color: theme-get('neutral-6'); + } + filter: brightness(1.25); div { - color: $white-color; + @include theme { + color: theme-get('neutral-8'); + } } } diff --git a/src/pages/Balance/TransferConvertToken/index.tsx b/src/pages/Balance/TransferConvertToken/index.tsx index c3f124f3e..20910928e 100644 --- a/src/pages/Balance/TransferConvertToken/index.tsx +++ b/src/pages/Balance/TransferConvertToken/index.tsx @@ -1,5 +1,6 @@ import { BigDecimal, + getTokensFromNetwork, // flattenTokens NetworkChainId, toDisplay, @@ -17,9 +18,9 @@ import PowerByOBridge from 'components/PowerByOBridge'; import { displayToast, TToastType } from 'components/Toasts/Toast'; import TokenBalance from 'components/TokenBalance'; import { cosmosTokens, flattenTokens, tokenMap } from 'config/bridgeTokens'; -import { btcChains, evmChains } from 'config/chainInfos'; +import { btcChains, evmChains, OsmosisTokenList, tonNetworkMainnet } from 'config/chainInfos'; import copy from 'copy-to-clipboard'; -import { filterChainBridge, getAddressTransfer, networks } from 'helper'; +import { filterChainBridge, findChainByChainId, getAddressTransfer, networks } from 'helper'; import { useCoinGeckoPrices } from 'hooks/useCoingecko'; import useConfigReducer from 'hooks/useConfigReducer'; import useTokenFee, { useRelayerFeeToken } from 'hooks/useTokenFee'; @@ -39,6 +40,9 @@ import styles from './index.module.scss'; import { useGetContractConfig } from 'pages/BitcoinDashboardV2/hooks'; import ToggleSwitch from 'components/ToggleSwitch'; import { CWBitcoinFactoryDenom } from 'helper/constants'; +import useGetFee from '../hooks/useGetFee'; +import useTonBridgeHandler, { EXTERNAL_MESSAGE_FEE } from '../hooks/useTonBridgeHandler'; +import { TonChainId } from 'context/ton-provider'; interface TransferConvertProps { token: TokenItemType; @@ -48,6 +52,7 @@ interface TransferConvertProps { subAmounts?: object; isFastMode?: boolean; setIsFastMode?: Function; + setToNetwork: Function; } const TransferConvertToken: FC = ({ @@ -57,9 +62,14 @@ const TransferConvertToken: FC = ({ onClickTransfer, subAmounts, isFastMode, - setIsFastMode + setIsFastMode, + setToNetwork }) => { - const bridgeNetworks = networks.filter((item) => filterChainBridge(token, item)); + // const bridgeNetworks = networks.filter((item) => filterChainBridge(token, item)); + const bridgeNetworks = [...(token?.bridgeTo || ['Oraichain'])].map((chainId) => { + const net = findChainByChainId(chainId); + return net; + }); const [[convertAmount, convertUsd], setConvertAmount] = useState([undefined, 0]); const [transferLoading, setTransferLoading] = useState(false); const [copied, setCopied] = useState(false); @@ -84,6 +94,7 @@ const TransferConvertToken: FC = ({ const address = await getAddressTransfer(findNetwork, walletByNetworks); setAddressTransfer(address); setToNetworkChainId(defaultToChainId); + setToNetwork(defaultToChainId); } })(); }, [token.chainId]); @@ -150,8 +161,20 @@ const TransferConvertToken: FC = ({ const remoteTokenDenomTo = getRemoteTokenDenom(to); // token fee - const fromTokenFee = useTokenFee(remoteTokenDenomFrom); - const toTokenFee = useTokenFee(remoteTokenDenomTo); + const fromTokenFee = useTokenFee(remoteTokenDenomFrom) || 0; + const toTokenFee = useTokenFee(remoteTokenDenomTo) || 0; + + const { bridgeFee: bridgeFeeTon, tokenFee: tonTokenFee } = useGetFee({ + token, + fromNetwork: token.chainId, + toNetwork: toNetworkChainId + }); + + const { deductNativeAmount, checkBalanceBridgeByNetwork } = useTonBridgeHandler({ + token, + fromNetwork: token.chainId, + toNetwork: toNetworkChainId + }); // bridge fee & relayer fee let bridgeFee = fromTokenFee + toTokenFee; @@ -197,12 +220,49 @@ const TransferConvertToken: FC = ({ toDisplayBTCFee = new BigDecimal(withdrawalFeeBtc.withdrawal_fees ?? 0).div(1e14).toNumber(); } - let receivedAmount = convertAmount ? convertAmount * (1 - bridgeFee / 100) - relayerFeeTokenFee - toDisplayBTCFee : 0; + let receivedAmount = convertAmount + ? convertAmount * (1 - bridgeFee / 100) - relayerFeeTokenFee - toDisplayBTCFee - (bridgeFeeTon || 0) + : 0; const renderBridgeFee = () => { + const [balanceMax, setBalanceMax] = useState(0); + + useEffect(() => { + (async () => { + if (toNetworkChainId === TonChainId) { + const tokenOnTon = [...getTokensFromNetwork(tonNetworkMainnet)].find( + (tk) => tk.chainId === toNetworkChainId && tk.coinGeckoId === token.coinGeckoId + ); + + const maxBalance = await checkBalanceBridgeByNetwork(token.chainId, tokenOnTon); + setBalanceMax(maxBalance || 0); + } + })(); + }, [token, toNetworkChainId]); + return (
- Bridge fee: {bridgeFee}% + {bridgeFeeTon ? ( + <> + Bridge fee:{' '} + + {bridgeFeeTon} {token.name}{' '} + + + ) : ( + <> + Bridge fee: {bridgeFee}% + + )} + {tonTokenFee > 0 ? ( +
+ - Token fee:{' '} + + {' '} + {tonTokenFee} {token.name}{' '} + +
+ ) : null}{' '} {relayerFeeTokenFee > 0 ? (
- Relayer fee:{' '} @@ -215,7 +275,7 @@ const TransferConvertToken: FC = ({ - Received amount: {' '} - {receivedAmount.toFixed(6)} {token.name} + {(receivedAmount > 0 ? receivedAmount : 0).toFixed(6)} {token.name} {!!toDisplayBTCFee && ( <> @@ -223,6 +283,14 @@ const TransferConvertToken: FC = ({ - BTC fee: {toDisplayBTCFee} BTC )} + {toNetworkChainId === TonChainId && ( +

+ Available amount:{' '} + + {balanceMax.toFixed(6)} {token.name} + +

+ )}
); }; @@ -314,31 +382,31 @@ const TransferConvertToken: FC = ({ {isOpen && (
    - {networks - .filter((item) => filterChainBridge(token, item)) - .map((net) => { - return ( -
  • { - e.stopPropagation(); - const address = await getAddressTransfer(net, walletByNetworks); - setAddressTransfer(address); - setToNetworkChainId(net.chainId); - setIsOpen(false); - }} - > - {net && ( -
    -
    - -
    -
    {net.chainName}
    + {[...(token?.bridgeTo || ['Oraichain'])].map((chainId) => { + const net = findChainByChainId(chainId); + return ( +
  • { + e.stopPropagation(); + const address = await getAddressTransfer(net, walletByNetworks); + setAddressTransfer(address); + setToNetworkChainId(net.chainId); + setToNetwork(net.chainId); + setIsOpen(false); + }} + > + {net && ( +
    +
    +
    - )} -
  • - ); - })} +
    {net.chainName}
    +
+ )} + + ); + })}
)} @@ -377,13 +445,22 @@ const TransferConvertToken: FC = ({ className={classNames(styles.balanceBtn, styles[theme])} onClick={(event) => { event.stopPropagation(); - const finalAmount = calcMaxAmount({ - maxAmount, - token, - coeff - }); + if (token.chainId === TonChainId && token.coinGeckoId === 'the-open-network') { + const finalAmount = new BigDecimal(maxAmount) + .sub(toDisplay(deductNativeAmount || 0n, token.decimals)) + .sub(deductNativeAmount > 0n ? EXTERNAL_MESSAGE_FEE : 0) + .toNumber(); + + setConvertAmount([finalAmount * coeff, amountDetail.usd * coeff]); + } else { + const finalAmount = calcMaxAmount({ + maxAmount, + token, + coeff + }); - setConvertAmount([finalAmount * coeff, amountDetail.usd * coeff]); + setConvertAmount([finalAmount * coeff, amountDetail.usd * coeff]); + } }} > {text} diff --git a/src/pages/Balance/helpers.ts b/src/pages/Balance/helpers.ts index 9df8654de..c79ef1e28 100644 --- a/src/pages/Balance/helpers.ts +++ b/src/pages/Balance/helpers.ts @@ -386,9 +386,17 @@ export const transferIbcCustom = async ( export const findDefaultToToken = (from: TokenItemType) => { if (!from.bridgeTo) return; - return flattenTokens.find( - (t) => from.bridgeTo.includes(t.chainId) && from.name.includes(t.name) && from.chainId !== t.chainId - ); + + const defaultToken = flattenTokens.find((t) => { + const defaultChain = from.bridgeTo[0]; + return defaultChain === t.chainId && from.coinGeckoId === t.coinGeckoId && from.chainId !== t.chainId; + }); + + return defaultToken; + + // return flattenTokens.find( + // (t) => from.bridgeTo.includes(t.chainId) && from.name.includes(t.name) && from.chainId !== t.chainId + // ); }; export const convertKwt = async (transferAmount: number, fromToken: TokenItemType): Promise => { @@ -492,9 +500,9 @@ export const calcMaxAmount = ({ if (!token) return maxAmount; let finalAmount = maxAmount; + if (token.chainId === 'ton') return finalAmount; const feeCurrencyOfToken = token.feeCurrencies?.find((e) => e.coinMinimalDenom === token.denom); - if (feeCurrencyOfToken) { const useFeeEstimate = feeEstimate(token, gas); diff --git a/src/pages/Balance/hooks/useGetFee.ts b/src/pages/Balance/hooks/useGetFee.ts new file mode 100644 index 000000000..807538d2d --- /dev/null +++ b/src/pages/Balance/hooks/useGetFee.ts @@ -0,0 +1,99 @@ +import { BigDecimal, toDisplay, TokenItemType } from '@oraichain/oraidex-common'; +import { TonbridgeBridgeClient } from '@oraichain/tonbridge-contracts-sdk'; +import { tonNetworkMainnet } from 'config/chainInfos'; +import { network } from 'config/networks'; +import { TonChainId } from 'context/ton-provider'; +import useConfigReducer from 'hooks/useConfigReducer'; +import { useEffect, useState } from 'react'; + +const useGetFee = ({ + token, + fromNetwork, + toNetwork +}: { + token: TokenItemType; + fromNetwork: string; + toNetwork: string; +}) => { + const [oraiAddress] = useConfigReducer('address'); + const [bridgeFee, setBridgeFee] = useState(0); + const [tokenFee, setTokenFee] = useState(0); + const [walletsTon] = useConfigReducer('walletsTon'); + + useEffect(() => { + (async () => { + try { + if (![fromNetwork, toNetwork].includes(TonChainId)) { + return setTokenFee(0); + } + + if (token && fromNetwork && toNetwork) { + const tokenInTon = tonNetworkMainnet.currencies.find((tk) => tk.coinGeckoId === token.coinGeckoId); + if (!tokenInTon) { + return; + } + const walletTon = walletsTon[tokenInTon.coinMinimalDenom]; + + if (!walletTon) { + return; + } + + const tonBridgeClient = new TonbridgeBridgeClient(window.client, oraiAddress, network.CW_TON_BRIDGE); + + const tokenFeeConfig = await tonBridgeClient.tokenFee({ + remoteTokenDenom: walletTon + }); + + if (tokenFeeConfig) { + const { nominator, denominator } = tokenFeeConfig; + const fee = new BigDecimal(nominator).div(denominator).toNumber(); + + setTokenFee(fee); + } + } + } catch (error) { + if (error.message.toString().includes('type: tonbridge_bridge::state::Ratio; key:')) { + setTokenFee(0); + } else { + console.log(error); + } + } + })(); + }, [token, oraiAddress, walletsTon, fromNetwork, toNetwork]); + + useEffect(() => { + (async () => { + if (![fromNetwork, toNetwork].includes(TonChainId)) { + return setBridgeFee(0); + } + + if (token && fromNetwork && toNetwork) { + const tokenInTon = tonNetworkMainnet.currencies.find((tk) => tk.coinGeckoId === token.coinGeckoId); + if (!tokenInTon) { + return; + } + + const walletTon = walletsTon[tokenInTon.coinMinimalDenom]; + if (!walletTon) { + return; + } + + const tonBridgeClient = new TonbridgeBridgeClient(window.client, oraiAddress, network.CW_TON_BRIDGE); + + const config = await tonBridgeClient.pairMapping({ + key: walletTon + }); + const pairMapping = config.pair_mapping; + + setBridgeFee(parseInt(pairMapping.relayer_fee) / 10 ** pairMapping.remote_decimals); + } + })(); + }, [token, oraiAddress, walletsTon, fromNetwork, toNetwork]); + + return { + bridgeFee, + tokenFee + }; +}; + +export default useGetFee; diff --git a/src/pages/Balance/hooks/useGetStateData.ts b/src/pages/Balance/hooks/useGetStateData.ts new file mode 100644 index 000000000..f0b3b3186 --- /dev/null +++ b/src/pages/Balance/hooks/useGetStateData.ts @@ -0,0 +1,27 @@ +import { TonbridgeBridgeClient } from '@oraichain/tonbridge-contracts-sdk'; +import { network } from 'config/networks'; +import useConfigReducer from 'hooks/useConfigReducer'; +import { useEffect, useState } from 'react'; + +const useGetStateData = () => { + const [oraiAddress] = useConfigReducer('address'); + const [balances, setBalances] = useState([]); + + const getChanelStateData = async () => { + const tonBridgeClient = new TonbridgeBridgeClient(window.client, oraiAddress, network.CW_TON_BRIDGE); + + const config = await tonBridgeClient.channelStateData(); + if (config) { + const { balances } = config; + setBalances(balances); + } + }; + + useEffect(() => { + getChanelStateData(); + }, []); + + return { balances, getChanelStateData }; +}; + +export default useGetStateData; diff --git a/src/pages/Balance/hooks/useLoadWalletsTon.ts b/src/pages/Balance/hooks/useLoadWalletsTon.ts new file mode 100644 index 000000000..23b24a33e --- /dev/null +++ b/src/pages/Balance/hooks/useLoadWalletsTon.ts @@ -0,0 +1,50 @@ +import { JettonMinter } from '@oraichain/ton-bridge-contracts'; +import { Address } from '@ton/ton'; +import { TON_ZERO_ADDRESS, tonNetworkMainnet } from 'config/chainInfos'; +import { TonInteractionContract, TonNetwork } from 'context/ton-provider'; +import useConfigReducer from 'hooks/useConfigReducer'; +import { useEffect } from 'react'; +import { getTonClient, retryOrbs } from './../../../helper/index'; + +// dev: use to load wallet jetton address of bridge adapter +export const useLoadWalletsTon = ({ tonNetwork = TonNetwork.Mainnet }: { tonNetwork?: TonNetwork }) => { + const [, handleSetWalletsTonCache] = useConfigReducer('walletsTon'); + + const loadWalletsTon = async () => { + let tokenOnTons = tonNetworkMainnet.currencies || []; + + let walletsTon = {}; + for (const tokenOnTon of tokenOnTons) { + if (tokenOnTon.contractAddress == TON_ZERO_ADDRESS) { + walletsTon = { + ...walletsTon, + [tokenOnTon.coinMinimalDenom]: TON_ZERO_ADDRESS + }; + continue; + } + + await retryOrbs(async () => { + const client = await getTonClient(); + + const jettonMinter = JettonMinter.createFromAddress(Address.parse(tokenOnTon.contractAddress)); + const jettonMinterContract = client.open(jettonMinter); + const jettonWalletAddress = await jettonMinterContract.getWalletAddress( + Address.parse(TonInteractionContract[tonNetwork].bridgeAdapter) + ); + walletsTon = { + ...walletsTon, + [tokenOnTon.coinMinimalDenom]: jettonWalletAddress.toString() + }; + }); + } + handleSetWalletsTonCache(walletsTon); + }; + + useEffect(() => { + loadWalletsTon(); + }, [tonNetwork]); + + return { + loadWalletsTon + }; +}; diff --git a/src/pages/Balance/hooks/useTonBridgeHandler.ts b/src/pages/Balance/hooks/useTonBridgeHandler.ts new file mode 100644 index 000000000..941c1823a --- /dev/null +++ b/src/pages/Balance/hooks/useTonBridgeHandler.ts @@ -0,0 +1,735 @@ +import { ExecuteInstruction, toBinary } from '@cosmjs/cosmwasm-stargate'; +import { fromBech32 } from '@cosmjs/encoding'; +import { coin, Coin, coins, GasPrice } from '@cosmjs/stargate'; +import { + BigDecimal, + calculateTimeoutTimestamp, + CosmosChainId, + cosmosChains, + CW20_DECIMALS, + getCosmosGasPrice, + getEncodedExecuteContractMsgs, + handleSentFunds, + IBC_WASM_CONTRACT, + OSMOSIS_ROUTER_CONTRACT, + toAmount, + toDisplay, + TokenItemType +} from '@oraichain/oraidex-common'; +import { buildUniversalSwapMemo, SwapAndAction, UniversalSwapHelper } from '@oraichain/oraidex-universal-swap'; +import { BridgeAdapter, JettonMinter, JettonWallet } from '@oraichain/ton-bridge-contracts'; +import { TonbridgeBridgeClient } from '@oraichain/tonbridge-contracts-sdk'; +import { getHttpEndpoint } from '@orbs-network/ton-access'; +import { Address, beginCell, Cell, toNano } from '@ton/core'; +import { TonClient } from '@ton/ton'; +import { Base64 } from '@tonconnect/protocol'; +import { useTonConnectUI } from '@tonconnect/ui-react'; +import { displayToast, TToastType } from 'components/Toasts/Toast'; +import { + AlloyedPool, + chainInfos, + oraichainTokensWithIcon, + OsmosisAlloyedPools, + OsmosisTokenDenom, + OsmosisTokenList, + TON_ZERO_ADDRESS, + tonNetworkTokens +} from 'config/chainInfos'; +import { network } from 'config/networks'; +import { TON_SCAN, TonChainId, TonInteractionContract, TonNetwork } from 'context/ton-provider'; +import { MsgTransfer } from 'cosmjs-types/ibc/applications/transfer/v1/tx'; +import { getTransactionUrl, handleErrorTransaction } from 'helper'; +import { numberWithCommas } from 'helper/format'; +import { useCoinGeckoPrices } from 'hooks/useCoingecko'; +import useConfigReducer from 'hooks/useConfigReducer'; +import useLoadTokens from 'hooks/useLoadTokens'; +import { getCosmWasmClient } from 'libs/cosmjs'; +import { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { RootState } from 'store/configure'; +import useGetFee from './useGetFee'; +import useGetStateData from './useGetStateData'; + +const FWD_AMOUNT = toNano(0.15); +const TON_MESSAGE_VALID_UNTIL = 100000; +const BRIDGE_TON_TO_ORAI_MINIMUM_GAS = toNano(1); +const EXTERNAL_MESSAGE_FEE = 0.01; +const MINIMUM_BRIDGE_PER_USD = 1; // 10; // TODO: update for product is 10 + +export { + BRIDGE_TON_TO_ORAI_MINIMUM_GAS, + EXTERNAL_MESSAGE_FEE, + FWD_AMOUNT, + MINIMUM_BRIDGE_PER_USD, + TON_MESSAGE_VALID_UNTIL +}; + +const useTonBridgeHandler = ({ + token, + fromNetwork, + toNetwork +}: { + token: TokenItemType; + fromNetwork: string; + toNetwork: string; +}) => { + const [tonAddress, setTonAddress] = useConfigReducer('tonAddress'); + const [oraiAddress, setOraiAddress] = useConfigReducer('address'); + const [walletsTon] = useConfigReducer('walletsTon'); + const amounts = useSelector((state: RootState) => state.token.amounts); + + const { balances: sentBalance, getChanelStateData } = useGetStateData(); + const loadTokenAmounts = useLoadTokens(); + + const [tonConnectUI] = useTonConnectUI(); + const { data: prices } = useCoinGeckoPrices(); + const [tokenInfo, setTokenInfo] = useState({ + jettonWalletAddress: null + }); + const [deductNativeAmount, setDeductNativeAmount] = useState(0n); + + const { bridgeFee, tokenFee } = useGetFee({ + token, + fromNetwork, + toNetwork + }); + + useEffect(() => { + if (token?.chainId === TonChainId && token?.contractAddress === TON_ZERO_ADDRESS) { + setDeductNativeAmount(BRIDGE_TON_TO_ORAI_MINIMUM_GAS); + return; + } + setDeductNativeAmount(0n); + }, [token]); + + // @dev: this function will changed based on token minter address (which is USDT, USDC, bla bla bla) + useEffect(() => { + try { + (async () => { + if (token?.chainId !== TonChainId) return; + + // get the decentralized RPC endpoint + const endpoint = await getHttpEndpoint(); + const client = new TonClient({ + endpoint + }); + if (token?.contractAddress === TON_ZERO_ADDRESS) { + setDeductNativeAmount(BRIDGE_TON_TO_ORAI_MINIMUM_GAS); + setTokenInfo({ + jettonWalletAddress: '' + }); + return; + } + + const jettonMinter = JettonMinter.createFromAddress(Address.parse(token.contractAddress)); + const jettonMinterContract = client.open(jettonMinter); + const jettonWalletAddress = await jettonMinterContract.getWalletAddress(Address.parse(tonAddress)); + + setTokenInfo({ + jettonWalletAddress + }); + setDeductNativeAmount(0n); + })(); + } catch (error) { + console.log('error :>>', error); + } + }, [token]); // toNetwork, tonAddress + + const handleCheckBalanceBridgeOfTonNetwork = async (token: TokenItemType) => { + try { + // get the decentralized RPC endpoint + const endpoint = await getHttpEndpoint(); + const client = new TonClient({ + endpoint + }); + const bridgeAdapter = TonInteractionContract[TonNetwork.Mainnet].bridgeAdapter; + + if (token.contractAddress === TON_ZERO_ADDRESS) { + const balance = await client.getBalance(Address.parse(bridgeAdapter)); + + return { + balance: balance + }; + } + + const jettonWallet = JettonWallet.createFromAddress(Address.parse(walletsTon[token.denom])); + const jettonWalletContract = client.open(jettonWallet); + const balance = await jettonWalletContract.getBalance(); + return { + balance: balance.amount + }; + } catch (error) { + console.log('error :>> handleCheckBalanceBridgeOfTonNetwork', error); + } + }; + + const handleCheckBalanceBridgeOfOraichain = async (token: TokenItemType) => { + try { + if (token) { + if (!token.contractAddress) { + const data = await window.client.getBalance(network.CW_TON_BRIDGE, token.denom); + + return { + balance: data.amount + }; + } + + const tx = await window.client.queryContractSmart(token.contractAddress, { + balance: { address: network.CW_TON_BRIDGE } + }); + + return { + balance: tx?.balance || 0 + }; + } + } catch (error) { + console.log('error :>> handleCheckBalanceBridgeOfOraichain', error); + } + }; + + const handleCheckBalanceBridgeOfOsmosis = async (token: TokenItemType, fromChainId: string) => { + try { + if (token) { + if (!token.contractAddress) { + const findCosmosChain = chainInfos.find((chain) => chain.chainId === fromChainId); + const { client } = await getCosmWasmClient( + { chainId: fromChainId, rpc: findCosmosChain.rpc }, + { + gasPrice: GasPrice.fromString( + `${getCosmosGasPrice(findCosmosChain.feeCurrencies[0].gasPriceStep)}${ + findCosmosChain.feeCurrencies[0].coinMinimalDenom + }` + ) + } + ); + const data = await client.getBalance(network.CW_TON_BRIDGE, token.denom); + + return { + balance: data.amount + }; + } + } + } catch (error) { + console.log('error :>> handleCheckBalanceBridgeOfOsmosis', error); + } + }; + + const checkBalanceBridgeByNetwork = async (networkFrom: string, token: TokenItemType) => { + const handler = { + ['Oraichain']: handleCheckBalanceBridgeOfTonNetwork, + [TonChainId]: handleCheckBalanceBridgeOfOraichain, + ['osmosis-1']: handleCheckBalanceBridgeOfTonNetwork + }; + + const { balance } = handler[networkFrom] ? (await handler[networkFrom](token)) || { balance: 0 } : { balance: 0 }; + + return toDisplay(balance || 0, token.decimals || token.decimals || CW20_DECIMALS); + }; + + const validatePrice = (token: TokenItemType, amount: number) => { + let totalFee = + Number.parseFloat( + numberWithCommas(bridgeFee || 0, undefined, { + maximumFractionDigits: CW20_DECIMALS + }) + ) + + Number.parseFloat( + numberWithCommas(new BigDecimal(tokenFee).mul(amount || 0).toNumber(), undefined, { maximumFractionDigits: 6 }) + ); + + if (amount < totalFee) { + throw Error(`Minimum bridge is ${totalFee} ${token['coinDenom'] || token.name}`); + } + }; + + const buildOsorSwapMsg = ( + { user_swap, min_asset, timeout_timestamp, post_swap_action, affiliates }: SwapAndAction, + isInitial: boolean, + fromAddress?: string, + funds?: Coin[] + ) => { + const msg = { + msg: { + swap_and_action: { + user_swap, + min_asset, + timeout_timestamp, + post_swap_action, + affiliates + } + } + }; + + if (isInitial) { + if (!fromAddress) { + throw new Error('Missing fromAddress'); + } + return { + msgActionSwap: { + sender: fromAddress, + contractAddress: OSMOSIS_ROUTER_CONTRACT, + funds, + ...msg + } + }; + } + + return { + msgActionSwap: { + wasm: { + contract: OSMOSIS_ROUTER_CONTRACT, + ...msg + } + } + }; + }; + + const handleBridgeFromTon = async (amount: number | string) => { + try { + if (!oraiAddress) throw 'Please connect OWallet or Kelpr!'; + + if (!tonAddress) throw 'Please connect Ton Wallet'; + + if (!token || !amount) throw 'Not valid!'; + + if (toDisplay(amounts?.[token.denom] || '0', token['coinDecimals'] || token.decimals) < Number(amount)) + throw 'Insufficient funds'; + + validatePrice(token, Number(amount)); + + // setLoading(true); + + const tokenInOrai = oraichainTokensWithIcon.find((tk) => tk.coinGeckoId === token.coinGeckoId); + const balanceMax = await checkBalanceBridgeByNetwork(TonChainId, tokenInOrai); + + const isMintBurn = ['hamster-kombat', 'the-open-network'].includes(tokenInOrai?.coinGeckoId); + + if (!isMintBurn && Number(balanceMax) < Number(amount)) { + // setLoading(false); + throw `The bridge contract does not have enough balance to process this bridge transaction. Wanted ${amount} ${ + token['coinDenom'] || token.name + }, have ${balanceMax} ${token['coinDenom'] || token.name}`; + } + const bridgeAdapterAddress = Address.parse(TonInteractionContract[TonNetwork.Mainnet].bridgeAdapter); + const fmtAmount = new BigDecimal(10).pow(token.decimals || token['coinDecimals']).mul(amount); + const isNativeTon: boolean = token.contractAddress === TON_ZERO_ADDRESS; + const toAddress: string = isNativeTon + ? bridgeAdapterAddress.toString() + : tokenInfo.jettonWalletAddress?.toString(); + console.log('THIS IS TO ADDRESS:', { toAddress }, { walletsTon }); + const oraiAddressBech32 = fromBech32(oraiAddress).data; + const gasAmount = isNativeTon + ? fmtAmount.add(BRIDGE_TON_TO_ORAI_MINIMUM_GAS).toString() + : BRIDGE_TON_TO_ORAI_MINIMUM_GAS.toString(); + const timeout = BigInt(Math.floor(new Date().getTime() / 1000) + 3600); + + let memo = beginCell().endCell(); + + if (toNetwork === 'osmosis-1') { + const osmosisAddress = await window.Keplr.getKeplrAddr(toNetwork); + let osmosisReceiver = osmosisAddress; + if (!osmosisAddress) throw 'Please connect OWallet or Kelpr!'; + + let osorRouterMemo = ''; + let hasAlloyedPool = canConvertToAlloyedToken(token.coinGeckoId); + if (hasAlloyedPool) { + osmosisReceiver = OSMOSIS_ROUTER_CONTRACT; + let { msgActionSwap } = buildOsorSwapMsg( + { + user_swap: { + swap_exact_asset_in: { + swap_venue_name: 'osmosis-poolmanager', + operations: [ + { + pool: hasAlloyedPool.poolId, + denom_in: hasAlloyedPool.sourceToken, + denom_out: hasAlloyedPool.alloyedToken + } + ] + } + }, + min_asset: { + native: { + denom: hasAlloyedPool.alloyedToken, + amount: '0' + } + }, // consider add minimum receive (Currently, alloy pool is swap 1-1, so no't need to add min_asset + timeout_timestamp: Number(calculateTimeoutTimestamp(3600)), + post_swap_action: { + transfer: { + to_address: osmosisAddress + } + }, + affiliates: [] + }, + false + ); + osorRouterMemo = JSON.stringify(msgActionSwap); + } + + const buildMemoSwap = buildUniversalSwapMemo( + { + minimumReceive: '0', + recoveryAddr: oraiAddress + }, + undefined, + undefined, + undefined, + { + sourceChannel: 'channel-13', + sourcePort: 'transfer', + receiver: osmosisReceiver, + memo: osorRouterMemo, + recoverAddress: oraiAddress + }, + undefined + ); + memo = beginCell().storeStringRefTail(buildMemoSwap).endCell(); + } + + const getNativeBridgePayload = () => + BridgeAdapter.buildBridgeTonBody( + { + amount: BigInt(fmtAmount.toString()), + memo, + remoteReceiver: oraiAddress, + timeout + }, + oraiAddressBech32, + { + queryId: 0, + value: toNano(0) // don't care this + } + ).toBoc(); + + const getOtherBridgeTokenPayload = () => + JettonWallet.buildSendTransferPacket( + Address.parse(tonAddress), + { + fwdAmount: FWD_AMOUNT, + jettonAmount: BigInt(fmtAmount.toString()), + jettonMaster: Address.parse(token.contractAddress), + remoteReceiver: oraiAddress, + timeout, + memo, + toAddress: bridgeAdapterAddress + }, + 0 + ).toBoc(); + + const boc = isNativeTon ? getNativeBridgePayload() : getOtherBridgeTokenPayload(); + + console.log('Debug', { + address: toAddress, // dia chi token + amount: gasAmount, // gas + payload: Base64.encode(boc) + }); + + const tx = await tonConnectUI.sendTransaction({ + validUntil: TON_MESSAGE_VALID_UNTIL, + messages: [ + { + address: toAddress, // dia chi token + amount: gasAmount, // gas + payload: Base64.encode(boc) + } + ] + }); + + const txHash = Cell.fromBoc(Buffer.from(tx.boc, 'base64'))[0].hash().toString('hex'); + + if (txHash) { + displayToast(TToastType.TX_SUCCESSFUL, { + customLink: `${TON_SCAN}/transaction/${txHash}` + }); + + loadTokenAmounts({ oraiAddress, tonAddress }); + getChanelStateData(); + } + } catch (error) { + console.log('error Bridge from TON :>>', error); + + handleErrorTransaction(error, { + tokenName: token['coindDenom'], + chainName: toNetwork + }); + } + }; + + const handleBridgeFromCosmos = async (amount: number | string) => { + try { + if (!oraiAddress) throw 'Please connect OWallet or Kelpr!'; + + if (!tonAddress) throw 'Please connect Ton Wallet'; + + if (!token || !amount) throw 'Not valid!'; + + // setLoading(true); + const isFromOsmosisToOraichain = fromNetwork === 'osmosis-1' && toNetwork === 'Oraichain'; + const isFromOraichainToOsmosis = fromNetwork === 'Oraichain' && toNetwork === 'osmosis-1'; + const isFromOsmosisToTon = fromNetwork === 'osmosis-1' && toNetwork === TonChainId; + + // Osmosis <-> Oraichain + // Oraichain <-> Osmosis + // Osmosis -> Ton + if (isFromOsmosisToOraichain || isFromOraichainToOsmosis || isFromOsmosisToTon) { + if (isFromOsmosisToTon) { + console.log('470', token, amount, bridgeFee, token, fromNetwork, toNetwork); + validatePrice(token, Number(amount)); + } + const timeout = Math.floor(new Date().getTime() / 1000) + 3600; + const fromChainId = fromNetwork as CosmosChainId; + const toChainId = isFromOsmosisToTon ? ('Oraichain' as CosmosChainId) : (toNetwork as CosmosChainId); + + let [fromAddress, toAddress] = await Promise.all([ + window.Keplr.getKeplrAddr(fromChainId), + window.Keplr.getKeplrAddr(toChainId) + ]); + + if (!fromAddress || !toAddress) throw 'Please connect OWallet or Kelpr!'; + + let memo = ''; + if (isFromOsmosisToTon) { + toAddress = IBC_WASM_CONTRACT; + + const memoUniversal = buildUniversalSwapMemo( + { minimumReceive: '0', recoveryAddr: oraiAddress }, + undefined, + undefined, + { + contractAddress: network.CW_TON_BRIDGE, + msg: toBinary({ + bridge_to_ton: { + to: tonAddress, + denom: tonNetworkTokens.find((tk) => tk.coinGeckoId === token.coinGeckoId).contractAddress, + timeout, + recovery_addr: oraiAddress + } + }) + }, + undefined, + undefined + ); + + memo = JSON.stringify({ + wasm: { + contract: IBC_WASM_CONTRACT, + msg: { + ibc_hooks_receive: { + func: 'universal_swap', + orai_receiver: oraiAddress, + args: memoUniversal + } + } + } + }); + } + if (isFromOraichainToOsmosis) { + let hasAlloyedPool = canConvertToAlloyedToken(token.coinGeckoId); + if (hasAlloyedPool) { + let { msgActionSwap } = buildOsorSwapMsg( + { + user_swap: { + swap_exact_asset_in: { + swap_venue_name: 'osmosis-poolmanager', + operations: [ + { + pool: hasAlloyedPool.poolId, + denom_in: hasAlloyedPool.sourceToken, + denom_out: hasAlloyedPool.alloyedToken + } + ] + } + }, + min_asset: { + native: { + denom: hasAlloyedPool.alloyedToken, + amount: '0' + } + }, // consider add minimum receive (Currently, alloy pool is swap 1-1, so no't need to add min_asset + timeout_timestamp: Number(calculateTimeoutTimestamp(3600)), + post_swap_action: { + transfer: { + to_address: toAddress + } + }, + affiliates: [] + }, + false + ); + memo = JSON.stringify(msgActionSwap); + toAddress = OSMOSIS_ROUTER_CONTRACT; + } + } + + const ibcInfo = UniversalSwapHelper.getIbcInfo(fromChainId, toChainId); + let executeMsg; + if (fromNetwork === 'osmosis-1' && token.denom === OsmosisTokenDenom.allTon) { + let hasAlloyedPool = canConvertToAlloyedToken(token.coinGeckoId); + if (!hasAlloyedPool) throw new Error('AlloyPool does not exist!'); + // need convert from alloyed first + let { msgActionSwap } = buildOsorSwapMsg( + { + user_swap: { + swap_exact_asset_in: { + swap_venue_name: 'osmosis-poolmanager', + operations: [ + { + pool: hasAlloyedPool.poolId, + denom_in: hasAlloyedPool.alloyedToken, + denom_out: hasAlloyedPool.sourceToken + } + ] + } + }, + min_asset: { + native: { + denom: hasAlloyedPool.sourceToken, + amount: '0' + } + }, // consider add minimum receive (Currently, alloy pool is swap 1-1, so no't need to add min_asset + timeout_timestamp: Number(calculateTimeoutTimestamp(3600)), + post_swap_action: { + ibc_transfer: { + ibc_info: { + source_channel: ibcInfo.channel, + receiver: toAddress, + memo, + recover_address: fromAddress + } + } + }, + affiliates: [] + }, + true, + fromAddress, + coins(toAmount(amount, token.decimals).toString(), token.denom) + ); + + executeMsg = getEncodedExecuteContractMsgs(fromAddress, [msgActionSwap as ExecuteInstruction]); + } else + executeMsg = [ + { + typeUrl: '/ibc.applications.transfer.v1.MsgTransfer', + value: MsgTransfer.fromPartial({ + sourcePort: ibcInfo.source, + sourceChannel: ibcInfo.channel, + token: coin(toAmount(amount, token.decimals).toString(), token.denom), + sender: fromAddress, + receiver: toAddress, + memo, + timeoutTimestamp: BigInt(calculateTimeoutTimestamp(ibcInfo.timeout)) + }) + } + ]; + + const findCosmosChain = cosmosChains.find((chain) => chain.chainId === fromNetwork); + + const { client } = await getCosmWasmClient( + { chainId: fromChainId, rpc: findCosmosChain.rpc }, + { + gasPrice: GasPrice.fromString( + `${getCosmosGasPrice(findCosmosChain.feeCurrencies[0].gasPriceStep)}${ + findCosmosChain.feeCurrencies[0].coinMinimalDenom + }` + ) + } + ); + const tx = await client.signAndBroadcast(fromAddress, executeMsg, 'auto'); + + if (tx?.transactionHash) { + displayToast(TToastType.TX_SUCCESSFUL, { + customLink: getTransactionUrl(fromNetwork as any, tx.transactionHash) + }); + loadTokenAmounts({ oraiAddress, tonAddress }); + } + return; + } + validatePrice(token, Number(amount)); + + const tokenInTon = tonNetworkTokens.find((tk) => tk.coinGeckoId === token.coinGeckoId); + const bridgeJettonWallet = walletsTon[tokenInTon.denom]; + if (!bridgeJettonWallet) throw 'Bridge wallet not found!'; + + const balanceMax = (sentBalance || []).find((b) => b.native.denom === bridgeJettonWallet)?.native.amount; + + const displayBalance = toDisplay(balanceMax, tokenInTon?.decimals || CW20_DECIMALS); + + if (displayBalance < Number(amount) && token.contractAddress !== null) { + // setLoading(false); + throw `The bridge contract does not have enough balance to process this bridge transaction. Wanted ${amount} ${token.name}, have ${displayBalance} ${token.name}`; + } + + const tonBridgeClient = new TonbridgeBridgeClient(window.client, oraiAddress, network.CW_TON_BRIDGE); + + let tx; + + const timeout = Math.floor(new Date().getTime() / 1000) + 3600; + + const msg = { + // crcSrc: ARG_BRIDGE_TO_TON.CRC_SRC, + denom: bridgeJettonWallet, + timeout, + to: tonAddress + }; + + const funds = handleSentFunds({ + denom: token.denom, + amount: toAmount(amount, token.decimals || token['coinDecimals']).toString() + }); + + // native token + if (!token.contractAddress) { + tx = await tonBridgeClient.bridgeToTon(msg, 'auto', null, funds); + } + // cw20 token + else { + tx = await window.client.execute( + oraiAddress, + token.contractAddress, + { + send: { + contract: network.CW_TON_BRIDGE, + amount: toAmount(amount, token.decimals || token['coinDecimals']).toString(), + msg: toBinary({ + denom: msg.denom, + timeout, + to: msg.to + }) + } + }, + 'auto' + ); + } + + if (tx?.transactionHash) { + displayToast(TToastType.TX_SUCCESSFUL, { + customLink: getTransactionUrl(fromNetwork as any, tx.transactionHash) + }); + loadTokenAmounts({ oraiAddress, tonAddress }); + } + } catch (error) { + console.log('error Bridge from Oraichain :>>', error); + handleErrorTransaction(error, { + tokenName: token.name || token['coinDenom'], + chainName: toNetwork + }); + } + }; + + return { + deductNativeAmount, + checkBalanceBridgeByNetwork, + handleBridgeFromCosmos, + handleBridgeFromTon + }; +}; + +export default useTonBridgeHandler; + +export const canConvertToAlloyedToken = (coinGeckoId: string): AlloyedPool | undefined => { + const hasAlloyed = OsmosisTokenList.find( + (token) => token.coinGeckoId == coinGeckoId && token.denom === OsmosisTokenDenom.allTon + ); + return hasAlloyed ? OsmosisAlloyedPools.find((pool) => pool.alloyedToken == hasAlloyed.denom) : undefined; +}; diff --git a/src/pages/Balance/index.tsx b/src/pages/Balance/index.tsx index ad4a49bc0..57467c6b5 100644 --- a/src/pages/Balance/index.tsx +++ b/src/pages/Balance/index.tsx @@ -13,7 +13,8 @@ import { getCosmosGasPrice, solChainId, SOLANA_RPC, - toDisplay + toDisplay, + tronToEthAddress } from '@oraichain/oraidex-common'; import { UniversalSwapHandler, UniversalSwapHelper } from '@oraichain/oraidex-universal-swap'; import { isMobile } from '@walletconnect/browser-utils'; @@ -38,6 +39,7 @@ import { getSpecialCoingecko, getTransactionUrl, handleCheckAddress, + handleCheckWallet, handleErrorTransaction, networks } from 'helper'; @@ -74,6 +76,8 @@ import { getUtxos, mapUtxos, moveOraibToOraichain, + transferIbcCustom, + transferIBCKwt, useDepositFeesBitcoinV2, useGetWithdrawlFeesBitcoinV2 } from './helpers'; @@ -96,20 +100,27 @@ import { ORAICHAIN_RELAYER_ADDRESS } from 'program/web3'; import { clusterApiUrl, Connection, PublicKey } from '@solana/web3.js'; import { getAccount, getAssociatedTokenAddress, NATIVE_MINT } from '@solana/spl-token'; import { BN } from '@coral-xyz/anchor'; +import { TonChainId } from 'context/ton-provider'; +import useTonBridgeHandler from './hooks/useTonBridgeHandler'; interface BalanceProps {} export const isMaintainBridge = false; const Balance: React.FC = () => { + //@ts-ignore + const isOwallet = window.owallet?.isOwallet; + // hook const [searchParams] = useSearchParams(); - const tokenUrl = searchParams.get('token'); + const ref = useRef(null); const navigate = useNavigate(); + const tokenUrl = searchParams.get('token'); const amounts = useSelector((state: RootState) => state.token.amounts); const feeConfig = useSelector((state: RootState) => state.token.feeConfigs); const nomic = useContext(NomicContext); const cwBitcoinContext = useContext(CwBitcoinContext); + const [walletByNetworks] = useWalletReducer('walletsByNetwork'); // state internal const [loadingRefresh, setLoadingRefresh] = useState(false); @@ -117,29 +128,36 @@ const Balance: React.FC = () => { const [isDepositBtcModal, setIsDepositBtcModal] = useState(false); const [, setTxHash] = useState(''); const [[from, to], setTokenBridge] = useState([]); + const [toNetworkChainId, setToNetworkChainId] = useState(); const [[otherChainTokens, oraichainTokens], setTokens] = useState([[], []]); - const [walletByNetworks] = useWalletReducer('walletsByNetwork'); + const [addressRecovery, setAddressRecovery] = useState(''); + const [isFastMode, setIsFastMode] = useState(true); const [theme] = useConfigReducer('theme'); - const [oraiAddress] = useConfigReducer('address'); + + const [filterNetworkUI, setFilterNetworkUI] = useConfigReducer('filterNetwork'); const [hideOtherSmallAmount, setHideOtherSmallAmount] = useConfigReducer('hideOtherSmallAmount'); + const [metamaskAddress] = useConfigReducer('metamaskAddress'); - const [filterNetworkUI, setFilterNetworkUI] = useConfigReducer('filterNetwork'); + const [oraiAddress] = useConfigReducer('address'); const [tronAddress] = useConfigReducer('tronAddress'); - const [btcAddress] = useConfigReducer('btcAddress'); + const [tonAddress] = useConfigReducer('tonAddress'); const [solAddress] = useConfigReducer('solAddress'); - const [addressRecovery, setAddressRecovery] = useState(''); - const [isFastMode, setIsFastMode] = useState(true); + const [btcAddress] = useConfigReducer('btcAddress'); + + const wallet = useWallet(); + + const { handleBridgeFromCosmos, handleBridgeFromTon } = useTonBridgeHandler({ + token: from, + fromNetwork: from?.chainId, + toNetwork: toNetworkChainId + }); const depositV2Fee = useDepositFeesBitcoinV2(true); const withdrawV2Fee = useGetWithdrawlFeesBitcoinV2({ enabled: true, bitcoinAddress: btcAddress }); - const wallet = useWallet(); - const ref = useRef(null); - //@ts-ignore - const isOwallet = window.owallet?.isOwallet; const getAddress = async () => { try { await nomic.generateAddress(); @@ -161,7 +179,7 @@ const Balance: React.FC = () => { if (isOwallet) { getAddress(); } - }, [isOwallet, oraiAddress]); + }, [oraiAddress, isOwallet]); useOnClickOutside(ref, () => { setTokenBridge([undefined, undefined]); @@ -310,7 +328,7 @@ const Balance: React.FC = () => { customLink: `/bitcoin-dashboard${isV2 ? '-v2' : ''}?tab=pending_deposits` }); setTimeout(async () => { - await loadTokenAmounts({ metamaskAddress, tronAddress, oraiAddress, btcAddress: btcAddr }); + await loadTokenAmounts({ metamaskAddress, tronAddress, oraiAddress, btcAddress: btcAddr, tonAddress }); }, 5000); return; } @@ -419,6 +437,35 @@ const Balance: React.FC = () => { return [isSoltoOraichain, isOraichainToSol, isBTCtoOraichain, isBTCtoOraichain || isOraichainToBTC]; }; + const checkTransferTon = async (toNetworkChainId: string) => { + const isFromTon = from.chainId === TonChainId && !!toNetworkChainId; + const isFromCosmosToTON = !!from.chainId && toNetworkChainId === TonChainId; + const isBridgeWithOraiAndOsmo = + [from.chainId, to.chainId].includes('Oraichain') && [from.chainId, to.chainId].includes('osmosis-1'); + + const isFromCosmos = isFromCosmosToTON || isBridgeWithOraiAndOsmo; + + if (isFromCosmos || isFromTon) { + return { + isTonBridge: true, + isFromTon + }; + } + return { + isTonBridge: false, + isFromTon + }; + }; + + const handleTransferTon = async ({ isTonToCosmos, transferAmount }) => { + const tonAddress = window.Ton.account?.address; + if (!tonAddress) throw Error('Not found your ton address!'); + if (isTonToCosmos) { + return await handleBridgeFromTon(transferAmount); + } + return await handleBridgeFromCosmos(transferAmount); + }; + const handleTransferBTC = async ({ isBTCToOraichain, fromToken, transferAmount }) => { const btcAddr = await window.Bitcoin.getAddress(); if (!btcAddr) throw Error('Not found your bitcoin address!'); @@ -531,6 +578,7 @@ const Balance: React.FC = () => { toNetworkChainId?: NetworkChainId ) => { try { + await handleCheckWallet(); assert(from && to, 'Please choose both from and to tokens'); const initFromBalance = amounts[from.denom]; const subAmounts = getSubAmountDetails(amounts, from); @@ -551,6 +599,15 @@ const Balance: React.FC = () => { assert(newToToken, 'Cannot find newToToken token that matches from token to bridge!'); } + // check transfer TON <=> ORAICHAIN + const { isTonBridge, isFromTon } = await checkTransferTon(toNetworkChainId); + if (isTonBridge) { + return await handleTransferTon({ + isTonToCosmos: isFromTon, + transferAmount: fromAmount + }); + } + assert( newToToken.coinGeckoId === from.coinGeckoId, `From token ${from.coinGeckoId} is different from to token ${newToToken.coinGeckoId}` @@ -795,7 +852,7 @@ const Balance: React.FC = () => { await refreshBalances( loadingRefresh, setLoadingRefresh, - { metamaskAddress, tronAddress, oraiAddress, btcAddress, solAddress }, + { metamaskAddress, tronAddress, oraiAddress, btcAddress, solAddress, tonAddress }, loadTokenAmounts ); }} diff --git a/src/pages/BitcoinDashboardV2/components/Header/Header.module.scss b/src/pages/BitcoinDashboardV2/components/Header/Header.module.scss index af9b5d6b6..dd63e3948 100644 --- a/src/pages/BitcoinDashboardV2/components/Header/Header.module.scss +++ b/src/pages/BitcoinDashboardV2/components/Header/Header.module.scss @@ -13,7 +13,6 @@ flex-wrap: wrap; margin-bottom: 16px; - &_text { display: flex; align-items: flex-end; @@ -118,7 +117,6 @@ flex-direction: column; flex: 1 0 0; - color: $neutron-text-body-light; font-size: 16px; font-weight: 500; diff --git a/src/pages/Pool-V3/hooks/useCreatePositionForm.ts b/src/pages/Pool-V3/hooks/useCreatePositionForm.ts index dff90bea9..85b0c4871 100644 --- a/src/pages/Pool-V3/hooks/useCreatePositionForm.ts +++ b/src/pages/Pool-V3/hooks/useCreatePositionForm.ts @@ -202,7 +202,7 @@ const useCreatePositionForm = ( data.push({ time: Date.now(), price: currentPrice - }) + }); const padding = 0.1; @@ -319,13 +319,13 @@ const useCreatePositionForm = ( const higherTick = Math.max( Number(getMinTick(Number(poolKey.fee_tier.tick_spacing))), Number(pool.current_tick_index) + - Number(poolKey.fee_tier.tick_spacing) * TICK_SPACING_TO_RANGE[poolKey.fee_tier.tick_spacing] + Number(poolKey.fee_tier.tick_spacing) * TICK_SPACING_TO_RANGE[poolKey.fee_tier.tick_spacing] ); const lowerTick = Math.min( Number(getMaxTick(Number(poolKey.fee_tier.tick_spacing))), Number(pool.current_tick_index) - - Number(poolKey.fee_tier.tick_spacing) * TICK_SPACING_TO_RANGE[poolKey.fee_tier.tick_spacing] + Number(poolKey.fee_tier.tick_spacing) * TICK_SPACING_TO_RANGE[poolKey.fee_tier.tick_spacing] ); const minPrice = calcPrice(lowerTick, isXToY, tokenX.decimals, tokenY.decimals); diff --git a/src/pages/UniversalSwap/Component/AssetsTab.tsx b/src/pages/UniversalSwap/Component/AssetsTab.tsx index cd0be5110..f4c664449 100644 --- a/src/pages/UniversalSwap/Component/AssetsTab.tsx +++ b/src/pages/UniversalSwap/Component/AssetsTab.tsx @@ -40,12 +40,16 @@ export const AssetsTab: FC<{ networkFilter: string }> = ({ networkFilter }) => { stakerAddress: address }); let totalUsd: number = getTotalUsd(amounts, prices); - if (networkFilter) { - const subAmounts = Object.fromEntries( - Object.entries(amounts).filter(([denom]) => tokenMap?.[denom]?.chainId === networkFilter) - ); - totalUsd = getTotalUsd(subAmounts, prices); - } + // if (networkFilter) { + // const subAmounts = Object.fromEntries( + // Object.entries(amounts).filter(([denom]) => tokenMap?.[denom]?.chainId === networkFilter) + // ); + // totalUsd = getTotalUsd(subAmounts, prices); + // } + + useEffect(() => { + dispatch(updateTotalLpv3(totalLpV3Info || 0)); + }, [totalLpV3Info]); useEffect(() => { dispatch(updateTotalLpv3(totalLpV3Info || 0)); diff --git a/src/pages/UniversalSwap/Component/HistoryTab.tsx b/src/pages/UniversalSwap/Component/HistoryTab.tsx index f1cc34913..0fa7c64a3 100644 --- a/src/pages/UniversalSwap/Component/HistoryTab.tsx +++ b/src/pages/UniversalSwap/Component/HistoryTab.tsx @@ -115,8 +115,6 @@ export const HistoryTab: React.FC<{ }> = ({ networkFilter }) => { const { transHistory } = useGetTransHistory(); const [selectedData, setSelectedData] = useState(null); - console.log({ selectedData }); - const headers: TableHeaderProps = { assets: { name: '', diff --git a/src/pages/UniversalSwap/Swap/components/SelectToken/SelectToken.tsx b/src/pages/UniversalSwap/Swap/components/SelectToken/SelectToken.tsx index 97f7a6835..3b354c0c4 100644 --- a/src/pages/UniversalSwap/Swap/components/SelectToken/SelectToken.tsx +++ b/src/pages/UniversalSwap/Swap/components/SelectToken/SelectToken.tsx @@ -84,6 +84,10 @@ export default function SelectToken({ (textSearch ? item.name.toLowerCase().includes(textSearch.toLowerCase()) : true) ); + console.log({ + listItems + }); + return ( <>
diff --git a/src/pages/UniversalSwap/Swap/hooks/useHandleEffectTokenChange.ts b/src/pages/UniversalSwap/Swap/hooks/useHandleEffectTokenChange.ts index d1d11e878..a0994fdbb 100644 --- a/src/pages/UniversalSwap/Swap/hooks/useHandleEffectTokenChange.ts +++ b/src/pages/UniversalSwap/Swap/hooks/useHandleEffectTokenChange.ts @@ -26,6 +26,7 @@ const useHandleEffectTokenChange = ({ fromTokenDenomSwap, toTokenDenomSwap }) => const [metamaskAddress] = useConfigReducer('metamaskAddress'); const [tronAddress] = useConfigReducer('tronAddress'); + const [tonAddress] = useConfigReducer('tonAddress'); const [oraiAddress] = useConfigReducer('address'); const [addressTransfer, setAddressTransfer] = useState(''); @@ -53,19 +54,23 @@ const useHandleEffectTokenChange = ({ fromTokenDenomSwap, toTokenDenomSwap }) => useEffect(() => { (async () => { if (!isMobile()) { - if (!walletByNetworks.evm && !walletByNetworks.cosmos && !walletByNetworks.tron) { - return setAddressTransfer(''); - } + const isNetworkSupported = + walletByNetworks.evm || walletByNetworks.cosmos || walletByNetworks.tron || walletByNetworks.ton; - if (originalToToken.cosmosBased && !walletByNetworks.cosmos) { + if (!isNetworkSupported) { return setAddressTransfer(''); } - if (!originalToToken.cosmosBased && originalToToken.chainId === '0x2b6653dc' && !walletByNetworks.tron) { - return setAddressTransfer(''); - } + const isCosmosBased = originalToToken.cosmosBased; + const chainId = originalToToken.chainId; - if (!originalToToken.cosmosBased && !walletByNetworks.evm) { + if ( + (isCosmosBased && !walletByNetworks.cosmos) || + (!isCosmosBased && + ((chainId === '0x2b6653dc' && !walletByNetworks.tron) || + (chainId === 'ton' && !walletByNetworks.ton) || + (['0x01', '0x38'].includes(chainId) && !walletByNetworks.evm))) + ) { return setAddressTransfer(''); } } @@ -83,11 +88,14 @@ const useHandleEffectTokenChange = ({ fromTokenDenomSwap, toTokenDenomSwap }) => oraiAddress, metamaskAddress, tronAddress, + tonAddress, walletByNetworks.evm, walletByNetworks.cosmos, walletByNetworks.tron, + walletByNetworks.ton, window?.ethereumDapp, - window?.tronWebDapp + window?.tronWebDapp, + window?.Ton ]); useEffect(() => { @@ -113,7 +121,7 @@ const useHandleEffectTokenChange = ({ fromTokenDenomSwap, toTokenDenomSwap }) => }, [originalFromToken, fromToken]); const isConnectedWallet = - walletByNetworks.cosmos || walletByNetworks.bitcoin || walletByNetworks.evm || walletByNetworks.tron; + walletByNetworks.cosmos || walletByNetworks.bitcoin || walletByNetworks.evm || walletByNetworks.tron || window.Ton; let validAddress = { isValid: true diff --git a/src/pages/UniversalSwap/Swap/index.tsx b/src/pages/UniversalSwap/Swap/index.tsx index 571056144..f78bd01b1 100644 --- a/src/pages/UniversalSwap/Swap/index.tsx +++ b/src/pages/UniversalSwap/Swap/index.tsx @@ -85,6 +85,7 @@ import useCalculateDataSwap, { SIMULATE_INIT_AMOUNT } from './hooks/useCalculate import { useFillToken } from './hooks/useFillToken'; import useHandleEffectTokenChange from './hooks/useHandleEffectTokenChange'; import styles from './index.module.scss'; +import TonWallet from '@oraichain/tonbridge-sdk/build/wallet'; const cx = cn.bind(styles); @@ -97,6 +98,7 @@ const SwapComponent: React.FC<{ const [metamaskAddress] = useConfigReducer('metamaskAddress'); const [tronAddress] = useConfigReducer('tronAddress'); const [oraiAddress] = useConfigReducer('address'); + const [tonAddress] = useConfigReducer('tonAddress'); const [walletByNetworks] = useWalletReducer('walletsByNetwork'); const [theme] = useConfigReducer('theme'); const isLightMode = theme === 'light'; @@ -320,8 +322,25 @@ const SwapComponent: React.FC<{ const isCustomRecipient = validAddress.isValid && addressTransfer !== initAddressTransfer; const alphaSmartRoutes = simulateData?.routes; + let tonWallet = undefined; + if ([originalFromToken.chainId, originalToToken.chainId].includes('ton') && !!walletByNetworks.ton) { + tonWallet = await TonWallet.create('mainnet', { + mnemonicData: { + mnemonic: undefined, + tonWalletVersion: 'V4' + }, + tonConnector: window?.Ton as any + }); + } + + const tonAddress = tonWallet?.sender?.address?.toString(); const swapData = { - sender: { cosmos: cosmosAddress, evm: checksumMetamaskAddress, tron: tronAddress }, + sender: { + cosmos: cosmosAddress, + evm: checksumMetamaskAddress, + tron: tronAddress, + ton: tonAddress + }, originalFromToken, originalToToken, fromAmount: fromAmountToken, @@ -339,6 +358,7 @@ const SwapComponent: React.FC<{ const univeralSwapHandler = new UniversalSwapHandler(swapData, { cosmosWallet: window.Keplr, evmWallet: new Metamask(window.tronWebDapp), + tonWallet, swapOptions: { isAlphaIbcWasm: useAlphaIbcWasm, isIbcWasm: useIbcWasm, @@ -348,12 +368,14 @@ const SwapComponent: React.FC<{ } }); - const { transactionHash } = await univeralSwapHandler.processUniversalSwap(); + const result = await univeralSwapHandler.processUniversalSwap(); + let transactionHash = result?.transactionHash; + if (transactionHash) { displayToast(TToastType.TX_SUCCESSFUL, { customLink: getTransactionUrl(originalFromToken.chainId, transactionHash) }); - loadTokenAmounts({ oraiAddress, metamaskAddress, tronAddress }); + loadTokenAmounts({ oraiAddress, metamaskAddress, tronAddress, tonAddress }); setSwapLoading(false); // save to duckdb @@ -670,7 +692,14 @@ const SwapComponent: React.FC<{ loadingRefresh, setLoadingRefresh, // TODO: need add bitcoinAddress when universal swap support bitcoin - { metamaskAddress, tronAddress, oraiAddress, btcAddress: undefined, solAddress: undefined }, + { + metamaskAddress, + tronAddress, + oraiAddress, + tonAddress, + btcAddress: undefined, + solAddress: undefined + }, loadTokenAmounts ) } diff --git a/src/pages/UniversalSwap/helpers.ts b/src/pages/UniversalSwap/helpers.ts index dfc6b4a11..46c9d3951 100644 --- a/src/pages/UniversalSwap/helpers.ts +++ b/src/pages/UniversalSwap/helpers.ts @@ -193,6 +193,8 @@ export const getExplorerScan = (chainId: NetworkChainId) => { return 'https://scan.kawaii.global/tx'; case 'noble-1': return 'https://www.mintscan.io/noble/tx'; + case 'ton': + return 'https://tonscan.org/address'; default: return 'https://scan.orai.io/txs'; } @@ -279,13 +281,13 @@ export const getTokenIcon = (token: TokenItemType, theme: string) => { export const refreshBalances = async ( loadingRefresh: boolean, setLoadingRefresh: (boolean) => void, - { metamaskAddress, tronAddress, oraiAddress, btcAddress, solAddress }, + { metamaskAddress, tronAddress, oraiAddress, btcAddress, solAddress, tonAddress }, callback ) => { try { if (loadingRefresh) return; setLoadingRefresh(true); - await callback({ metamaskAddress, tronAddress, oraiAddress, btcAddress, solAddress }); + await callback({ metamaskAddress, tronAddress, oraiAddress, btcAddress, solAddress, tonAddress }); } catch (err) { console.log({ err }); } finally { @@ -338,11 +340,6 @@ export const getDisableSwap = ({ simulateData, isLoadingSimulate }) => { - const mobileMode = isMobile(); - const canSwapToCosmos = !mobileMode && originalToToken.cosmosBased && !walletByNetworks.cosmos; - const canSwapToEvm = !mobileMode && !originalToToken.cosmosBased && !walletByNetworks.evm; - const canSwapToTron = !mobileMode && originalToToken.chainId === '0x2b6653dc' && !walletByNetworks.tron; - const canSwapTo = canSwapToCosmos || canSwapToEvm || canSwapToTron; const disabledSwapBtn = swapLoading || !fromAmountToken || @@ -350,15 +347,11 @@ export const getDisableSwap = ({ fromAmountTokenBalance > fromTokenBalance || // insufficent fund !addressTransfer || !validAddress.isValid || - isLoadingSimulate || - canSwapTo; + isLoadingSimulate; let disableMsg: string; if (!validAddress.isValid) disableMsg = `Recipient address not found`; if (!addressTransfer) disableMsg = `Recipient address not found`; - if (canSwapToCosmos) disableMsg = `Please connect cosmos wallet`; - if (canSwapToEvm) disableMsg = `Please connect evm wallet`; - if (canSwapToTron) disableMsg = `Please connect tron wallet`; if (!simulateData || simulateData.displayAmount <= 0) disableMsg = 'Enter an amount'; if (fromAmountTokenBalance > fromTokenBalance) disableMsg = `Insufficient funds`; if (isLoadingSimulate) disableMsg = `Swap`; @@ -379,7 +372,7 @@ export const getProtocolsSmartRoute = ( ) => { const protocols = ['Oraidex', 'OraidexV3']; if (useIbcWasm && !useAlphaIbcWasm) return protocols; - if (fromToken.chainId === 'noble-1' || toToken?.chainId === 'noble-1') return protocols; + if (fromToken?.chainId === 'noble-1' || toToken?.chainId === 'noble-1') return protocols; const allowOsmosisProtocols = ['injective-1', 'Neutaro-1', 'noble-1', 'osmosis-1', 'cosmoshub-4', 'celestia']; const isAllowOsmosisProtocol = diff --git a/src/reducer/config.ts b/src/reducer/config.ts index 5d7b370fb..71637cdb8 100644 --- a/src/reducer/config.ts +++ b/src/reducer/config.ts @@ -20,12 +20,16 @@ export type RewardPoolType = { liquidity_token: string; }; +type WalletTonDetails = { [denom: string]: string }; + export interface ConfigState { address: string; metamaskAddress: string | null; tronAddress: string | null; btcAddress: string | null; solAddress: string | null; + tonAddress: string | null; + walletsTon: WalletTonDetails; // ton wallets for bridge adapter cosmosAddress: { [key: string]: string }; allPendingDeposits: { [key: string]: DepositInfo[] }; chainId: string; @@ -71,8 +75,10 @@ const initialState: ConfigState = { metamaskAddress: '', bannerTime: 0, btcAddress: '', + tonAddress: '', tronAddress: '', solAddress: '', + walletsTon: {}, walletTypeStore: 'owallet', cosmosAddress: {}, allPendingDeposits: {}, diff --git a/src/reducer/poolDetailV3.ts b/src/reducer/poolDetailV3.ts index 64deeb7e3..144cabd09 100644 --- a/src/reducer/poolDetailV3.ts +++ b/src/reducer/poolDetailV3.ts @@ -66,11 +66,11 @@ const initialState: PoolDetailV3State = { tokenY: null, isXToY: true, historicalRange: '7d', - cache7Day: [], - cache1Month: [], - cache3Month: [], - cache1Year: [], - historicalChartData: [], + cache7Day: null, + cache1Month: null, + cache3Month: null, + cache1Year: null, + historicalChartData: null, fullRange: false, xRange: null, yRange: null, @@ -329,7 +329,7 @@ export const fetchHistoricalPriceData3M = createAsyncThunk( export const fetchHistoricalPriceData7D = createAsyncThunk( 'poolDetailV3/fetchHistoricalPriceData7D', async (poolId: string) => { - return await getHistoricalPriceDataInDay(poolId, '7d'); + return await getHistoricalPriceDataInHour(poolId, '7d'); } ); diff --git a/src/reducer/wallet.ts b/src/reducer/wallet.ts index ab9b7e9c2..428ac094c 100644 --- a/src/reducer/wallet.ts +++ b/src/reducer/wallet.ts @@ -7,6 +7,7 @@ export type WalletsByNetwork = { tron: 'owallet' | 'tronLink' | null; bitcoin: 'owallet' | null; solana: 'phantom' | 'owallet' | null; + ton: 'ton' | null; }; export interface WalletState { @@ -19,7 +20,8 @@ const initialState: WalletState = { evm: null, tron: null, bitcoin: null, - solana: null + solana: null, + ton: null } }; diff --git a/src/rest/graphClient.ts b/src/rest/graphClient.ts index 5ef68787a..31681cd4f 100644 --- a/src/rest/graphClient.ts +++ b/src/rest/graphClient.ts @@ -684,7 +684,7 @@ export const getHistoricalPriceDataInDay = async ( default: dayIndex = currentIndex - 7; } - + // create array of chunkOffset: [dayIndex, dayIndex + CHUNK_QUERY, ...] to current Index const length = Math.ceil((currentIndex - dayIndex) / CHUNK_QUERY); const chunkOffset = Array.from({ length }, (_, i) => dayIndex + i * CHUNK_QUERY); diff --git a/src/styles/_reboot.scss b/src/styles/_reboot.scss index 512ec48d8..cb0298b94 100644 --- a/src/styles/_reboot.scss +++ b/src/styles/_reboot.scss @@ -117,3 +117,8 @@ textarea { [type='number']::-webkit-outer-spin-button { -webkit-appearance: none; } + +#tc-widget-root { + position: absolute; + z-index: 1022; // set for zindex above react-modal (1021) +} diff --git a/src/styles/_themes.scss b/src/styles/_themes.scss index 8259cc75c..89ed6c368 100644 --- a/src/styles/_themes.scss +++ b/src/styles/_themes.scss @@ -159,7 +159,8 @@ $themes: ( 'neutral-text-btn-default': #152703, 'neutral-surface-bg-3': #f7f7f7, 'neutral-surface-bg-swap': #1b1d19, - 'neutral-surface-bg-strong': #131412 + 'neutral-surface-bg-strong': #131412, + 'neutral-surface-bg-box': #232521 ), light: ( 'warning-surface-default-subtle': #fffdeb, @@ -266,6 +267,7 @@ $themes: ( 'bg-overlay-section': rgba(255, 255, 255, 0.5), 'neutral-surface-bg-3': #181a17, 'neutral-surface-bg-swap': #fff, - 'neutral-surface-bg-strong': #f2f4f0 + 'neutral-surface-bg-strong': #f2f4f0, + 'neutral-surface-bg-box': #f2f4f0 ) ); diff --git a/src/types/global.d.ts b/src/types/global.d.ts index 1e51c7b6a..b32fa718a 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -10,6 +10,7 @@ import { Networks as _Networks } from 'libs/ethereum-multicall/enums'; import { DuckDb } from 'libs/duckdb'; import { Class } from '@oraichain/common-contracts-sdk/build/CwIcs721Bridge.types'; import Bitcoin, { IBitcoin } from 'libs/bitcoin'; +import { TonConnectUI } from '@tonconnect/ui-react'; declare global { type AmountDetails = { [denom: string]: string }; @@ -117,6 +118,7 @@ declare global { Wallet: Wallet; Keplr: Keplr; Bitcoin: Bitcoin; + Ton: TonConnectUI; tronWeb: _TronWeb; tronLink: TronLink; ethereum: MetaMaskEthereumProvider; diff --git a/vite.config.ts b/vite.config.ts index d19df1a10..3d7519977 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -40,5 +40,10 @@ export default defineConfig({ }, optimizeDeps: { exclude: ['node_modules/.cache'] + }, + esbuild: { + supported: { + 'top-level-await': true //browsers can handle top-level-await features + } } }); diff --git a/yarn.lock b/yarn.lock index a24bf9707..c01a98d0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3847,10 +3847,10 @@ react-use "^17.4.0" react-use-websocket "^4.5.0" -"@oraichain/oraidex-common@1.1.39", "@oraichain/oraidex-common@^1.1.27", "@oraichain/oraidex-common@^1.1.32": - version "1.1.39" - resolved "https://registry.yarnpkg.com/@oraichain/oraidex-common/-/oraidex-common-1.1.39.tgz#6fe8ab3435d131a412d9f5876801a8709fb50073" - integrity sha512-CggmZqynoSYUzah0n+Q4bbcIopDOaH09nfd4/w/0HbqWN9Sn0hWk0wIqotaXRxO2EXJk4NEsn+yZNrfwFY63vw== +"@oraichain/oraidex-common@1.1.32-beta.8", "@oraichain/oraidex-common@1.1.40-beta.1", "@oraichain/oraidex-common@^1.1.32", "@oraichain/oraidex-common@^1.1.6": + version "1.1.40-beta.1" + resolved "https://registry.yarnpkg.com/@oraichain/oraidex-common/-/oraidex-common-1.1.40-beta.1.tgz#db74f7bf3a34c2bc8dc965f6f4b44a7a0951b478" + integrity sha512-17KpgbJyHwqEfndmASqkn7X4WZBeIXy0ONzzg5vwaVIsuBKx76hqah14chtqWUIDJSdd7FFeR16KhIAlMWOnYg== dependencies: "@cosmjs/amino" "^0.32.4" "@cosmjs/cosmwasm-stargate" "^0.32.4" @@ -3889,13 +3889,13 @@ resolved "https://registry.yarnpkg.com/@oraichain/oraidex-contracts-sdk/-/oraidex-contracts-sdk-1.0.45.tgz#42dae0fdd9e005f920ba305b987009f791acc365" integrity sha512-/nYztdxEX5LQM4DMJQmi9HvZrBVoY3nLAmYqSKZGZ0U1h1SxU7O/o22R3/pQwB+sAJdcibaI8ygC0ov7jC8paA== -"@oraichain/oraidex-universal-swap@1.1.23": - version "1.1.23" - resolved "https://registry.yarnpkg.com/@oraichain/oraidex-universal-swap/-/oraidex-universal-swap-1.1.23.tgz#2f6c5485728f096cbc11fba517376737c899e610" - integrity sha512-gSk4B84kT2qGx7Nc2uqiRQevS1447DWgmNAOdx4mNDUB4a6nJ79ie/hYGYemddEv6UQ1T/ppao9oxVjWmBecmQ== +"@oraichain/oraidex-universal-swap@1.1.23-beta.1": + version "1.1.23-beta.1" + resolved "https://registry.yarnpkg.com/@oraichain/oraidex-universal-swap/-/oraidex-universal-swap-1.1.23-beta.1.tgz#8cc21ab2cccc09adecbe76cfcd28a21ff8140e8f" + integrity sha512-ET51KRav/gfXxoqM/3jB8ueLJcXY/GMnKMNq0q+ORTwu63ww5U8svYyljigCzoJlPlIU80zpwRkbLV61Gl6qOg== dependencies: "@oraichain/common" "^1.0.3" - "@oraichain/oraidex-common" "^1.1.27" + "@oraichain/oraidex-common" "1.1.32-beta.8" "@oraichain/oraidex-contracts-sdk" "^1.0.49" "@oraichain/osor-api-contracts-sdk" "^1.0.2" bech32 "1.1.4" @@ -3922,11 +3922,49 @@ "@cosmjs/crypto" "0.31.3" "@cosmjs/proto-signing" "0.31.3" +"@oraichain/ton-bridge-contracts@^0.15.8": + version "0.15.9" + resolved "https://registry.yarnpkg.com/@oraichain/ton-bridge-contracts/-/ton-bridge-contracts-0.15.9.tgz#279f78ac434d5df69ccf2e51c01e57484789e91e" + integrity sha512-M4ukzlCkPRS+3S+05g/P+82fXjBCe05kBqpJiaZkUDAN7UDbRQJH480AZWl8HotPiM1Ae2TKNl8GPivNmzmwRA== + dependencies: + "@cosmjs/tendermint-rpc" "^0.32.4" + "@orbs-network/ton-access" "^2.3.3" + "@types/varstruct" "^6.1.3" + cosmjs-types "^0.9.0" + dotenv "^16.4.5" + varstruct "^6.1.3" + +"@oraichain/tonbridge-contracts-sdk@^1.3.1", "@oraichain/tonbridge-contracts-sdk@^1.3.6": + version "1.3.6" + resolved "https://registry.yarnpkg.com/@oraichain/tonbridge-contracts-sdk/-/tonbridge-contracts-sdk-1.3.6.tgz#1ee1309e6bb8e186eaa5d706c7d32276ef976402" + integrity sha512-k1DgzfYg3nQNPn2dLiXbkDHXd1WREQA9Dl0ezQ5kPtnlbNK9XjmxSco2gmaJwql0tE3cbBExUflfG0MaRF0dWw== + +"@oraichain/tonbridge-sdk@^1.3.6": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@oraichain/tonbridge-sdk/-/tonbridge-sdk-1.4.2.tgz#05722298dfd0bdbc4feaa45eb652d98361619638" + integrity sha512-pMj/y76S87b/GT4cNjp0oZ+J86tb/vPE0Kcm9vfi9SIMMYeWsk/H5AVlzoYevPrZflkzIA6l/vx+oHkMxpoVyQ== + dependencies: + "@oraichain/common-contracts-sdk" "^1.0.31" + "@oraichain/oraidex-common" "^1.1.6" + "@oraichain/ton-bridge-contracts" "^0.15.8" + "@oraichain/tonbridge-contracts-sdk" "^1.3.6" + "@orbs-network/ton-access" "^2.3.3" + "@ton/core" "^0.56.3" + "@ton/ton" "^14.0.0" + "@tonconnect/ui-react" "^2.0.9" + "@oraichain/wasm-json-toolkit@^1.0.24": version "1.0.24" resolved "https://registry.yarnpkg.com/@oraichain/wasm-json-toolkit/-/wasm-json-toolkit-1.0.24.tgz#e9a431560e8e946fbb7ec257e5f13d9320ffd23a" integrity sha512-j+8gN3dE3rqaiEUVVblt0dfJrE6RIsSkfqF08ISxRvHkH9Pe9exIOgxpCyd2Qn3liHj27hwth6R0ELw7y3QcLg== +"@orbs-network/ton-access@^2.3.3": + version "2.3.3" + resolved "https://registry.yarnpkg.com/@orbs-network/ton-access/-/ton-access-2.3.3.tgz#20292f83b52a75f451f185821180a7d9b829bcd3" + integrity sha512-b1miCPts7wBG9JKYgzXIRZQm/LMy5Uk1mNK8NzlcXHL3HRHJkkFbuYJGuj3IkWCiIicW3Ipp4sYnn3Fwo4oB0g== + dependencies: + isomorphic-fetch "^3.0.0" + "@parcel/watcher-android-arm64@2.5.0": version "2.5.0" resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz#e32d3dda6647791ee930556aee206fcd5ea0fb7a" @@ -5489,6 +5527,114 @@ dependencies: tippy.js "^6.3.1" +"@ton/core@^0.56.3": + version "0.56.3" + resolved "https://registry.yarnpkg.com/@ton/core/-/core-0.56.3.tgz#1162764573abb76032eba70f8497e5cb2ea532ee" + integrity sha512-HVkalfqw8zqLLPehtq0CNhu5KjVzc7IrbDwDHPjGoOSXmnqSobiWj8a5F+YuWnZnEbQKtrnMGNOOjVw4LG37rg== + dependencies: + symbol.inspect "1.0.1" + +"@ton/crypto-primitives@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@ton/crypto-primitives/-/crypto-primitives-2.1.0.tgz#8c9277c250b59aae3c819e0d6bd61e44d998e9ca" + integrity sha512-PQesoyPgqyI6vzYtCXw4/ZzevePc4VGcJtFwf08v10OevVJHVfW238KBdpj1kEDQkxWLeuNHEpTECNFKnP6tow== + dependencies: + jssha "3.2.0" + +"@ton/crypto@^3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@ton/crypto/-/crypto-3.3.0.tgz#019103df6540fbc1d8102979b4587bc85ff9779e" + integrity sha512-/A6CYGgA/H36OZ9BbTaGerKtzWp50rg67ZCH2oIjV1NcrBaCK9Z343M+CxedvM7Haf3f/Ee9EhxyeTp0GKMUpA== + dependencies: + "@ton/crypto-primitives" "2.1.0" + jssha "3.2.0" + tweetnacl "1.0.3" + +"@ton/ton@^14.0.0": + version "14.0.0" + resolved "https://registry.yarnpkg.com/@ton/ton/-/ton-14.0.0.tgz#a66fbbfb159200892557442039a0341cda71fc2d" + integrity sha512-xb2CY6U0AlHUKc7DV7xK/K4Gqn6YoR253yUrM2E7L5WegVFsDF0CQRUIfpYACCuj1oUywQc5J2oMolYNu/uGkA== + dependencies: + axios "^1.6.7" + dataloader "^2.0.0" + symbol.inspect "1.0.1" + teslabot "^1.3.0" + zod "^3.21.4" + +"@tonconnect/isomorphic-eventsource@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@tonconnect/isomorphic-eventsource/-/isomorphic-eventsource-0.0.2.tgz#e58c44cf9953e090f2c35da9a638946ddb614be5" + integrity sha512-B4UoIjPi0QkvIzZH5fV3BQLWrqSYABdrzZQSI9sJA9aA+iC0ohOzFwVVGXanlxeDAy1bcvPbb29f6sVUk0UnnQ== + dependencies: + eventsource "^2.0.2" + +"@tonconnect/isomorphic-fetch@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@tonconnect/isomorphic-fetch/-/isomorphic-fetch-0.0.3.tgz#31978e04ddc4428eff532c23d20229ed5ddb6417" + integrity sha512-jIg5nTrDwnite4fXao3dD83eCpTvInTjZon/rZZrIftIegh4XxyVb5G2mpMqXrVGk1e8SVXm3Kj5OtfMplQs0w== + dependencies: + node-fetch "^2.6.9" + +"@tonconnect/protocol@^2.2.6": + version "2.2.6" + resolved "https://registry.yarnpkg.com/@tonconnect/protocol/-/protocol-2.2.6.tgz#24b3fbcde6003e65fb5840a190072db5378699db" + integrity sha512-kyoDz5EqgsycYP+A+JbVsAUYHNT059BCrK+m0pqxykMODwpziuSAXfwAZmHcg8v7NB9VKYbdFY55xKeXOuEd0w== + dependencies: + tweetnacl "^1.0.3" + tweetnacl-util "^0.15.1" + +"@tonconnect/sdk@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@tonconnect/sdk/-/sdk-3.0.3.tgz#0cc6a570817194c648193f7a1db525419c570e3f" + integrity sha512-ElVre1DPixzQLgLtQIa8Wu5xS8nozlgblZTJhFFPrk82M2rZ+sawyF+LAVwt9wZRN7+htWnJrNz0+bBr4b3geA== + dependencies: + "@tonconnect/isomorphic-eventsource" "^0.0.2" + "@tonconnect/isomorphic-fetch" "^0.0.3" + "@tonconnect/protocol" "^2.2.6" + +"@tonconnect/sdk@3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@tonconnect/sdk/-/sdk-3.0.5.tgz#08a202bdc8ea897c37221fd69925c35cd2106323" + integrity sha512-ow0qnN4s3iQ/r2uXobZ7YzdQBtan/36CgCT9IP35G07g38UxsUXwzw8ANmJTDj/JPiQcIKuYBMfIwIX9zLM0wg== + dependencies: + "@tonconnect/isomorphic-eventsource" "^0.0.2" + "@tonconnect/isomorphic-fetch" "^0.0.3" + "@tonconnect/protocol" "^2.2.6" + +"@tonconnect/ui-react@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@tonconnect/ui-react/-/ui-react-2.0.6.tgz#9adadda97da0c8ff2e40eee5dbaa1693aa6662b5" + integrity sha512-9VRYP2o/YW4ti+rOMSptIYaRO39V5UFmzdUl18AmDFDdynjacAIht/IInO+AGAKfrnF9bUk/J5NgLcbxVqvZww== + dependencies: + "@tonconnect/ui" "2.0.6" + +"@tonconnect/ui-react@^2.0.9": + version "2.0.9" + resolved "https://registry.yarnpkg.com/@tonconnect/ui-react/-/ui-react-2.0.9.tgz#c226b81110e05b7e259bbf965ad5da42318a5dd3" + integrity sha512-wN7tEZpQiRYSUcdNAxFsDkk5TYo8krIu00ZLE1R5kXyr+XpO120jOmTEweBSXvIzTgEVkD/PxDZbBQQxRTXsUw== + dependencies: + "@tonconnect/ui" "2.0.9" + +"@tonconnect/ui@2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@tonconnect/ui/-/ui-2.0.6.tgz#4cf092b3ff138238fa7ae7d4754d8bfed549d892" + integrity sha512-JMTSxgbnpDjpHi9g0s7w3tpAiLHa8BHUpaBrbSTDcKGQVEm7+NtqhN+gQkGkm8pV7NYRqiS/sKUpRQ1MyjtTBQ== + dependencies: + "@tonconnect/sdk" "3.0.3" + classnames "^2.3.2" + deepmerge "^4.2.2" + ua-parser-js "^1.0.35" + +"@tonconnect/ui@2.0.9": + version "2.0.9" + resolved "https://registry.yarnpkg.com/@tonconnect/ui/-/ui-2.0.9.tgz#589285c9b8f4b0d94c10b3feadfae266bf086503" + integrity sha512-ZxofTBf81NqrxyD0ybI8AuFHN11uKVg/00xTDFhP5FoPB8rYC7En9qE2VJ6IvwvtTpmh8jspi2ancOHUMBoCQA== + dependencies: + "@tonconnect/sdk" "3.0.5" + classnames "^2.3.2" + deepmerge "^4.2.2" + ua-parser-js "^1.0.35" + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" @@ -6267,6 +6413,13 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== +"@types/varstruct@^6.1.3": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@types/varstruct/-/varstruct-6.1.3.tgz#dad39ad875eef0a9b9d63ae4e503cc3d5c456955" + integrity sha512-LgivtaEn0RHmVk+CHnIS95CtkV0xMAT6u1hPlr6qXNmYnDUU7ZhXM5fM7/byytaqXk7fNX8B/ibN1xpxe+X+hg== + dependencies: + "@types/node" "*" + "@types/w3c-web-usb@^1.0.6": version "1.0.10" resolved "https://registry.yarnpkg.com/@types/w3c-web-usb/-/w3c-web-usb-1.0.10.tgz#cf89cccd2d93b6245e784c19afe0a9f5038d4528" @@ -8646,7 +8799,7 @@ cjson@^0.3.1: dependencies: json-parse-helpfulerror "^1.0.3" -classnames@*, classnames@^2.2.6, classnames@^2.3.1: +classnames@*, classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2: version "2.5.1" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== @@ -9467,7 +9620,7 @@ data-urls@^5.0.0: whatwg-mimetype "^4.0.0" whatwg-url "^14.0.0" -dataloader@^2.2.2: +dataloader@^2.0.0, dataloader@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.2.2.tgz#216dc509b5abe39d43a9b9d97e6e5e473dfbe3e0" integrity sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g== @@ -9565,7 +9718,7 @@ deep-is@^0.1.3, deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -deepmerge@^4.3.1: +deepmerge@^4.2.2, deepmerge@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== @@ -9765,6 +9918,11 @@ dotenv@^16.0.0, dotenv@^16.3.1: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== +dotenv@^16.4.5: + version "16.4.7" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" + integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== + dotenv@^8.2.0: version "8.6.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" @@ -10489,6 +10647,11 @@ events@3.3.0, events@^3.0.0, events@^3.3.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== +eventsource@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-2.0.2.tgz#76dfcc02930fb2ff339520b6d290da573a9e8508" + integrity sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA== + evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" @@ -11871,6 +12034,11 @@ install-artifact-from-github@^1.3.5: resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.3.5.tgz#88c96fe40e5eb21d45586d564208c648a1dbf38d" integrity sha512-gZHC7f/cJgXz7MXlHFBxPVMsvIbev1OQN1uKQYKVJDydGNm9oYf9JstbU4Atnh/eSvk41WtEovoRm+8IF686xg== +int53@^0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/int53/-/int53-0.2.4.tgz#5ed8d7aad6c5c6567cae69aa7ffc4a109ee80f86" + integrity sha512-a5jlKftS7HUOhkUyYD7j2sJ/ZnvWiNlZS1ldR+g1ifQ+/UuZXIE+YTc/lK1qGj/GwAU5F8Z0e1eVq2t1J5Ob2g== + int64-buffer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/int64-buffer/-/int64-buffer-1.0.1.tgz#c78d841b444cadf036cd04f8683696c740f15dca" @@ -12736,6 +12904,11 @@ jsqr@^1.2.0: resolved "https://registry.yarnpkg.com/jsqr/-/jsqr-1.4.0.tgz#8efb8d0a7cc6863cb6d95116b9069123ce9eb2d1" integrity sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A== +jssha@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jssha/-/jssha-3.2.0.tgz#88ec50b866dd1411deaddbe6b3e3692e4c710f16" + integrity sha512-QuruyBENDWdN4tZwJbQq7/eAK85FqrI4oDbXjy5IBhYD+2pTJyBUWZe8ctWaCkrV0gy6AaelgOZZBMeswEa/6Q== + jwa@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" @@ -16657,6 +16830,11 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== +symbol.inspect@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/symbol.inspect/-/symbol.inspect-1.0.1.tgz#e13125b8038c4996eb0dfa1567332ad4dcd0763f" + integrity sha512-YQSL4duoHmLhsTD1Pw8RW6TZ5MaTX5rXJnqacJottr2P2LZBF/Yvrc3ku4NUpMOm8aM0KOCqM+UAkMA5HWQCzQ== + system-architecture@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/system-architecture/-/system-architecture-0.1.0.tgz#71012b3ac141427d97c67c56bc7921af6bff122d" @@ -16714,6 +16892,11 @@ temp@^0.9.4: mkdirp "^0.5.1" rimraf "~2.6.2" +teslabot@^1.3.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/teslabot/-/teslabot-1.5.0.tgz#70f544516699ca5f696d8ae94f3d12cd495d5cd6" + integrity sha512-e2MmELhCgrgZEGo7PQu/6bmYG36IDH+YrBI1iGm6jovXkeDIGa3pZ2WSqRjzkuw2vt1EqfkZoV5GpXgqL8QJVg== + test-exclude@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-7.0.1.tgz#20b3ba4906ac20994e275bbcafd68d510264c2a2" @@ -17059,16 +17242,16 @@ tweetnacl-util@^0.15.1: resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz#b80fcdb5c97bcc508be18c44a4be50f022eea00b" integrity sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw== +tweetnacl@1.0.3, tweetnacl@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" + integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== -tweetnacl@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" - integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== - type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" @@ -17536,6 +17719,14 @@ value-or-promise@^1.0.11, value-or-promise@^1.0.12: resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.12.tgz#0e5abfeec70148c78460a849f6b003ea7986f15c" integrity sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q== +varstruct@^6.1.3: + version "6.1.3" + resolved "https://registry.yarnpkg.com/varstruct/-/varstruct-6.1.3.tgz#45a8a073e41fb88adb10bc71158c945e20c57fbe" + integrity sha512-4l1Q7uxrVUBZXsMcb2cakrZL6gd4G+Ykn/m9cGnT4EY8iRBPkxOxKVDwOnL9AsIPKmREBx5BDqjfNMKKP6Zy2w== + dependencies: + int53 "^0.2.4" + safe-buffer "^5.1.1" + varuint-bitcoin@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/varuint-bitcoin/-/varuint-bitcoin-2.0.0.tgz#59a53845a87ad18c42f184a3d325074465341523" @@ -18160,3 +18351,8 @@ zip-stream@^4.1.0: archiver-utils "^3.0.4" compress-commons "^4.1.2" readable-stream "^3.6.0" + +zod@^3.21.4: + version "3.23.8" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" + integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==