Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: markets page coingecko lists and savers recommended assets #7782

Merged
merged 25 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ff03143
[skip ci] wip: markets page coingecko recommended categories
gomesalexandre Sep 22, 2024
cdd1a8d
[skip ci] feat: cleanup some
gomesalexandre Sep 23, 2024
2d11a95
Merge remote-tracking branch 'origin/develop' into feat_coingecko_rec…
gomesalexandre Sep 23, 2024
b5b6f0f
[skip ci] feat: make it better
gomesalexandre Sep 23, 2024
bbdf131
[skip ci] feat: handle native assets
gomesalexandre Sep 23, 2024
12f485c
feat: trending
gomesalexandre Sep 23, 2024
1311940
feat: cleanup
gomesalexandre Sep 23, 2024
64296f7
feat: more cleanup
gomesalexandre Sep 23, 2024
41e5870
feat: more more cleanup
gomesalexandre Sep 23, 2024
2d84a1a
[skip ci] feat: progression
gomesalexandre Sep 23, 2024
e4c9059
feat: cleanup
gomesalexandre Sep 23, 2024
c79b2fc
feat: thorchain progression
gomesalexandre Sep 23, 2024
3acfe70
[skip ci] thor too
gomesalexandre Sep 23, 2024
68b6338
fix: times 100 thor apy
gomesalexandre Sep 23, 2024
dce484a
fix: thor narrowing
gomesalexandre Sep 23, 2024
50e085e
Merge remote-tracking branch 'origin/develop' into feat_coingecko_rec…
gomesalexandre Sep 23, 2024
f5a24a5
fix: don't display chaindropdown for coingecko bits
gomesalexandre Sep 23, 2024
adaf5b2
feat: savers supported ChainIds
gomesalexandre Sep 23, 2024
5334f58
[skip ci] fix: uniq
gomesalexandre Sep 23, 2024
38ae34e
feat: add volume and market cap categories, revert chain selection
gomesalexandre Sep 23, 2024
90ebc5e
fix: queryKey
gomesalexandre Sep 23, 2024
eb4c3d5
chore: update src/assets/translations/en/main.json
gomesalexandre Sep 23, 2024
c9962ef
Merge remote-tracking branch 'origin/develop' into feat_coingecko_rec…
gomesalexandre Sep 25, 2024
c9f20b9
feat: attempt using caip/src/adapters to keep things DRY
gomesalexandre Sep 25, 2024
a0c597b
feat: dry
gomesalexandre Sep 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion packages/caip/src/adapters/coingecko/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,21 @@ import type { AssetId } from '../../assetId/assetId'
import { fromAssetId } from '../../assetId/assetId'
import type { ChainId } from '../../chainId/chainId'
import { fromChainId, toChainId } from '../../chainId/chainId'
import { CHAIN_NAMESPACE, CHAIN_REFERENCE } from '../../constants'
import {
arbitrumChainId,
arbitrumNovaChainId,
avalancheChainId,
baseChainId,
bscChainId,
CHAIN_NAMESPACE,
CHAIN_REFERENCE,
cosmosChainId,
ethChainId,
gnosisChainId,
optimismChainId,
polygonChainId,
thorchainChainId,
} from '../../constants'
import * as adapters from './generated'

// https://api.coingecko.com/api/v3/asset_platforms
Expand Down Expand Up @@ -92,6 +106,35 @@ export const chainIdToCoingeckoAssetPlatform = (chainId: ChainId): string => {
}
}

export const coingeckoAssetPlatformToChainId = (platform: CoingeckoAssetPlatform): ChainId => {
switch (platform) {
case CoingeckoAssetPlatform.Ethereum:
return ethChainId
case CoingeckoAssetPlatform.Avalanche:
return avalancheChainId
case CoingeckoAssetPlatform.Optimism:
return optimismChainId
case CoingeckoAssetPlatform.BnbSmartChain:
return bscChainId
case CoingeckoAssetPlatform.Polygon:
return polygonChainId
case CoingeckoAssetPlatform.Gnosis:
return gnosisChainId
case CoingeckoAssetPlatform.Arbitrum:
return arbitrumChainId
case CoingeckoAssetPlatform.ArbitrumNova:
return arbitrumNovaChainId
case CoingeckoAssetPlatform.Base:
return baseChainId
case CoingeckoAssetPlatform.Cosmos:
return cosmosChainId
case CoingeckoAssetPlatform.Thorchain:
return thorchainChainId
default:
throw new Error(`Unsupported Coingecko asset platform: ${platform}`)
}
}

