From db991c4509c937a534ac1227b1e8f82f898b3d70 Mon Sep 17 00:00:00 2001 From: saml33 Date: Mon, 16 May 2022 21:56:28 +1000 Subject: [PATCH 1/3] market selection on mobile --- components/MarketNavItem.tsx | 2 +- components/MarketsTable.tsx | 22 +++- components/SwitchMarketDropdown.tsx | 8 +- components/mobile/BottomBar.tsx | 4 +- components/mobile/MobileTradePage.tsx | 46 +-------- components/trade_form/AdvancedTradeForm.tsx | 95 ++++++++---------- pages/markets.tsx | 21 ++-- pages/select.tsx | 105 -------------------- 8 files changed, 80 insertions(+), 223 deletions(-) delete mode 100644 pages/select.tsx diff --git a/components/MarketNavItem.tsx b/components/MarketNavItem.tsx index 827502a1..ad58bab7 100644 --- a/components/MarketNavItem.tsx +++ b/components/MarketNavItem.tsx @@ -91,7 +91,7 @@ const MarketNavItem: FunctionComponent = ({ ) : null} -
+
diff --git a/components/MarketsTable.tsx b/components/MarketsTable.tsx index 8303e501..a032ad6f 100644 --- a/components/MarketsTable.tsx +++ b/components/MarketsTable.tsx @@ -10,14 +10,16 @@ import MobileTableHeader from './mobile/MobileTableHeader' import { ExpandableRow } from './TableElements' import { FavoriteMarketButton } from './TradeNavMenu' import { useSortableData } from '../hooks/useSortableData' -import { LinkButton } from './Button' +import Button, { LinkButton } from './Button' import { ArrowSmDownIcon } from '@heroicons/react/solid' +import { useRouter } from 'next/router' const MarketsTable = ({ isPerpMarket }) => { const { t } = useTranslation('common') const { width } = useViewport() const isMobile = width ? width < breakpoints.md : false const marketsInfo = useMangoStore((s) => s.marketsInfo) + const router = useRouter() const perpMarketsInfo = useMemo( () => @@ -267,7 +269,7 @@ const MarketsTable = ({ isPerpMarket }) => { ) : (
{items.map((market, index) => { @@ -297,9 +299,9 @@ const MarketsTable = ({ isPerpMarket }) => { className={`mr-2.5`} /> - {market.baseSymbol} + {name}
-
+
{formatUsdValue(last)}
|
{ key={`${name}${index}`} panelTemplate={ <> -
+
{t('daily-low')} @@ -378,6 +380,16 @@ const MarketsTable = ({ isPerpMarket }) => {
) : null} +
} diff --git a/components/SwitchMarketDropdown.tsx b/components/SwitchMarketDropdown.tsx index c14cf682..ed8bef0c 100644 --- a/components/SwitchMarketDropdown.tsx +++ b/components/SwitchMarketDropdown.tsx @@ -103,10 +103,10 @@ const SwitchMarketDropdown = () => { leaveTo="opacity-0" > -
+
onSearch(e.target.value)} prefix={} @@ -134,7 +134,7 @@ const SwitchMarketDropdown = () => {

{t('futures')}

-

+

{t('favorite')}

@@ -148,7 +148,7 @@ const SwitchMarketDropdown = () => { ))}

{t('spot')}

-

+

{t('favorite')}

diff --git a/components/mobile/BottomBar.tsx b/components/mobile/BottomBar.tsx index 4197228e..3feb8dfd 100644 --- a/components/mobile/BottomBar.tsx +++ b/components/mobile/BottomBar.tsx @@ -36,12 +36,12 @@ const BottomBar = () => {
diff --git a/components/mobile/MobileTradePage.tsx b/components/mobile/MobileTradePage.tsx index c040c8ed..bc504fe9 100644 --- a/components/mobile/MobileTradePage.tsx +++ b/components/mobile/MobileTradePage.tsx @@ -1,9 +1,9 @@ -import { useMemo, useState } from 'react' +import { useState } from 'react' import { Disclosure } from '@headlessui/react' import dynamic from 'next/dynamic' -import { SwitchHorizontalIcon, XIcon } from '@heroicons/react/outline' +import { XIcon } from '@heroicons/react/outline' import useMangoStore from '../../stores/useMangoStore' -import { getWeights, PerpMarket } from '@blockworks-foundation/mango-client' +import { PerpMarket } from '@blockworks-foundation/mango-client' import { CandlesIcon } from '../icons' import SwipeableTabs from './SwipeableTabs' import AdvancedTradeForm from '../trade_form/AdvancedTradeForm' @@ -16,8 +16,8 @@ import RecentMarketTrades from '../RecentMarketTrades' import FloatingElement from '../FloatingElement' import Swipeable from './Swipeable' import { useTranslation } from 'next-i18next' -import Link from 'next/link' import { useWallet } from '@solana/wallet-adapter-react' +import SwitchMarketDropdown from 'components/SwitchMarketDropdown' const TVChartContainer = dynamic( () => import('../../components/TradingView/index'), @@ -30,24 +30,11 @@ const MobileTradePage = () => { const { connected } = useWallet() const selectedMarket = useMangoStore((s) => s.selectedMarket.current) const marketConfig = useMangoStore((s) => s.selectedMarket.config) - const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current) - const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config) - const baseSymbol = marketConfig.baseSymbol - const isPerpMarket = marketConfig.kind === 'perp' const handleChangeViewIndex = (index) => { setViewIndex(index) } - const initLeverage = useMemo(() => { - if (!mangoGroup || !marketConfig) return 1 - - const ws = getWeights(mangoGroup, marketConfig.marketIndex, 'Init') - const w = - marketConfig.kind === 'perp' ? ws.perpAssetWeight : ws.spotAssetWeight - return Math.round((100 * -1) / (w.toNumber() - 1)) / 100 - }, [mangoGroup, marketConfig]) - const TABS = selectedMarket instanceof PerpMarket ? ['Trade', 'Details', 'Position', 'Orders'] @@ -57,30 +44,7 @@ const MobileTradePage = () => {
- -
-
{baseSymbol}
- - {isPerpMarket ? '-' : '/'} - -
- {isPerpMarket ? 'PERP' : groupConfig.quoteSymbol} -
-
- - {initLeverage}x - - -
- -
- +
{({ open }) => ( diff --git a/components/trade_form/AdvancedTradeForm.tsx b/components/trade_form/AdvancedTradeForm.tsx index b97e51b0..abfb8b5b 100644 --- a/components/trade_form/AdvancedTradeForm.tsx +++ b/components/trade_form/AdvancedTradeForm.tsx @@ -15,11 +15,7 @@ import { InformationCircleIcon, } from '@heroicons/react/outline' import { notify } from '../../utils/notifications' -import { - calculateTradePrice, - getDecimalCount, - percentFormat, -} from '../../utils' +import { calculateTradePrice, getDecimalCount } from '../../utils' import { floorToDecimal } from '../../utils/index' import useMangoStore, { Orderbook } from '../../stores/useMangoStore' import Button, { LinkButton } from '../Button' @@ -79,7 +75,7 @@ export default function AdvancedTradeForm({ const [spotMargin, setSpotMargin] = useState(defaultSpotMargin) const [positionSizePercent, setPositionSizePercent] = useState('') const [insufficientSol, setInsufficientSol] = useState(false) - const { takerFee, makerFee } = useFees() + const { takerFee } = useFees() const { totalMsrm } = useSrmAccount() const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current) @@ -947,7 +943,7 @@ export default function AdvancedTradeForm({
) : null ) : null} -
+
{isLimitOrder ? (
@@ -989,42 +985,44 @@ export default function AdvancedTradeForm({ auto updating the reduceOnly state when doing a market order: && showReduceOnly(perpAccount?.basePosition.toNumber()) */} - {marketConfig.kind === 'perp' || isLuna ? ( -
- - reduceOnChange(e.target.checked)} - disabled={isTriggerOrder || isLuna} +
+ {marketConfig.kind === 'perp' || isLuna ? ( +
+ - Reduce Only - - -
- ) : null} - {marketConfig.kind === 'perp' && tradeType === 'Limit' ? ( -
- - postOnlySlideOnChange(e.target.checked)} - disabled={isTriggerOrder} + reduceOnChange(e.target.checked)} + disabled={isTriggerOrder || isLuna} + > + Reduce Only + + +
+ ) : null} + {marketConfig.kind === 'perp' && tradeType === 'Limit' ? ( +
+ - Slide - - -
- ) : null} + postOnlySlideOnChange(e.target.checked)} + disabled={isTriggerOrder} + > + Slide + + +
+ ) : null} +
{marketConfig.kind === 'spot' ? (
{t('slippage-warning')}
) : null} -
+
{canTrade ? (
- ) : ( -
-
- {t('maker-fee')}: {percentFormat.format(makerFee)}{' '} -
- | -
- {' '} - {t('taker-fee')}: {percentFormat.format(takerFee)} -
-
- )} + ) : null}
diff --git a/pages/markets.tsx b/pages/markets.tsx index 7fa62cc8..b24c3c2f 100644 --- a/pages/markets.tsx +++ b/pages/markets.tsx @@ -6,7 +6,7 @@ import { useTranslation } from 'next-i18next' import MarketsTable from '../components/MarketsTable' import Tabs from '../components/Tabs' -const TABS = ['perp', 'spot'] +const TABS = ['futures', 'spot'] export async function getStaticProps({ locale }) { return { @@ -18,15 +18,13 @@ export async function getStaticProps({ locale }) { } export default function Markets() { - const [activeTab, setActiveTab] = useState('perp') + const [activeTab, setActiveTab] = useState('futures') const { t } = useTranslation(['common']) const handleTabChange = (tabName) => { setActiveTab(tabName) } - const isPerp = activeTab === 'perp' - return (
@@ -35,13 +33,14 @@ export default function Markets() {

{t('markets')}

- -

- {isPerp - ? `${t('perp')} ${t('markets')}` - : `${t('spot')} ${t('markets')}`} -

- +
+ +
+
diff --git a/pages/select.tsx b/pages/select.tsx deleted file mode 100644 index d9156462..00000000 --- a/pages/select.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { useTranslation } from 'next-i18next' -import { useEffect, useState } from 'react' -import { ChevronRightIcon } from '@heroicons/react/solid' -import useMangoStore from '../stores/useMangoStore' -import Link from 'next/link' -import { formatUsdValue } from '../utils' -import { serverSideTranslations } from 'next-i18next/serverSideTranslations' -import PageBodyContainer from '../components/PageBodyContainer' -import TopBar from '../components/TopBar' - -export async function getStaticProps({ locale }) { - return { - props: { - ...(await serverSideTranslations(locale, ['common', 'tv-chart'])), - // Will be passed to the page component as props - }, - } -} - -const SelectMarket = () => { - const { t } = useTranslation('common') - const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config) - const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current) - const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache) - - const [markets, setMarkets] = useState([]) - useEffect(() => { - const markets: any[] = [] - const allMarkets = - groupConfig?.spotMarkets && groupConfig?.perpMarkets - ? [...groupConfig.spotMarkets, ...groupConfig.perpMarkets] - : [] - allMarkets.forEach((market) => { - const base = market.name.slice(0, -5) - const found = markets.find((b) => b.baseAsset === base) - if (!found) { - markets.push({ baseAsset: base, markets: [market] }) - } else { - found.markets.push(market) - } - }) - setMarkets(markets) - }, []) - - if (!mangoCache) { - return null - } - - return ( -
- - -
- {t('markets')} -
- {markets.map((mkt) => { - return ( -
-
-
- - {mkt.baseAsset} -
-
- -
- ) - })} - {/* spacer so last market can be selected albeit bottom bar overlay */} -

-
-
- ) -} - -export default SelectMarket From 807ee38ddf1aca6f4748ec37871f7d0f2bd5a55f Mon Sep 17 00:00:00 2001 From: saml33 Date: Tue, 17 May 2022 09:29:45 +1000 Subject: [PATCH 2/3] make funding apr smaller --- components/MarketsTable.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/components/MarketsTable.tsx b/components/MarketsTable.tsx index a032ad6f..30485f9b 100644 --- a/components/MarketsTable.tsx +++ b/components/MarketsTable.tsx @@ -359,9 +359,10 @@ const MarketsTable = ({ isPerpMarket }) => { {t('average-funding')}
{funding1h ? ( - `${funding1h.toLocaleString(undefined, { - maximumSignificantDigits: 3, - })}% (${fundingApr}% APR)` + <> + {`${funding1h.toFixed(4)}%`}{' '} + {`(${fundingApr}% APR)`} + ) : ( Unavailable )} From d75e716fc1790051366f5ccdfd74e9c9d1bdbab7 Mon Sep 17 00:00:00 2001 From: saml33 Date: Tue, 17 May 2022 22:03:20 +1000 Subject: [PATCH 3/3] add price charts to markets page --- components/MarketsTable.tsx | 294 +++++++++++++------------- components/Orderbook.tsx | 31 +-- components/mobile/MobileTradePage.tsx | 17 +- public/locales/en/common.json | 1 + public/locales/es/common.json | 1 + public/locales/zh/common.json | 1 + public/locales/zh_tw/common.json | 1 + stores/useMangoStore.tsx | 27 +++ utils/tokens.ts | 17 ++ 9 files changed, 218 insertions(+), 172 deletions(-) diff --git a/components/MarketsTable.tsx b/components/MarketsTable.tsx index 30485f9b..635b2874 100644 --- a/components/MarketsTable.tsx +++ b/components/MarketsTable.tsx @@ -1,4 +1,4 @@ -import { useMemo } from 'react' +import { useEffect, useMemo } from 'react' import Link from 'next/link' import { formatUsdValue, perpContractPrecision, usdFormatter } from '../utils' import { Table, Td, Th, TrBody, TrHead } from './TableElements' @@ -6,21 +6,30 @@ import { useViewport } from '../hooks/useViewport' import { breakpoints } from './TradePageGrid' import { useTranslation } from 'next-i18next' import useMangoStore from '../stores/useMangoStore' -import MobileTableHeader from './mobile/MobileTableHeader' -import { ExpandableRow } from './TableElements' import { FavoriteMarketButton } from './TradeNavMenu' import { useSortableData } from '../hooks/useSortableData' -import Button, { LinkButton } from './Button' +import { LinkButton } from './Button' import { ArrowSmDownIcon } from '@heroicons/react/solid' import { useRouter } from 'next/router' +import { AreaChart, Area, XAxis, YAxis } from 'recharts' +import { InformationCircleIcon } from '@heroicons/react/outline' +import Tooltip from './Tooltip' const MarketsTable = ({ isPerpMarket }) => { const { t } = useTranslation('common') const { width } = useViewport() const isMobile = width ? width < breakpoints.md : false const marketsInfo = useMangoStore((s) => s.marketsInfo) + const actions = useMangoStore((s) => s.actions) + const coingeckoPrices = useMangoStore((s) => s.coingeckoPrices) const router = useRouter() + useEffect(() => { + if (coingeckoPrices.length === 0) { + actions.fetchCoingeckoPrices() + } + }, [coingeckoPrices]) + const perpMarketsInfo = useMemo( () => marketsInfo @@ -51,7 +60,9 @@ const MarketsTable = ({ isPerpMarket }) => { className="flex items-center font-normal no-underline" onClick={() => requestSort('name')} > - {t('market')} + + {t('market')} + { className="flex items-center font-normal no-underline" onClick={() => requestSort('last')} > - {t('price')} + + {t('price')} + { className="flex items-center font-normal no-underline" onClick={() => requestSort('change24h')} > - + {t('rolling-change')} { className="flex items-center font-normal no-underline" onClick={() => requestSort('volumeUsd24h')} > - + {t('daily-volume')} { className="flex items-center font-normal no-underline" onClick={() => requestSort('funding1h')} > - + {t('average-funding')} { className="flex items-center no-underline" onClick={() => requestSort('openInterestUsd')} > - + {t('open-interest')} { const fundingApr = funding1h ? (funding1h * 24 * 365).toFixed(2) : '-' - + const coingeckoData = coingeckoPrices.find( + (asset) => asset.symbol === baseSymbol + ) + const chartData = coingeckoData ? coingeckoData.prices : undefined return ( @@ -199,12 +215,36 @@ const MarketsTable = ({ isPerpMarket }) => { - - {last ? ( - formatUsdValue(last) - ) : ( - Unavailable - )} + +
+ {last ? ( + formatUsdValue(last) + ) : ( + {t('unavailable')} + )} +
+
+ {chartData !== undefined ? ( + + + + + + ) : ( + t('unavailable') + )} +
{ {change24h || change24h === 0 ? ( `${(change24h * 100).toFixed(2)}%` ) : ( - Unavailable + {t('unavailable')} )} @@ -221,7 +261,7 @@ const MarketsTable = ({ isPerpMarket }) => { {volumeUsd24h ? ( usdFormatter(volumeUsd24h, 0) ) : ( - Unavailable + {t('unavailable')} )} {isPerpMarket ? ( @@ -233,7 +273,9 @@ const MarketsTable = ({ isPerpMarket }) => { {`(${fundingApr}% APR)`} ) : ( - Unavailable + + {t('unavailable')} + )} @@ -251,7 +293,9 @@ const MarketsTable = ({ isPerpMarket }) => { ) : null} ) : ( - Unavailable + + {t('unavailable')} + )} @@ -267,139 +311,89 @@ const MarketsTable = ({ isPerpMarket }) => { ) : ( -
- - {items.map((market, index) => { - const { - baseSymbol, - change24h, - funding1h, - high24h, - last, - low24h, - name, - openInterest, - volumeUsd24h, - } = market - const fundingApr = funding1h ? (funding1h * 24 * 365).toFixed(2) : '-' + items.map((market) => { + const { baseSymbol, change24h, funding1h, last, name } = market + const fundingApr = funding1h ? (funding1h * 24 * 365).toFixed(2) : '-' + const coingeckoData = coingeckoPrices.find( + (asset) => asset.symbol === baseSymbol + ) + const chartData = coingeckoData ? coingeckoData.prices : undefined + return ( + -
- - } - /> - ) - })} -
+ + ) : ( + {t('unavailable')} + ) + ) : null} +
+
+ + ) + }) ) ) : null } -export default MarketsTable +export default MarketsTable as any diff --git a/components/Orderbook.tsx b/components/Orderbook.tsx index 965836eb..705511e1 100644 --- a/components/Orderbook.tsx +++ b/components/Orderbook.tsx @@ -523,27 +523,18 @@ export default function Orderbook({ depth = 8 }) {
- { + setDisplayCumulativeSize(!displayCumulativeSize) + }} + className="flex h-8 w-8 items-center justify-center rounded-full bg-th-bkg-3 hover:text-th-primary focus:outline-none" > - - + {displayCumulativeSize ? ( + + ) : ( + + )} +
{ const { connected } = useWallet() const selectedMarket = useMangoStore((s) => s.selectedMarket.current) const marketConfig = useMangoStore((s) => s.selectedMarket.config) + const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current) const handleChangeViewIndex = (index) => { setViewIndex(index) } + const initLeverage = useMemo(() => { + if (!mangoGroup || !marketConfig) return 1 + + const ws = getWeights(mangoGroup, marketConfig.marketIndex, 'Init') + const w = + marketConfig.kind === 'perp' ? ws.perpAssetWeight : ws.spotAssetWeight + return Math.round((100 * -1) / (w.toNumber() - 1)) / 100 + }, [mangoGroup, marketConfig]) + const TABS = selectedMarket instanceof PerpMarket ? ['Trade', 'Details', 'Position', 'Orders'] @@ -45,6 +55,9 @@ const MobileTradePage = () => {
+ + {initLeverage}x +
{({ open }) => ( diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 2825436c..1f8b9e6d 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -431,6 +431,7 @@ "trigger-price": "Trigger Price", "try-again": "Try again", "type": "Type", + "unavailable": "Unavailable", "unrealized-pnl": "Unrealized PnL", "unsettled": "Unsettled", "unsettled-balance": "Redeemable Value", diff --git a/public/locales/es/common.json b/public/locales/es/common.json index df7f9fdb..2e5f4cc0 100644 --- a/public/locales/es/common.json +++ b/public/locales/es/common.json @@ -431,6 +431,7 @@ "trigger-price": "Precio de activación", "try-again": "Inténtalo de nuevo", "type": "Tipo", + "unavailable": "Unavailable", "unrealized-pnl": "PnL no realizado", "unsettled": "Inestable", "unsettled-balance": "Saldo pendiente", diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index 3bb63b41..28e2a27d 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -431,6 +431,7 @@ "trigger-price": "触发价格", "try-again": "请再试一次", "type": "类型", + "unavailable": "无资料", "unrealized-pnl": "未实现盈亏", "unsettled": "未结清", "unsettled-balance": "可领取价值", diff --git a/public/locales/zh_tw/common.json b/public/locales/zh_tw/common.json index 499373a8..848c5fb7 100644 --- a/public/locales/zh_tw/common.json +++ b/public/locales/zh_tw/common.json @@ -431,6 +431,7 @@ "trigger-price": "觸發價格", "try-again": "請再試一次", "type": "類型", + "unavailable": "無資料", "unrealized-pnl": "未實現盈虧", "unsettled": "未結清", "unsettled-balance": "可領取價值", diff --git a/stores/useMangoStore.tsx b/stores/useMangoStore.tsx index aa0a598b..d1ed51d8 100644 --- a/stores/useMangoStore.tsx +++ b/stores/useMangoStore.tsx @@ -40,6 +40,7 @@ import { getProfilePicture, ProfilePicture } from '@solflare-wallet/pfp' import { decodeBook } from '../hooks/useHydrateStore' import { IOrderLineAdapter } from '../public/charting_library/charting_library' import { Wallet } from '@solana/wallet-adapter-react' +import { coingeckoIds } from 'utils/tokens' export const ENDPOINTS: EndpointInfo[] = [ { @@ -223,6 +224,7 @@ export type MangoStore = { deleteAlert: (id: string) => void loadAlerts: (pk: PublicKey) => void fetchMarketsInfo: () => void + fetchCoingeckoPrices: () => void } alerts: { activeAlerts: Array @@ -236,6 +238,7 @@ export type MangoStore = { tradingView: { orderLines: Map } + coingeckoPrices: any[] } const useMangoStore = create< @@ -360,6 +363,7 @@ const useMangoStore = create< tradingView: { orderLines: new Map(), }, + coingeckoPrices: [], set: (fn) => set(produce(fn)), actions: { async fetchWalletTokens(wallet: Wallet) { @@ -1000,6 +1004,29 @@ const useMangoStore = create< console.log('ERORR: Unable to load all market info') } }, + async fetchCoingeckoPrices() { + const set = get().set + try { + const promises: any = [] + for (const asset of coingeckoIds) { + promises.push( + fetch( + `https://api.coingecko.com/api/v3/coins/${asset.id}/market_chart?vs_currency=usd&days=2` + ).then((res) => res.json()) + ) + } + + const data = await Promise.all(promises) + for (let i = 0; i < data.length; i++) { + data[i].symbol = coingeckoIds[i].symbol + } + set((state) => { + state.coingeckoPrices = data + }) + } catch (e) { + console.log('ERORR: Unable to load Coingecko prices') + } + }, }, } }) diff --git a/utils/tokens.ts b/utils/tokens.ts index 0a98d59e..9ba02a25 100644 --- a/utils/tokens.ts +++ b/utils/tokens.ts @@ -22,3 +22,20 @@ export function parseTokenAccountData(data: Buffer): { amount, } } + +export const coingeckoIds = [ + { id: 'bitcoin', symbol: 'BTC' }, + { id: 'ethereum', symbol: 'ETH' }, + { id: 'solana', symbol: 'SOL' }, + { id: 'mango-markets', symbol: 'MNGO' }, + { id: 'binancecoin', symbol: 'BNB' }, + { id: 'serum', symbol: 'SRM' }, + { id: 'raydium', symbol: 'RAY' }, + { id: 'ftx-token', symbol: 'FTT' }, + { id: 'avalanche-2', symbol: 'AVAX' }, + { id: 'terra-luna', symbol: 'LUNA' }, + { id: 'cope', symbol: 'COPE' }, + { id: 'cardano', symbol: 'ADA' }, + { id: 'msol', symbol: 'MSOL' }, + { id: 'tether', symbol: 'USDT' }, +]