Skip to content

Commit

Permalink
Merge branch 'develop' into caip-native-sol
Browse files Browse the repository at this point in the history
  • Loading branch information
gomesalexandre committed Sep 25, 2024
2 parents 633e3bf + 8a1b4cb commit ed15cc7
Show file tree
Hide file tree
Showing 13 changed files with 684 additions and 186 deletions.
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 @@ -4,7 +4,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 @@ -101,6 +115,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
Binary file removed src/assets/nightsky.jpg
Binary file not shown.
Binary file added src/assets/splash-sidebar.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 5 additions & 3 deletions src/assets/translations/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -287,12 +287,16 @@
},
"connectWalletPage": {
"title": "The Original Multichain Exchange",
"primaryTitle": "Welcome to Multichain DeFi.",
"secondaryTitle": "Private. Decentralized. Non-custodial.",
"primaryDescription": "Trade, bridge, and earn rewards effortlessly. Experience Native Bitcoin, Dogecoin, and more. Manage Liquidity and DeFi Positions in One-Click.",
"snapDescription": "Download the ShapeShift Multichain Snap and Unlock Bitcoin, Dogecoin, and more for your MetaMask.",
"shapeshift": "ShapeShift",
"exploreThe": "Explore the",
"defiUniverse": "DeFi Universe",
"body": "Trade, bridge & earn. Private. Community owned. Non-custodial. Decentralized.",
"header": "Please connect a wallet to get started",
"cta": "Connect Wallet",
"cta": "Connect or Create Wallet",
"viewADemo": "View a Demo",
"dontHaveWallet": "Don't have a wallet?",
"welcomeBack": "Welcome back!",
Expand Down Expand Up @@ -1544,8 +1548,6 @@
"title": "Multichain Snap needs updating",
"subtitle": "Click 'Update' to continue using ShapeShift's multichain features with MetaMask!"
},
"secondaryTitle": "The best Multichain experience for MetaMask: Powered by ShapeShift",
"secondaryBody": "Send, receive, track, trade, and earn with the ShapeShift Multichain Snap on the following chains:",
"connectMetaMask": "Connect MetaMask",
"andMore": "...and more",
"snapInstalledToast": "ShapeShift Multichain MetaMask Snap Installed",
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'

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

0 comments on commit ed15cc7

Please sign in to comment.