export const makeCoingeckoAssetUrl = (assetId: AssetId): string | undefined => {
const id = assetIdToCoingecko(assetId)
if (!id) return
Expand Down
10 changes: 9 additions & 1 deletion src/assets/translations/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -2602,7 +2602,15 @@
"title": "Trending",
"subtitle": "These are top assets that have jumped %{percentage}% or more"
},
"topMovements": {
"tradingVolume": {
"title": "Trading Volume",
"subtitle": "These are top assets with the highest trading volume in the last 24 hours"
},
"marketCap": {
"title": "Market Cap",
"subtitle": "These are top assets with the highest market capitalization in the last 24 hours"
},
"topMovers": {
"title": "Top Movers"
},
"recentlyAdded": {
Expand Down
31 changes: 31 additions & 0 deletions src/lib/coingecko/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { AssetId } from '@shapeshiftoss/caip'
import {
adapters,
baseAssetId,
bchAssetId,
bscAssetId,
btcAssetId,
cosmosAssetId,
dogeAssetId,
ethAssetId,
gnosisAssetId,
ltcAssetId,
polygonAssetId,
thorchainAssetId,
} from '@shapeshiftoss/caip'

export const COINGECKO_NATIVE_ASSET_ID_TO_ASSET_ID: Partial<Record<string, AssetId>> = {
bitcoin: btcAssetId,
'bitcoin-cash': bchAssetId,
dogecoin: dogeAssetId,
litecoin: ltcAssetId,
[adapters.CoingeckoAssetPlatform.Ethereum]: ethAssetId,
[adapters.CoingeckoAssetPlatform.Thorchain]: thorchainAssetId,
[adapters.CoingeckoAssetPlatform.Gnosis]: gnosisAssetId,
[adapters.CoingeckoAssetPlatform.Cosmos]: cosmosAssetId,
// This isn't a mistake - the network and id are different in the case of MATIC/POS
'polygon-ecosystem-token': polygonAssetId,
[adapters.CoingeckoAssetPlatform.Base]: baseAssetId,
// This isn't a mistake - the network and id are different in the case of BSC
binanceCoin: bscAssetId,
}
80 changes: 80 additions & 0 deletions src/lib/coingecko/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type { AssetId, ChainId } from '@shapeshiftoss/caip'
import type {
CoinGeckoMarketCap,
CoinGeckoMarketData,
} from 'lib/market-service/coingecko/coingecko-types'

// Non-exhaustive types, refer to https://docs.coingecko.com/reference/coins-id and other endpoints for full response schema
export type CoingeckoAssetDetails = {
market_data: CoinGeckoMarketData
asset_platform_id: string
id: string
image: Record<string, string>
name: string
symbol: string
detail_platforms: Record<string, { decimal_place: number; contract_address: string }>
platforms: Record<string, string>
}

export type MoverAsset = Pick<
CoinGeckoMarketCap,
'id' | 'symbol' | 'name' | 'image' | 'market_cap_rank'
> & {
usd: string
usd_24h_vol: string
usd_1y_change: string
}

export type MoversResponse = {
top_gainers: MoverAsset[]
top_losers: MoverAsset[]
}

// Non-exhaustive
export type TrendingCoin = {
id: string
coin_id: number
name: string
symbol: string
market_cap_rank: number
thumb: string
small: string
large: string
slug: string
price_btc: number
score: number
data: {
price: number
price_btc: string
price_change_percentage_24h: Record<string, number>
market_cap: string
market_cap_btc: string
total_volume: string
}
}

export type TrendingResponse = {
coins: {
item: TrendingCoin
}[]
}

export type RecentlyAddedCoin = {
id: string
symbol: string
name: string
activated_at: number
}

export type RecentlyAddedResponse = RecentlyAddedCoin[]

export type CoingeckoAsset = {
assetId: AssetId
details: CoingeckoAssetDetails
}

export type CoingeckoList = {
byId: Record<AssetId, CoingeckoAsset>
ids: AssetId[]
chainIds: ChainId[]
}
120 changes: 120 additions & 0 deletions src/lib/coingecko/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { adapters, ASSET_NAMESPACE, bscChainId, toAssetId } from '@shapeshiftoss/caip'
import type { CoingeckoAssetPlatform } from '@shapeshiftoss/caip/src/adapters'
import axios from 'axios'
import { queryClient } from 'context/QueryClientProvider/queryClient'
import type { CoinGeckoMarketCap } from 'lib/market-service/coingecko/coingecko-types'

import { COINGECKO_NATIVE_ASSET_ID_TO_ASSET_ID } from './constants'
import type {
CoingeckoAsset,
CoingeckoAssetDetails,
MoverAsset,
MoversResponse,
RecentlyAddedCoin,
RecentlyAddedResponse,
TrendingCoin,
TrendingResponse,
} from './types'

const coingeckoBaseUrl = 'https://api.proxy.shapeshift.com/api/v1/markets'
gomesalexandre marked this conversation as resolved.
Show resolved Hide resolved

const getCoinDetails = async (
marketCap: CoinGeckoMarketCap | RecentlyAddedCoin | TrendingCoin | MoverAsset,
i: number,
all: CoingeckoAsset[],
) => {
try {
const { data } = await queryClient.fetchQuery({
queryKey: ['coingecko', 'coin', marketCap.id],
// Shared query across consumers, so make it infinite as there will be a lot of overlap
queryFn: () => axios.get<CoingeckoAssetDetails>(`${coingeckoBaseUrl}/coins/${marketCap.id}`),
gcTime: Infinity,
staleTime: Infinity,
})
const { asset_platform_id, id } = data

const address = data.platforms?.[asset_platform_id]

if (!address) return

const assetId = (() => {
// Handles native assets, which *may* not contain a platform_id
if (COINGECKO_NATIVE_ASSET_ID_TO_ASSET_ID[id])
return COINGECKO_NATIVE_ASSET_ID_TO_ASSET_ID[id]

const chainId = adapters.coingeckoAssetPlatformToChainId(
asset_platform_id as CoingeckoAssetPlatform,
)
if (!chainId) return

const assetId = toAssetId({
chainId,
assetNamespace: chainId === bscChainId ? ASSET_NAMESPACE.bep20 : ASSET_NAMESPACE.erc20,
assetReference: address,
})
return assetId
})()

if (!assetId) return marketCap

all[i] = {
assetId,
details: data,
}
} catch (error) {
console.error(`Error fetching asset details for ${marketCap.id}:`, error)
return null
}
}

export const getCoingeckoTopMovers = async (): Promise<CoingeckoAsset[]> => {
const { data } = await axios.get<MoversResponse>(
`${coingeckoBaseUrl}/coins/top_gainers_losers?vs_currency=usd`,
)

const all: CoingeckoAsset[] = []

await Promise.allSettled(
data.top_gainers
.concat(data.top_losers)
.map((marketData, i) => getCoinDetails(marketData, i, all)),
)

return all.filter(mover => Boolean(mover.assetId))
}

export const getCoingeckoTrending = async (): Promise<CoingeckoAsset[]> => {
const { data } = await axios.get<TrendingResponse>(`${coingeckoBaseUrl}/search/trending`)

const all: CoingeckoAsset[] = []

await Promise.allSettled(
data.coins.map(({ item }) => item).map((marketData, i) => getCoinDetails(marketData, i, all)),
)

return all.filter(mover => Boolean(mover.assetId))
}

export const getCoingeckoRecentlyAdded = async (): Promise<CoingeckoAsset[]> => {
const { data } = await axios.get<RecentlyAddedResponse>(`${coingeckoBaseUrl}/coins/list/new`)

const all: CoingeckoAsset[] = []

await Promise.allSettled(data.map((marketData, i) => getCoinDetails(marketData, i, all)))

return all.filter(mover => Boolean(mover.assetId))
}

export const getCoingeckoMarkets = async (
order: 'market_cap_desc' | 'volume_desc',
): Promise<CoingeckoAsset[]> => {
const { data } = await axios.get<CoinGeckoMarketCap[]>(
`${coingeckoBaseUrl}/coins/markets?vs_currency=usd&order=${order}`,
)

const all: CoingeckoAsset[] = []

await Promise.allSettled(data.map((marketData, i) => getCoinDetails(marketData, i, all)))

return all.filter(mover => Boolean(mover.assetId))
}
Loading
Loading