Merge pull request #416 from blockworks-foundation/finn/prospective_dashboard

Prospective Tokens
This commit is contained in:
Finn Casey Fierro 2024-05-01 21:26:16 +01:00 committed by GitHub
commit b9f1472a13
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 269 additions and 0 deletions

71
pages/api/tokens.ts Normal file
View File

@ -0,0 +1,71 @@
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const { offset = 0 } = req.query
// Check if the API key is defined or provide a default/fallback value
const apiKey =
process.env.NEXT_PUBLIC_BIRDEYE_API_KEY || 'your-default-api-key' // Consider a fallback if environment variable might not be set
const options = {
method: 'GET',
headers: {
'X-API-KEY': apiKey,
},
}
try {
// Fetch the initial list of tokens
const response = await fetch(
`https://public-api.birdeye.so/defi/tokenlist?sort_by=v24hUSD&sort_type=desc&offset=${offset}&limit=50`,
options,
)
const tokenListResponse = await response.json()
const tokenList = tokenListResponse['data']['tokens']
const filteredTokens = []
// Loop through each token to check additional criteria
for (const token of tokenList) {
const { address, v24hUSD } = token
if (v24hUSD > 50000) {
const now = new Date()
const nowInSeconds = Math.floor(now.getTime() / 1000)
const pastDate = new Date()
pastDate.setDate(pastDate.getDate() - 4)
pastDate.setMonth(pastDate.getMonth() - 1)
const pastDateInSeconds = Math.floor(pastDate.getTime() / 1000)
// Fetch history for the token
const historyResponse = await fetch(
`https://public-api.birdeye.so/defi/history_price?address=${address}&address_type=token&type=1D&time_from=${pastDateInSeconds}&time_to=${nowInSeconds}`,
options,
)
const historyData = await historyResponse.json()
if (historyData['data']['items']?.length >= 35) {
const detailResponse = await fetch(
`https://public-api.birdeye.so/defi/token_overview?address=${address}`,
options,
)
const tokenDetails = await detailResponse.json()
if (
tokenDetails['data']['mc'] > 15000000 &&
tokenDetails['data']['numberMarkets'] > 10
) {
// market cap greater than 1 mil
filteredTokens.push(tokenDetails['data'])
}
}
}
}
// Return the filtered list of tokens
res.status(200).json(filteredTokens)
} catch (error) {
console.error('Failed to fetch tokens:', error)
res.status(500).json({ error: 'Failed to fetch tokens' })
}
}

View File

@ -1380,6 +1380,19 @@ export const DashboardNavbar = () => {
</h4>
</Link>
</div>
<div>
<Link href={'/dashboard/prospective'} shallow={true}>
<h4
className={`${
asPath.includes('/dashboard/prospective')
? 'bg-th-bkg-2 text-th-active'
: ''
} cursor-pointer border-r border-th-bkg-3 px-6 py-4`}
>
Prospective
</h4>
</Link>
</div>
<div>
<Link
href={

View File

@ -0,0 +1,185 @@
import type { NextPage } from 'next'
import { DashboardNavbar } from '.'
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
import { useState, useMemo, useEffect } from 'react'
import { LinkButton } from '@components/shared/Button'
import Image from 'next/image'
import Loading from '@components/shared/Loading'
import useBanks from 'hooks/useBanks'
interface Token {
symbol: string // Every token will definitely have a symbol.
[key: string]: string // Other properties are unknown and can vary.
}
const fetchTokens = async (
offset = 0,
existingTokens: Token[] = [],
): Promise<Token[]> => {
const res = await fetch(`/api/tokens?offset=${offset * 50}&limit=50`)
const data = await res.json()
return [...existingTokens, ...data] // Ensure the returned data conforms to Token[]
}
const Prospective: NextPage = () => {
const banks = useBanks()['banks']
const bankNames = banks.map((bank) => bank.name.toUpperCase())
// Generate headers from the first token entry if available
const [tokensList, setTokensList] = useState<Token[]>([])
const [offset, setOffset] = useState<number>(0)
const [loading, setLoading] = useState<boolean>(false)
useEffect(() => {
const initialFetch = async () => {
setLoading(true)
try {
const initialTokens: Token[] = await fetchTokens(0)
setTokensList(initialTokens)
} catch (error) {
console.error('Failed to fetch tokens:', error)
} finally {
setLoading(false)
}
}
initialFetch()
}, [])
const handleShowMore = async () => {
setLoading(true)
try {
const newTokens = await fetchTokens(offset + 1, tokensList)
setTokensList(newTokens)
setOffset((prevOffset) => prevOffset + 1)
} catch (error) {
console.error('Failed to fetch more tokens:', error)
}
setLoading(false)
}
const heads = useMemo(() => {
if (tokensList.length > 0) {
const allKeys = Object.keys(tokensList[0])
const filteredKeys = allKeys.filter(
(key) =>
![
'symbol',
'extensions',
'lastTradeUnixTime',
'uniqueWallet30m',
].includes(key),
)
return ['symbol', ...filteredKeys]
}
return []
}, [tokensList])
return (
<div className="col-span-12 w-full lg:col-span-8 lg:col-start-3">
<DashboardNavbar />
<div className="mt-4">
<div className="mx-20 mb-4">
<p className="flex items-center space-x-4 text-th-fgd-4">
<span>Hidden Gems to Prospect On</span>
</p>
</div>
<div className="w-full overflow-scroll" style={{ maxHeight: '70vh' }}>
<Table className="h-full">
<thead>
<TrHead
style={{ boxShadow: '1px -5px 1px rgba(0,0,0,1)', zIndex: 19 }}
className="sticky top-0 border-t bg-th-bkg-2"
>
{heads.map((head, index) => (
<Th
key={head}
className="text-left"
style={{
borderLeft: index !== 0 ? '1px solid #ccc' : 'none',
}}
>
{head}
</Th>
))}
</TrHead>
</thead>
<tbody>
{tokensList.map((token: Token, idx) => (
<TrBody
key={idx}
className="h-10 cursor-pointer text-xs md:hover:bg-th-bkg-2"
onClick={() =>
window.open(
`https://birdeye.so/token/${token['address']}?chain=solana&tab=recentTrades`,
'_blank',
)
}
>
{heads.map((head, valIdx) => {
if (head == 'symbol') {
token[head] = token[head]?.toUpperCase().toString()
}
return (
<Td
xBorder={valIdx != 0}
key={valIdx}
className={`!py-3
${
valIdx === 0
? 'sticky left-0 z-10 ' +
(bankNames.includes(token[head])
? 'bg-lime-200'
: 'bg-th-bkg-2')
: ''
}
`}
>
<div className="flex">
<div className="mr-2 h-full">
{head == 'logoURI' ? (
<>
<Image
src={token[head]}
alt="Token Logo"
width={100} // Set the desired width
height={100} // Set the desired height
unoptimized={true} // Use this only if you face issues with the Next.js optimizer
/>{' '}
</>
) : (
<>
{token[head] ? token[head]?.toString() : 'N/A'}
</>
)}
</div>
</div>
</Td>
)
})}
</TrBody>
))}
</tbody>
</Table>
</div>
{loading && (
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '5vh', // Match the height of the table container
}}
>
<Loading className="mr-2 h-8 w-8" />
</div>
)}
<div className="flex justify-center py-6">
<LinkButton onClick={handleShowMore}>Show More</LinkButton>
</div>
</div>
</div>
)
}
export default Prospective