-
Notifications
You must be signed in to change notification settings - Fork 0
Add real-time tokenomics dashboard #3
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| export async function GET() { | ||
| try { | ||
| const res = await fetch('https://t.me/gudteksolana', { next: { revalidate: 3600 } }) | ||
| const text = await res.text() | ||
| const match = text.match(/tgme_page_extra[^>]*>([^<]+) members/) | ||
| const count = match ? parseInt(match[1].replace(/[^0-9]/g, '')) : 0 | ||
| return Response.json({ count }) | ||
| } catch (err) { | ||
| return Response.json({ count: 0 }) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,142 @@ | ||||||||||||||||||||||
| 'use client' | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import { useEffect, useState } from 'react' | ||||||||||||||||||||||
| import Navbar from '@/components/Navbar' | ||||||||||||||||||||||
| import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' | ||||||||||||||||||||||
| import { Progress } from '@/components/ui/progress' | ||||||||||||||||||||||
| import { PieChart, Pie, Cell, Tooltip, ResponsiveContainer } from 'recharts' | ||||||||||||||||||||||
| import { formatTokenBalance } from '@/lib/wallet' | ||||||||||||||||||||||
| import Link from 'next/link' | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const INITIAL_SUPPLY = 1_000_000_000 // 1B tokens | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const TEAM_WALLETS = [ | ||||||||||||||||||||||
| { name: 'Treasury', address: '3xmpZy4SFCp6wzqgk4Mh7jg1yF3ggvG5Xqkt3F9CEbLj', balance: 250_000_000 }, | ||||||||||||||||||||||
| { name: 'Operations', address: 'CXcG8pN7fT9VqgaJcfH8YzjzD4xfH14RJCdxu2YgSUn9', balance: 50_000_000 }, | ||||||||||||||||||||||
| { name: 'Marketing', address: '4HwME8YgUCnYJdeL9N2qHcNXTc3swaP42gnikLzeGWih', balance: 30_000_000 }, | ||||||||||||||||||||||
| { name: 'Liquidity', address: 'HkENe7A1Ji4VwWUvDdDhcW7UZ5EKeosFVJeFt3PcTJSd', balance: 120_000_000 } | ||||||||||||||||||||||
| ] | ||||||||||||||||||||||
|
Comment on lines
+13
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainValidate team wallet data structure. The team wallets contain addresses that should be verified as valid Solana addresses. 🏁 Script executed: #!/bin/bash
# Check if these addresses follow Solana address format (base58, 32-44 characters)
echo "Validating Solana addresses:"
echo "3xmpZy4SFCp6wzqgk4Mh7jg1yF3ggvG5Xqkt3F9CEbLj" | wc -c
echo "CXcG8pN7fT9VqgaJcfH8YzjzD4xfH14RJCdxu2YgSUn9" | wc -c
echo "4HwME8YgUCnYJdeL9N2qHcNXTc3swaP42gnikLzeGWih" | wc -c
echo "HkENe7A1Ji4VwWUvDdDhcW7UZ5EKeosFVJeFt3PcTJSd" | wc -cLength of output: 318 Validate team wallet addresses using Solana PublicKey The length check shows each string is 45 bytes (including newline), which implies a 44-character base58 string—the upper limit for Solana keys—but length alone doesn’t guarantee validity. Please enforce proper address validation and typing: • Import and use Example diff: --- app/tokenomics/page.tsx
+++ app/tokenomics/page.tsx
@@ lines 13-18
- const TEAM_WALLETS = [
- { name: 'Treasury', address: '3xmpZy4SFCp6wzqgk4Mh7jg1yF3ggvG5Xqkt3F9CEbLj', balance: 250_000_000 },
+ import { PublicKey } from '@solana/web3.js'
+
+ const TEAM_WALLETS: { name: string; address: PublicKey; balance: number }[] = [
+ { name: 'Treasury', address: new PublicKey('3xmpZy4SFCp6wzqgk4Mh7jg1yF3ggvG5Xqkt3F9CEbLj'), balance: 250_000_000 },
🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const GOALS = [ | ||||||||||||||||||||||
| { name: 'Twitter Followers', target: 10000, value: 4200, burn: 0.5 }, | ||||||||||||||||||||||
| { name: 'Token Holders', target: 5000, value: 1300 }, | ||||||||||||||||||||||
| { name: 'Telegram Members', target: 2000, value: 800 } | ||||||||||||||||||||||
| ] | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export default function TokenomicsPage() { | ||||||||||||||||||||||
| const [now, setNow] = useState(Date.now()) | ||||||||||||||||||||||
| // pretend the market cap recently crossed $400k | ||||||||||||||||||||||
| const marketCap = 450_000 | ||||||||||||||||||||||
| const nextThreshold = 500_000 | ||||||||||||||||||||||
| const holdStart = Date.now() - 12 * 60 * 60 * 1000 // halfway through 24h hold | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||
| const id = setInterval(() => setNow(Date.now()), 1000) | ||||||||||||||||||||||
| return () => clearInterval(id) | ||||||||||||||||||||||
| }, []) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const countdown = Math.max(0, 24 * 60 * 60 * 1000 - (now - holdStart)) | ||||||||||||||||||||||
| const countdownPct = ((24 * 60 * 60 * 1000 - countdown) / (24 * 60 * 60 * 1000)) * 100 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const burnedTokens = 150_000_000 | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainConsider making burned tokens dynamic. The burned tokens value is hardcoded, but the PR mentions displaying "live data from the Solana blockchain." Is this hardcoded value intentional for the initial implementation, or should it fetch live burn data from the blockchain? 🏁 Script executed: #!/bin/bash
# Check if there are other components or API routes that might handle live token data
fd -e tsx -e ts | xargs rg -l "burn.*token|token.*burn" | head -10Length of output: 193 🏁 Script executed: #!/bin/bash
# Inspect the API route handling token burns to see if it provides live burn data
cat app/api/game/burn-tokens/route.tsLength of output: 7728 Implement dynamic burned tokens on the tokenomics page Next steps:
Location to update: const burnedTokens = 150_000_000with a dynamic fetch to your total-burned API. 🤖 Prompt for AI Agents |
||||||||||||||||||||||
| const burnedPercent = (burnedTokens / INITIAL_SUPPLY) * 100 | ||||||||||||||||||||||
| const teamTotal = TEAM_WALLETS.reduce((acc, w) => acc + w.balance, 0) | ||||||||||||||||||||||
| const teamPercent = (teamTotal / INITIAL_SUPPLY) * 100 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const pieData = [ | ||||||||||||||||||||||
| { name: 'Burned', value: burnedPercent }, | ||||||||||||||||||||||
| { name: 'Team', value: teamPercent } | ||||||||||||||||||||||
| ] | ||||||||||||||||||||||
| const colors = ['#FF3B3B', '#FFA500'] | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return ( | ||||||||||||||||||||||
| <div className="min-h-screen bg-gradient-to-br from-orange-400 via-yellow-400 to-orange-500 overflow-hidden text-gray-900"> | ||||||||||||||||||||||
| <div className="fixed inset-0 opacity-10" aria-hidden="true"> | ||||||||||||||||||||||
| <div className="absolute inset-0 bg-[linear-gradient(90deg,#000_1px,transparent_1px),linear-gradient(180deg,#000_1px,transparent_1px)] bg-[size:50px_50px] animate-pulse" /> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
| <Navbar /> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <div className="relative z-10 max-w-5xl mx-auto pt-24 px-4 flex flex-col gap-8"> | ||||||||||||||||||||||
| <h1 className="text-5xl md:text-6xl font-black tracking-tight">Tokenomics</h1> | ||||||||||||||||||||||
| <p className="text-lg md:text-xl text-gray-800">Live transparency into the GUDTEK ecosystem.</p> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <div className="bg-white/30 rounded-xl border-2 border-gray-900 p-4 shadow-xl"> | ||||||||||||||||||||||
| <ResponsiveContainer width="100%" height={260}> | ||||||||||||||||||||||
| <PieChart> | ||||||||||||||||||||||
| <Pie data={pieData} dataKey="value" nameKey="name" innerRadius={60} outerRadius={100} label> | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve pie chart accessibility. The pie chart lacks proper accessibility attributes for screen readers. - <Pie data={pieData} dataKey="value" nameKey="name" innerRadius={60} outerRadius={100} label>
+ <Pie
+ data={pieData}
+ dataKey="value"
+ nameKey="name"
+ innerRadius={60}
+ outerRadius={100}
+ label
+ aria-label="Token distribution chart"
+ >📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| {pieData.map((_, i) => ( | ||||||||||||||||||||||
| <Cell key={`cell-${i}`} fill={colors[i % colors.length]} /> | ||||||||||||||||||||||
| ))} | ||||||||||||||||||||||
| </Pie> | ||||||||||||||||||||||
| <Tooltip formatter={(v: number) => v.toFixed(2) + '%'} /> | ||||||||||||||||||||||
| </PieChart> | ||||||||||||||||||||||
| </ResponsiveContainer> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <div> | ||||||||||||||||||||||
| <h2 className="text-2xl font-bold mb-2">Team Wallets</h2> | ||||||||||||||||||||||
| <Table> | ||||||||||||||||||||||
| <TableHeader> | ||||||||||||||||||||||
| <TableRow> | ||||||||||||||||||||||
| <TableHead>Name</TableHead> | ||||||||||||||||||||||
| <TableHead>Balance</TableHead> | ||||||||||||||||||||||
| <TableHead>% Supply</TableHead> | ||||||||||||||||||||||
| <TableHead>Solscan</TableHead> | ||||||||||||||||||||||
| </TableRow> | ||||||||||||||||||||||
| </TableHeader> | ||||||||||||||||||||||
| <TableBody> | ||||||||||||||||||||||
| {TEAM_WALLETS.map(w => ( | ||||||||||||||||||||||
| <TableRow key={w.address}> | ||||||||||||||||||||||
| <TableCell className="font-bold">{w.name}</TableCell> | ||||||||||||||||||||||
| <TableCell>{formatTokenBalance(w.balance)}</TableCell> | ||||||||||||||||||||||
| <TableCell>{((w.balance / INITIAL_SUPPLY) * 100).toFixed(3)}%</TableCell> | ||||||||||||||||||||||
| <TableCell> | ||||||||||||||||||||||
| <Link href={`https://solscan.io/account/${w.address}`} target="_blank" className="text-blue-600 underline">View</Link> | ||||||||||||||||||||||
| </TableCell> | ||||||||||||||||||||||
| </TableRow> | ||||||||||||||||||||||
| ))} | ||||||||||||||||||||||
| </TableBody> | ||||||||||||||||||||||
| </Table> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <div> | ||||||||||||||||||||||
| <h2 className="text-2xl font-bold mb-2">Goals</h2> | ||||||||||||||||||||||
| <Table> | ||||||||||||||||||||||
| <TableHeader> | ||||||||||||||||||||||
| <TableRow> | ||||||||||||||||||||||
| <TableHead>Milestone</TableHead> | ||||||||||||||||||||||
| <TableHead className="w-32">Progress</TableHead> | ||||||||||||||||||||||
| <TableHead className="text-right">Target</TableHead> | ||||||||||||||||||||||
| <TableHead className="text-right">Current</TableHead> | ||||||||||||||||||||||
| </TableRow> | ||||||||||||||||||||||
| </TableHeader> | ||||||||||||||||||||||
| <TableBody> | ||||||||||||||||||||||
| {GOALS.map(g => ( | ||||||||||||||||||||||
| <TableRow key={g.name}> | ||||||||||||||||||||||
| <TableCell className="font-bold">{g.name}{g.burn ? ` – Burn ${g.burn}%` : ''}</TableCell> | ||||||||||||||||||||||
| <TableCell> | ||||||||||||||||||||||
| <Progress value={Math.min(100, (g.value / g.target) * 100)} /> | ||||||||||||||||||||||
| </TableCell> | ||||||||||||||||||||||
| <TableCell className="text-right">{g.target.toLocaleString()}</TableCell> | ||||||||||||||||||||||
| <TableCell className="text-right">{g.value.toLocaleString()}</TableCell> | ||||||||||||||||||||||
| </TableRow> | ||||||||||||||||||||||
| ))} | ||||||||||||||||||||||
| </TableBody> | ||||||||||||||||||||||
| </Table> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <div className="bg-white/30 rounded-xl border-2 border-gray-900 p-4 shadow-xl"> | ||||||||||||||||||||||
| <h2 className="text-xl font-bold mb-2">Next Burn</h2> | ||||||||||||||||||||||
| <p className="mb-2">1% burn every $100k in market cap (up to $1M) held for 24h.</p> | ||||||||||||||||||||||
| <p className="mb-2">Current Market Cap: ${marketCap.toLocaleString()}</p> | ||||||||||||||||||||||
| <p className="mb-2">Threshold: ${nextThreshold.toLocaleString()}</p> | ||||||||||||||||||||||
| <div className="space-y-1"> | ||||||||||||||||||||||
| <Progress value={countdownPct * 100} /> | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix incorrect progress calculation. The progress value is being multiplied by 100, but the Progress component typically expects values between 0-100, not 0-10000. - <Progress value={countdownPct * 100} />
+ <Progress value={countdownPct} />📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| <p className="text-sm">{(countdown / 3600000).toFixed(1)} hrs remaining</p> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Consider using a more reliable approach than HTML scraping.
Web scraping is fragile and may break when Telegram updates their HTML structure. Additionally, this approach might violate Telegram's terms of service.
Consider these alternatives:
🌐 Web query:
💡 Result:
To get the member count of a Telegram channel using the Telegram Bot API, you should use the
getChatMemberCountmethod. This provides the total number of members in a channel or group, but does not return a list of user IDs or personal data—only the total count.Steps to Get Telegram Channel Member Count
Create and add your bot as an admin to the channel (if it’s a private channel, the bot must have permission to see member info).
Obtain your bot token from BotFather.
Make an HTTP GET request to the following endpoint, replacing
<TOKEN>with your bot’s token and<CHANNEL_ID>with the username (for public channels) or the full unique channel ID (for private channels):@example_channel:Parse the response: The API will return a JSON object with a
resultfield containing the member count.Example Response
{ "ok": true, "result": 1523 }Here,
1523is the current member count of the channel.[1][4][8][10]
Notes and Limitations
In summary:
To get the Telegram channel member count using the Bot API, use the
getChatMemberCountmethod with your bot’s token and the channel’s identifier[1][4][8][10].Citations:
Use the Telegram Bot API’s getChatMemberCount instead of HTML scraping
Switching to the official Bot API will be far more reliable—and compliant—than parsing Telegram’s page markup.
Key updates for app/api/telegram-members/route.ts (lines 1–11):
okfield before readingresultSuggested replacement snippet:
– Ensure your bot is added as an admin (private channels) or a member (public channels).
– Store TELEGRAM_BOT_TOKEN and TELEGRAM_CHANNEL_ID securely (e.g. in your environment or secrets manager).
– Remove the old HTML‐scraping logic entirely.
🤖 Prompt for AI Agents