use etherscan for fdv

This commit is contained in:
saml33 2024-01-29 13:08:14 +11:00
parent 1c19aa8f41
commit 62899c8f63
8 changed files with 89 additions and 24 deletions

View File

@ -23,7 +23,8 @@ const TokenCard = ({ token }: { token: FormattedTableData }) => {
logoURI,
symbol,
slug,
birdeyeEthData,
ethCircSupply,
ethMint,
} = token
const hasCustomIcon = mangoSymbol
? CUSTOM_TOKEN_ICONS[mangoSymbol.toLowerCase()]
@ -92,7 +93,7 @@ const TokenCard = ({ token }: { token: FormattedTableData }) => {
<div>
<p className="text-xs">FDV</p>
<div className="flex items-center">
{birdeyeEthData && !birdeyeEthData?.mc ? (
{ethMint && !ethCircSupply ? (
<Solana className="h-3.5 w-3.5 mr-1.5" />
) : null}
<p className="text-th-fgd-1">

View File

@ -24,10 +24,7 @@ import { MangoTokenData } from '../../types/mango'
import { useViewport } from '../../hooks/useViewport'
import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime'
import { useRouter } from 'next/navigation'
import {
BirdeyeOverviewData,
BirdeyePriceHistoryData,
} from '../../types/birdeye'
import { BirdeyePriceHistoryData } from '../../types/birdeye'
import SheenLoader from '../shared/SheenLoader'
import NoResults from './NoResults'
import Solana from '../icons/Solana'
@ -42,10 +39,11 @@ export type FormattedTableData = {
volume: number
mangoSymbol: string | undefined
mint: string
ethMint: string | undefined
symbol: string
slug: string
tags: string[]
birdeyeEthData: BirdeyeOverviewData | undefined
ethCircSupply: number | undefined
}
const goToTokenPage = (slug: string, router: AppRouterInstance) => {
@ -72,8 +70,9 @@ const TokenTable = ({
slug,
symbol,
birdeyeData,
birdeyeEthData,
ethCircSupply,
mint,
ethMint,
tags,
} = token
const mangoTokenData: MangoTokenData | undefined = mangoTokensData.find(
@ -96,7 +95,7 @@ const TokenTable = ({
let fdv = 0
if (birdeyeData?.mc) {
fdv = birdeyeData.mc + (birdeyeEthData?.mc || 0)
fdv = birdeyeData.mc + (ethCircSupply || 0) * price
}
const logoURI = birdeyeData?.logoURI
@ -120,10 +119,11 @@ const TokenTable = ({
logoURI,
mangoSymbol,
mint,
ethMint,
symbol,
slug,
tags,
birdeyeEthData,
ethCircSupply,
}
formatted.push(data)
}
@ -210,7 +210,8 @@ const TokenTable = ({
logoURI,
symbol,
slug,
birdeyeEthData,
ethCircSupply,
ethMint,
} = token
const hasCustomIcon = mangoSymbol
? CUSTOM_TOKEN_ICONS[mangoSymbol.toLowerCase()]
@ -295,7 +296,7 @@ const TokenTable = ({
</Td>
<Td>
<div className="flex items-center justify-end">
{birdeyeEthData && !birdeyeEthData?.mc ? (
{ethMint && !ethCircSupply ? (
<Solana className="h-3.5 w-3.5 mr-1.5" />
) : null}
<p>{fdv ? `$${numberCompacter.format(fdv)}` : ''}</p>

View File

@ -25,13 +25,19 @@ const TokenInfo = ({
loadingCoingeckoData: boolean
tokenPageData: TokenPageWithData
}) => {
const { mint, tags, birdeyeData, birdeyeEthData } = tokenPageData
const { mint, tags, birdeyeData, ethCircSupply, ethMint } = tokenPageData
const [copied, setCopied] = useState(false)
let fdv = 0
if (birdeyeData?.mc) {
fdv = birdeyeData.mc + (birdeyeEthData?.mc || 0)
const price = birdeyeData?.price
? birdeyeData.price
: coingeckoData?.market_data?.current_price?.usd
? coingeckoData.market_data.current_price.usd
: 1
fdv = birdeyeData.mc + (ethCircSupply || 0) * price
}
const handleCopyMint = (text: string) => {
@ -101,7 +107,7 @@ const TokenInfo = ({
value={
fdv ? (
<div className="flex items-center">
{birdeyeEthData && !birdeyeEthData?.mc ? (
{ethMint && !ethCircSupply ? (
<Solana className="h-3.5 w-3.5 mr-1.5" />
) : null}
<span>{`$${numberCompacter.format(fdv)}`}</span>

50
app/utils/etherscan.ts Normal file
View File

@ -0,0 +1,50 @@
import { fetchBirdEyeData } from '../../contentful/tokenPage'
const BN = require('bn.js')
const fetchEtherscanData = async (mint: string) => {
try {
const response = await fetch(
`https://api.etherscan.io/api?module=stats&action=tokensupply&contractaddress=${mint}&apikey=${process
.env.NEXT_PUBLIC_ETHERSCAN_API_KEY!}`,
{
next: { revalidate: 86400 },
},
)
const data = await response.json()
return data
} catch (error) {
console.error('failed to fetch etherscan data', error)
throw error // rethrow the error for upper-level handling
}
}
const calculateCirculatingSupply = (nativeSupply: string, decimals: number) => {
const uiSupply = new BN(nativeSupply)
const decimalsBN = new BN(decimals)
const circSupply = uiSupply.div(new BN(10).pow(decimalsBN))
return circSupply.toNumber()
}
export const fetchEthCircSupply = async (mint: string) => {
try {
const birdeyeEthData = await fetchBirdEyeData(mint, 'ethereum')
const etherscanData = await fetchEtherscanData(mint)
if (etherscanData?.message === 'OK' && birdeyeEthData?.decimals) {
const nativeSupply = etherscanData.result
const circSupply = calculateCirculatingSupply(
nativeSupply,
birdeyeEthData.decimals,
)
return circSupply
} else {
console.error('invalid data received from api')
return 0
}
} catch (error) {
console.error('failed to fetch ethereum token supply', error)
return 0
}
}

View File

@ -8,6 +8,7 @@ import {
BirdeyePriceHistoryData,
} from '../app/types/birdeye'
import { DAILY_SECONDS } from '../app/utils/constants'
import { fetchEthCircSupply } from '../app/utils/etherscan'
type TokenPageEntry = Entry<TypeTokenSkeleton, undefined, string>
@ -31,7 +32,7 @@ export interface TokenPage {
export interface TokenPageWithData extends TokenPage {
birdeyeData: BirdeyeOverviewData
birdeyeEthData?: BirdeyeOverviewData
ethCircSupply?: number | undefined
birdeyePrices?: BirdeyePriceHistoryData[]
}
@ -65,7 +66,7 @@ interface FetchTokenPagesOptions {
preview: boolean
}
const fetchBirdEyeData = async (address: string, chain?: string) => {
export const fetchBirdEyeData = async (address: string, chain?: string) => {
const response = await makeApiRequest(
`defi/token_overview?address=${address}`,
chain,
@ -76,8 +77,8 @@ const fetchBirdEyeData = async (address: string, chain?: string) => {
async function fetchDataForToken(tokenPage) {
// birdeye overview data
const birdeyeData = await fetchBirdEyeData(tokenPage.mint)
const birdeyeEthData = tokenPage?.ethMint
? await fetchBirdEyeData(tokenPage.ethMint, 'ethereum')
const ethCircSupply = tokenPage?.ethMint
? await fetchEthCircSupply(tokenPage.ethMint)
: undefined
// birdeye 24h price data
@ -91,7 +92,7 @@ async function fetchDataForToken(tokenPage) {
unixTime: data.unixTime * 1000,
})) || []
return { ...tokenPage, birdeyeData, birdeyePrices, birdeyeEthData }
return { ...tokenPage, birdeyeData, birdeyePrices, ethCircSupply }
}
export async function fetchTokenPages({
@ -144,11 +145,11 @@ export async function fetchTokenPage({
if (parsedTokenPage) {
const birdeyeData = await fetchBirdEyeData(parsedTokenPage.mint)
const birdeyeEthData = parsedTokenPage?.ethMint
? await fetchBirdEyeData(parsedTokenPage.ethMint, 'ethereum')
const ethCircSupply = parsedTokenPage?.ethMint
? await fetchEthCircSupply(parsedTokenPage.ethMint)
: undefined
return { ...parsedTokenPage, birdeyeData, birdeyeEthData }
return { ...parsedTokenPage, birdeyeData, ethCircSupply }
} else return null
}

View File

@ -31,6 +31,7 @@
"@heroicons/react": "^2.0.18",
"@tanstack/react-query": "^5.8.4",
"@tippyjs/react": "^4.2.6",
"bn.js": "^5.2.1",
"contentful": "^10.6.12",
"contentful-management": "^11.6.1",
"dayjs": "^1.11.10",

File diff suppressed because one or more lines are too long

View File

@ -1822,6 +1822,11 @@ bluebird@^3.3.3, bluebird@^3.7.2:
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
bn.js@^5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70"
integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"