Merge pull request #416 from blockworks-foundation/finn/prospective_dashboard
Prospective Tokens
This commit is contained in:
commit
b9f1472a13
|
@ -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' })
|
||||
}
|
||||
}
|
|
@ -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={
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue