From 6d2530f3f0d3678b7670262cd56360b6f3b7e201 Mon Sep 17 00:00:00 2001 From: envin Date: Thu, 25 Jan 2024 19:53:58 +0100 Subject: [PATCH] feat(ethPNT): add support to ethPNT in swaps --- .../organisms/assetInfo/AssetInfo.jsx | 43 +++++++++++-- .../assetListModal/AssetListModal.jsx | 13 +++- .../organisms/swapLine/SwapLine.jsx | 61 +++++++++++++++++-- src/components/pages/migration/Migration.jsx | 21 ++++--- src/components/pages/swap/Swap.jsx | 37 ++++++----- src/hooks/use-assets.js | 29 ++++++++- src/settings/swap-assets.js | 20 ++++++ src/store/swap/swap.actions.js | 7 ++- src/store/swap/utils/default-selection.js | 4 +- src/utils/fee.js | 10 ++- src/utils/ptokens.js | 28 ++++++++- src/utils/swap-valildator.js | 5 +- 12 files changed, 227 insertions(+), 51 deletions(-) diff --git a/src/components/organisms/assetInfo/AssetInfo.jsx b/src/components/organisms/assetInfo/AssetInfo.jsx index 3dffccbb..5751d4ac 100644 --- a/src/components/organisms/assetInfo/AssetInfo.jsx +++ b/src/components/organisms/assetInfo/AssetInfo.jsx @@ -1,14 +1,36 @@ -import React, { useState, useEffect, useCallback } from 'react' -import styled from 'styled-components' import PropTypes from 'prop-types' +import React, { useState, useEffect, useCallback } from 'react' import { Row, Col } from 'react-bootstrap' import ReactTooltip from 'react-tooltip' -import Icon from '../../atoms/icon/Icon' -import { copyToClipboard } from '../../../utils/utils' -import { registerToken } from '../../../utils/wallet' -import { getBase64Image } from '../../../utils/image' +import styled from 'styled-components' + +import { ETHPNT_ON_ETH_MAINNET } from '../../../constants' import { useProvider } from '../../../hooks/use-provider' import { capitalizeAllLettersExceptFirst } from '../../../utils/capitalize' +import { getBase64Image } from '../../../utils/image' +import { copyToClipboard } from '../../../utils/utils' +import { registerToken } from '../../../utils/wallet' +import Icon from '../../atoms/icon/Icon' + +const InfoEta = styled.div` + margin-top: 30px; + padding: 20px; + margin-bottom: 10px; + background: #66b8ff40; + border: 0.5px solid ${({ theme }) => theme.blue}; + border-radius: 10px; + color: ${({ theme }) => theme.blue}; + text-align: center; +` + +const InflationInfo = styled(InfoEta)` + width: 100%; + margin-top: 10px; + margin-bottom: 0px; + margin-right: 10px; + margin-left: 10px; + padding: 5px; +` const ContainerAssetInfo = styled(Col)` margin-top: 10px; @@ -120,6 +142,15 @@ const AssetInfo = ({ asset, wallet }) => { + {asset.id === ETHPNT_ON_ETH_MAINNET ? ( + + + ethPNT is an extension of the PNT token with cross-chain functionality, maintaining the same token economy + and functionalities as PNT-on-Ethereum. ethPNT grants the same DAO governance power of PNT, inheriting its + full value. + + + ) : null} _dataTip === 'Add to MetaMask' || _dataTip === 'Connect MetaMask to add the token' diff --git a/src/components/organisms/assetListModal/AssetListModal.jsx b/src/components/organisms/assetListModal/AssetListModal.jsx index 6ee7735a..ee568732 100644 --- a/src/components/organisms/assetListModal/AssetListModal.jsx +++ b/src/components/organisms/assetListModal/AssetListModal.jsx @@ -1,9 +1,15 @@ +import PropTypes from 'prop-types' import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react' import { Col, Row, Spinner } from 'react-bootstrap' -import PropTypes from 'prop-types' import styled from 'styled-components' + +import { + useAssetsWithouDefault, + useSearchAssets, + useAssetsGroupedByGivenStrategy, + useSortPntInflationToken, +} from '../../../hooks/use-assets' import { getAssetFromSymbol } from '../../../utils/maps' -import { useAssetsWithouDefault, useSearchAssets, useAssetsGroupedByGivenStrategy } from '../../../hooks/use-assets' import Icon from '../../atoms/icon/Icon' import Modal from '../../molecules/modal/Modal' @@ -166,7 +172,8 @@ const StyledSpinner = styled(Spinner)` const AssetListModal = ({ show: showModal, title, onClose, onSelect, assets: _assets, defaultAssets }) => { const [assetsWithoutDefault] = useAssetsWithouDefault(_assets) const [filteredAssets, setSearchWord] = useSearchAssets(assetsWithoutDefault) - const [assets] = useAssetsGroupedByGivenStrategy(filteredAssets) + const [groupedAssets] = useAssetsGroupedByGivenStrategy(filteredAssets) + const assets = useSortPntInflationToken(groupedAssets) const [show, setShow] = useState([]) const inputSearchRef = useRef(null) diff --git a/src/components/organisms/swapLine/SwapLine.jsx b/src/components/organisms/swapLine/SwapLine.jsx index e490ad69..5af83777 100644 --- a/src/components/organisms/swapLine/SwapLine.jsx +++ b/src/components/organisms/swapLine/SwapLine.jsx @@ -2,15 +2,27 @@ import PropTypes from 'prop-types' import React, { useState, useEffect, useMemo } from 'react' import { Row, Col } from 'react-bootstrap' import Skeleton from 'react-loading-skeleton' +import 'react-loading-skeleton/dist/skeleton.css' import { NumericFormat } from 'react-number-format' import styled from 'styled-components' +import { ETHPNT_ON_ETH_MAINNET, PNT_ON_ETH_MAINNET } from '../../../constants' import { getDecimalSeparator, getThousandSeparator } from '../../../utils/amount-utils' import { capitalizeAllLettersExceptFirst } from '../../../utils/capitalize' import Icon from '../../atoms/icon/Icon' +import Switch from '../../atoms/switch/Switch' import AssetInfo from '../assetInfo/AssetInfo' -import 'react-loading-skeleton/dist/skeleton.css' +const SelectCol = styled(Col)` + text-align: center; + font-weight: 500; + padding-top: 10px; + margin-left: 6px; + color: ${({ theme }) => theme.text1}; + @media (max-width: 767.98px) { + font-size: 12px; + } +` const SwapLineContainer = styled.div` border-radius: 20px; @@ -198,6 +210,7 @@ const Arrow = styled(Icon)` const SwapLine = ({ asset, + assets, amount, address, defaultImage, @@ -211,6 +224,9 @@ const SwapLine = ({ onMax, withTitleLabel, disableInput, + selectFrom = () => null, + selectTo = () => null, + migration = false, inputType = 'number', inputPlaceholder = '0', prefix = '', @@ -218,15 +234,40 @@ const SwapLine = ({ }) => { const [showInfo, setShowInfo] = useState(false) const [id, setId] = useState(false) + const [selectEthPNT, setSelectEthPNT] = useState(false) // NOTE: avoid to close show info when asset is reloaded with the balance useEffect(() => { - if (asset && asset.id !== id) { + if (asset && asset.id !== id && asset.id !== ETHPNT_ON_ETH_MAINNET) { setShowInfo(false) setId(asset.id) } }, [asset, id]) + useEffect(() => { + if (asset && asset.id === ETHPNT_ON_ETH_MAINNET) { + setSelectEthPNT(true) + setShowInfo(true) + } else setSelectEthPNT(false) + }, [asset, id]) + + // This switch provides the ability to use ethPNT or PNT alternatively. + // When pressed the selected asset switch between ethPNt or PNT. + const switchPNT = () => { + if (migration) return + if (asset.id === ETHPNT_ON_ETH_MAINNET) { + const pnt = assets.find((asset) => asset.id === PNT_ON_ETH_MAINNET) + if (!address && address !== '') selectFrom(pnt) + } else { + const ethPnt = assets.find((asset) => asset.id === ETHPNT_ON_ETH_MAINNET) + if (!address && address !== '') selectFrom(ethPnt) + } + } + + const isPNTcase = asset?.id === PNT_ON_ETH_MAINNET || asset?.id === ETHPNT_ON_ETH_MAINNET + + const showPntSwitch = asset && isPNTcase && !migration && !address && address !== '' + const formattedTitle = useMemo(() => { if (!withTitleLabel || !asset || !asset.titleLabel) return title @@ -237,7 +278,7 @@ const SwapLine = ({ {formattedTitle} - {asset && asset.formattedBalance !== '-' ? ( + {asset && asset.formattedBalance && asset.formattedBalance !== '-' ? ( {`Balance: ${asset.formattedBalance} ${ @@ -254,12 +295,15 @@ const SwapLine = ({ onClickImage && onClickImage()}> - onClickImage && onClickImage()} /> + onClickImage && onClickImage()} + /> {(asset && asset.withMiniImage) || (!asset && defaultMiniImage) ? ( - + ) : null} {' '} - {asset && asset.formattedBalance !== '-' && !hideMaxButton ? ( + {asset && asset.formattedBalance && asset.formattedBalance !== '-' && !hideMaxButton ? ( MAX @@ -287,6 +331,11 @@ const SwapLine = ({ + {showPntSwitch ? ( + + + + ) : null} setShowInfo(!showInfo)}> {asset && asset.address ? ( diff --git a/src/components/pages/migration/Migration.jsx b/src/components/pages/migration/Migration.jsx index c2ed8ef2..edb441b0 100644 --- a/src/components/pages/migration/Migration.jsx +++ b/src/components/pages/migration/Migration.jsx @@ -1,10 +1,16 @@ -import React from 'react' -import styled from 'styled-components' import PropTypes from 'prop-types' +import React from 'react' import { Row, Col, Container } from 'react-bootstrap' -import SwapLine from '../../organisms/swapLine/SwapLine' -import { useMigration } from '../../../hooks/use-migration' +import styled from 'styled-components' + +import { PBTC_ON_ETH_MAINNET_V2_MIGRATION, PNT_ON_ETH_MAINNET } from '../../../constants' import { useAssets } from '../../../hooks/use-assets' +import { useMigration } from '../../../hooks/use-migration' +import Button from '../../atoms/button/Button' +import Progress from '../../molecules/progress/Progress' +import InfoModal from '../../organisms/infoModal/InfoModal' +import MigrationInfo from '../../organisms/migrationInfo/MigrationInfo' +import SwapLine from '../../organisms/swapLine/SwapLine' import { OuterContainerSwap, ContainerSwap, @@ -13,11 +19,6 @@ import { ArrowContainer, SortIcon, } from '../swap/Swap' -import Button from '../../atoms/button/Button' -import Progress from '../../molecules/progress/Progress' -import InfoModal from '../../organisms/infoModal/InfoModal' -import MigrationInfo from '../../organisms/migrationInfo/MigrationInfo' -import { PBTC_ON_ETH_MAINNET_V2_MIGRATION, PNT_ON_ETH_MAINNET } from '../../../constants' const ArrowIcon = styled(SortIcon)` cursor: normal !important; @@ -91,6 +92,7 @@ const Migration = ({ asset={from} amount={fromAmount} wallet={fromWallet} + migration={true} onChangeAmount={onChangeFromAmount} onMax={onFromMax} withTitleLabel @@ -106,6 +108,7 @@ const Migration = ({ amount={toAmount} address={address} wallet={toWallet} + migration={true} inputType={'text'} onChangeAmount={onChangeToAmount} onChangeAddress={setAddress} diff --git a/src/components/pages/swap/Swap.jsx b/src/components/pages/swap/Swap.jsx index cade9f52..7e596bc9 100644 --- a/src/components/pages/swap/Swap.jsx +++ b/src/components/pages/swap/Swap.jsx @@ -1,26 +1,27 @@ -import React, { useCallback, useEffect, useState } from 'react' -import styled from 'styled-components' -import PropTypes from 'prop-types' import BigNumber from 'bignumber.js' +import PropTypes from 'prop-types' +import React, { useCallback, useEffect, useState } from 'react' import { Row, Col, Container } from 'react-bootstrap' -import AssetListModal from '../../organisms/assetListModal/AssetListModal' -import Progress from '../../molecules/progress/Progress' +import ReactTooltip from 'react-tooltip' +import styled from 'styled-components' + +import TermsOfService from '../../../components/molecules/popup/TermsOfService' +import { MAX_IMPACT, PBTC_ON_ETH_MAINNET_V1_MIGRATION } from '../../../constants' +import { sendEvent } from '../../../ga4' +import { useAssets } from '../../../hooks/use-assets' import { useSwap } from '../../../hooks/use-swap' -import SwapLine from '../../organisms/swapLine/SwapLine' -import DepositAddressModal from '../../organisms/depositAddressModal/DepositAddressModal' -import SwapInfo from '../../organisms/swapInfo/SwapInfo' import defaultAssets from '../../../settings/swap-assets' -import { useAssets } from '../../../hooks/use-assets' +import Button from '../../atoms/button/Button' import Icon from '../../atoms/icon/Icon' -import InfoModal from '../../organisms/infoModal/InfoModal' -import TermsOfService from '../../../components/molecules/popup/TermsOfService' +import Switch from '../../atoms/switch/Switch' import AddressWarning from '../../molecules/popup/AddressWarning' import WarningPopup from '../../molecules/popup/Warning' -import Switch from '../../atoms/switch/Switch' -import Button from '../../atoms/button/Button' -import { MAX_IMPACT, PBTC_ON_ETH_MAINNET_V1_MIGRATION } from '../../../constants' -import { sendEvent } from '../../../ga4' -import ReactTooltip from 'react-tooltip' +import Progress from '../../molecules/progress/Progress' +import AssetListModal from '../../organisms/assetListModal/AssetListModal' +import DepositAddressModal from '../../organisms/depositAddressModal/DepositAddressModal' +import InfoModal from '../../organisms/infoModal/InfoModal' +import SwapInfo from '../../organisms/swapInfo/SwapInfo' +import SwapLine from '../../organisms/swapLine/SwapLine' export const OuterContainerSwap = styled.div` @media (max-width: 767.98px) { @@ -342,9 +343,11 @@ const Swap = ({ defaultImage="./assets/svg/BTC.svg" title="From" asset={from} + assets={assets.length === 0 ? defaultAssets : assets} amount={fromAmount} wallet={fromWallet} disableInput={disableFromInput} + selectFrom={onSelectFrom} onChangeAmount={onChangeFromAmount} onClickImage={() => setShowModalFrom(true)} onMax={onFromMax} @@ -358,10 +361,12 @@ const Swap = ({ defaultMiniImage="./assets/svg/ETH.svg" title="To" asset={to} + assets={assets.length === 0 ? defaultAssets : assets} amount={toAmount} address={address} wallet={toWallet} disableInput={disableToInput} + selectTo={onSelectTo} onChangeAmount={onChangeToAmount} onClickImage={() => setShowModalTo(true)} onChangeAddress={setAddress} diff --git a/src/hooks/use-assets.js b/src/hooks/use-assets.js index ec25a307..29bbf5bf 100644 --- a/src/hooks/use-assets.js +++ b/src/hooks/use-assets.js @@ -46,6 +46,26 @@ const useAssetsGroupedByGivenStrategy = (_assets) => { }, [_assets]) } +/* + * This function is used to manage ethPNT as an extension of PNT and not as a standalone token in the Modal List. + * + * ethPNT is an extension of PNT so it must be removed from the modal list somehow. The easiest way found in order to not + * disrupt every other function which uses `swap-assets.js` list structure is to manipulate the array in the very + * component that displays the list. + */ +const useSortPntInflationToken = (_assets) => { + return useMemo(() => { + const pntExtendingTokens = Object.values(_assets) + .flatMap((array) => array) + .filter((asset) => asset.extendsPnt === true) + if (pntExtendingTokens) + return Object.fromEntries( + Object.entries(_assets).filter(([key]) => !pntExtendingTokens.some((token) => token.nativeSymbol === key)) + ) + return _assets + }, [_assets]) +} + const useSearchAssets = (_assets) => { const [searchWord, setSearchWord] = useState('') @@ -100,4 +120,11 @@ const updateAsset = (_asset) => ({ miniImage: `./assets/svg/${_asset.miniImage || _asset.blockchain}.svg`, }) -export { useAssets, useAssetsWithouDefault, usePtoken, useAssetsGroupedByGivenStrategy, useSearchAssets } +export { + useAssets, + useAssetsWithouDefault, + usePtoken, + useAssetsGroupedByGivenStrategy, + useSearchAssets, + useSortPntInflationToken, +} diff --git a/src/settings/swap-assets.js b/src/settings/swap-assets.js index babae1de..bef396a7 100644 --- a/src/settings/swap-assets.js +++ b/src/settings/swap-assets.js @@ -1123,6 +1123,26 @@ const swapAssets = [ withBalanceDecimalsConversion: true, chainId: ChainId.EthereumMainnet, }, + { + address: '0xf4eA6B892853413bD9d9f1a5D3a620A0ba39c5b2', + id: 'ETHPNT_ON_ETH_MAINNET', + symbol: 'ethPNT', + name: 'eth-pNetwork', + network: 'mainnet', + blockchain: 'ETH', + decimals: 18, + nativeDecimals: 18, + withMiniImage: true, + isNative: true, + nativeSymbol: 'ethPNT', + nativeBlockchain: 'ETH', + image: 'PNT.svg', + withBalanceDecimalsConversion: true, + titleLabel: 'ethPNT', + chainId: ChainId.EthereumMainnet, + extendsPnt: true, + onPnetworkV2: true, + }, { address: '0x02eca910cb3a7d43ebc7e8028652ed5c6b70259b', id: 'PTERIA', diff --git a/src/store/swap/swap.actions.js b/src/store/swap/swap.actions.js index b48d257a..f6fd0307 100644 --- a/src/store/swap/swap.actions.js +++ b/src/store/swap/swap.actions.js @@ -12,12 +12,13 @@ import { UPDATE_SWAP_BUTTON, BPM_LOADED, SWAPPERS_BALANCES_LOADED, + ETHPNT_ON_ETH_MAINNET, } from '../../constants/index' import settings from '../../settings' import assets from '../../settings/swap-assets' import eosioTokenAbi from '../../utils/abi/eosio.token' import { parseError } from '../../utils/errors' -import { createAsset, getSwapBuilder } from '../../utils/ptokens' +import { createAsset, createEthPntAsset, getSwapBuilder } from '../../utils/ptokens' import { getReadOnlyProviderByBlockchain } from '../../utils/read-only-providers' import { updateInfoModal } from '../pages/pages.actions' import { getWallets, getWalletByBlockchain } from '../wallets/wallets.selectors' @@ -329,8 +330,8 @@ const swap = (_from, _to, _amount, _address, _opts = {}) => { if (_from.requiresCurve) { _from = getAssetById(_fromNative.pTokenId) } - - const sourceAsset = await createAsset(_from, wallets, true) + const sourceAsset = + _from.id === ETHPNT_ON_ETH_MAINNET ? await createEthPntAsset() : await createAsset(_from, wallets) const destinationAsset = await createAsset(_to, wallets) const swapBuilder = getSwapBuilder() swapBuilder.setAmount(_amount) diff --git a/src/store/swap/utils/default-selection.js b/src/store/swap/utils/default-selection.js index 1f14cfaa..4adc0a50 100644 --- a/src/store/swap/utils/default-selection.js +++ b/src/store/swap/utils/default-selection.js @@ -50,7 +50,9 @@ const getDefaultSelectionV2 = ( const assetTo = _assets.find( ({ nativeSymbol, blockchain, address }) => - nativeSymbol.toLowerCase() === asset.toLowerCase() && + (asset.toLowerCase() === 'ethpnt' + ? nativeSymbol.toLowerCase() === 'pnt' + : nativeSymbol.toLowerCase() === asset.toLowerCase()) && blockchain.toLowerCase() === to.toLowerCase() && (algorand_to_assetid && blockchain === 'ALGORAND' ? address === algorand_to_assetid : true) ) diff --git a/src/utils/fee.js b/src/utils/fee.js index fef8d6b3..77a4efb3 100644 --- a/src/utils/fee.js +++ b/src/utils/fee.js @@ -1,8 +1,11 @@ import BigNumber from 'bignumber.js' +import _ from 'lodash' + +import { PNT_ON_ETH_MAINNET, ETHPNT_ON_ETH_MAINNET, PBTC_ON_ETH_MAINNET_V1_MIGRATION } from '../constants' +import swapAssets from '../settings/swap-assets' + import { formatDecimalSeparator } from './amount-utils' import { createAsset } from './ptokens' -import { PNT_ON_ETH_MAINNET, ETHPNT_ON_ETH_MAINNET, PBTC_ON_ETH_MAINNET_V1_MIGRATION } from '../constants' -import _ from 'lodash' const getFeeFactor = (fee) => (_.isNil(fee) ? null : 1 - fee / 100) @@ -24,8 +27,9 @@ const getMigrationFees = (_from, _to) => { } const getSwapFees = async (_from, _to) => { + const from = _from.id === ETHPNT_ON_ETH_MAINNET ? swapAssets.find((asset) => asset.id === PNT_ON_ETH_MAINNET) : _from try { - const fromAsset = await createAsset(_from) + const fromAsset = await createAsset(from) const toAsset = await createAsset(_to) const basisPoints = getBasisPoints(fromAsset, toAsset) const networkFee = toAsset.assetInfo.fees.networkFee diff --git a/src/utils/ptokens.js b/src/utils/ptokens.js index c487da2f..ebdfd5d3 100644 --- a/src/utils/ptokens.js +++ b/src/utils/ptokens.js @@ -1,12 +1,14 @@ import _ from 'lodash' import { pTokensAlgorandAssetBuilder, pTokensAlgorandProvider } from 'ptokens-assets-algorand' import { pTokensEosioAssetBuilder, pTokensEosioProvider } from 'ptokens-assets-eosio' -import { pTokensEvmAssetBuilder, pTokensEvmProvider } from 'ptokens-assets-evm' +import { pTokensEvmAssetBuilder, pTokensEvmProvider, pTokensEvmAsset } from 'ptokens-assets-evm' import { pTokensUtxoAssetBuilder, pTokensBlockstreamUtxoProvider } from 'ptokens-assets-utxo' import { pTokensNode, pTokensNodeProvider } from 'ptokens-node' import { pTokensSwapBuilder } from 'ptokens-swap' -import { PNETWORK_NODE_V3 } from '../constants/index' +import { ETHPNT_ON_ETH_MAINNET, PNETWORK_NODE_V3 } from '../constants/index' +import swapAssets from '../settings/swap-assets' +import { getWalletByBlockchain } from '../store/wallets/wallets.selectors' import { getReadOnlyProviderByBlockchain } from './read-only-providers' @@ -72,6 +74,28 @@ export const createAsset = async (_asset, _wallets) => { return asset } +export const createEthPntAsset = async () => { + const ethPnt = swapAssets.find((asset) => asset.id === ETHPNT_ON_ETH_MAINNET) + if (!ethPnt) throw new Error('ethPnt not found') + const wallet = getWalletByBlockchain(ethPnt.blockchain) + const provider = new pTokensNodeProvider(PNETWORK_NODE_V3) + const node = new pTokensNode(provider) + // Here _to is used for the symbol in order to get PNT assetInfo. + // ethPNT is not directly supported and it is used as PNT only modifying the contract address. + const assetInfo = await node.getAssetInfoByChainId('PNT', ethPnt.chainId) + const providerInfo = new pTokensEvmProvider( + wallet.provider || getReadOnlyProviderByBlockchain(ethPnt.blockchain.toUpperCase()) + ) + const config = { + node: node, + assetInfo: { ...assetInfo, tokenAddress: ethPnt.address, decimals: ethPnt.decimals }, + symbol: ethPnt.symbol, + chainId: ethPnt.chainId, + provider: providerInfo, + } + return new pTokensEvmAsset(config) +} + export const getSwapBuilder = () => { const node = getNode() return new pTokensSwapBuilder(node) diff --git a/src/utils/swap-valildator.js b/src/utils/swap-valildator.js index 342e22a8..2f7b6794 100644 --- a/src/utils/swap-valildator.js +++ b/src/utils/swap-valildator.js @@ -1,9 +1,12 @@ import _ from 'lodash' +import { ETHPNT_ON_ETH_MAINNET } from '../constants' + export const isValidSwap = (from, to, assets) => { if (_.isNil(from) || _.isNil(to)) return false if (to.id === from.id) return false - if (!assets.find(({ id }) => from.id === id) && !assets.find(({ id }) => to.id === id)) return false + if (!assets.find(({ id }) => from.id === id) || !assets.find(({ id }) => to.id === id)) return false + if (from.id === ETHPNT_ON_ETH_MAINNET && to.nativeSymbol === 'PNT' && !to.isNative) return true if (to.isHidden) return false if (to.nativeSymbol.toLowerCase() !== from.nativeSymbol.toLowerCase()) return false if (from.id === 'TLOS_ON_ETH_MAINNET' && to.id !== 'TLOS') return false