From f71460ff7483a49d3af0971d23568a084700171e Mon Sep 17 00:00:00 2001 From: Khubair Nasir M Nazir <95728079+dcccrypto@users.noreply.github.com> Date: Mon, 7 Jul 2025 03:20:42 +0100 Subject: [PATCH 1/2] Add dynamic Tokenomics page --- app/api/telegram-members/route.ts | 11 ++ app/tokenomics/page.tsx | 237 ++++++++++++++++++++++++++++++ components/Navbar.tsx | 3 +- 3 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 app/api/telegram-members/route.ts create mode 100644 app/tokenomics/page.tsx diff --git a/app/api/telegram-members/route.ts b/app/api/telegram-members/route.ts new file mode 100644 index 0000000..a3e9b44 --- /dev/null +++ b/app/api/telegram-members/route.ts @@ -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 }) + } +} diff --git a/app/tokenomics/page.tsx b/app/tokenomics/page.tsx new file mode 100644 index 0000000..d614ac5 --- /dev/null +++ b/app/tokenomics/page.tsx @@ -0,0 +1,237 @@ +'use client' + +import { useState, useEffect } 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 { getGudtekBalance, GUDTEK_TOKEN_MINT, formatTokenBalance } from '@/lib/wallet' +import { Connection } from '@solana/web3.js' +import Link from 'next/link' + +const INITIAL_SUPPLY = 1_000_000_000 // 1B tokens +const RPC_URL = 'https://mainnet.helius-rpc.com/?api-key=e568033d-06d6-49d1-ba90-b3564c91851b' + +const TEAM_WALLETS = [ + { name: 'Treasury', address: '3xmpZy4SFCp6wzqgk4Mh7jg1yF3ggvG5Xqkt3F9CEbLj' }, + { name: 'Operations', address: 'CXcG8pN7fT9VqgaJcfH8YzjzD4xfH14RJCdxu2YgSUn9' }, + { name: 'Marketing', address: '4HwME8YgUCnYJdeL9N2qHcNXTc3swaP42gnikLzeGWih' }, + { name: 'Liquidity', address: 'HkENe7A1Ji4VwWUvDdDhcW7UZ5EKeosFVJeFt3PcTJSd' } +] + +type Goal = { + name: string + target: number + value: number + burn?: number +} + +export default function TokenomicsPage() { + const [burnedPercent, setBurnedPercent] = useState(0) + const [teamPercent, setTeamPercent] = useState(0) + const [wallets, setWallets] = useState([]) + const [goals, setGoals] = useState([]) + const [marketCap, setMarketCap] = useState(0) + const [holdStart, setHoldStart] = useState(null) + + // Fetch token supply and wallet balances + useEffect(() => { + const connection = new Connection(RPC_URL, { + commitment: 'confirmed', + confirmTransactionInitialTimeout: 60000 + }) + + const fetchData = async () => { + try { + const supply = await connection.getTokenSupply(GUDTEK_TOKEN_MINT) + const currentSupply = supply.value.uiAmount || 0 + const burned = INITIAL_SUPPLY - currentSupply + setBurnedPercent((burned / INITIAL_SUPPLY) * 100) + + const updatedWallets = [] as typeof TEAM_WALLETS + let teamTotal = 0 + for (const w of TEAM_WALLETS) { + const bal = await getGudtekBalance(w.address) + teamTotal += bal + updatedWallets.push({ ...w, balance: bal }) + } + setTeamPercent((teamTotal / INITIAL_SUPPLY) * 100) + setWallets(updatedWallets as any) + } catch (err) { + console.error('Failed to fetch supply/wallet data', err) + } + } + + fetchData() + }, []) + + // Fetch market cap & social metrics + useEffect(() => { + const fetchMetrics = async () => { + try { + const res = await fetch('https://api.dexscreener.com/tokens/v1/solana/5QUgMieD3YQr9sEZjMAHKs1cKJiEhnvRNZatvzvcbonk') + const data = await res.json() + if (data && data.length > 0) { + const cap = data[0].marketCap as number + setMarketCap(cap) + } + } catch (err) { + console.error('Failed to fetch token metrics', err) + } + + try { + const twRes = await fetch('https://cdn.syndication.twimg.com/widgets/followbutton/info.json?screen_names=gudtek_solana') + const twJson = await twRes.json() + const followers = twJson?.[0]?.followers_count || 0 + + const holdersRes = await fetch('https://public-api.solscan.io/token/holders?tokenAddress=5QUgMieD3YQr9sEZjMAHKs1cKJiEhnvRNZatvzvcbonk&offset=0&limit=1') + const holdersJson = await holdersRes.json() + const holders = holdersJson?.total || 0 + + let telegram = 0 + try { + const tgRes = await fetch('/api/telegram-members') + const tgJson = await tgRes.json() + telegram = tgJson?.count || 0 + } catch {} + + setGoals([ + { name: 'Twitter Followers', target: 10000, value: followers, burn: 0.5 }, + { name: 'Token Holders', target: 5000, value: holders }, + { name: 'Telegram Members', target: 2000, value: telegram } + ]) + } catch (err) { + console.error('Failed to fetch social metrics', err) + } + } + + fetchMetrics() + const interval = setInterval(fetchMetrics, 60000) + return () => clearInterval(interval) + }, []) + + // Burn countdown logic + useEffect(() => { + const thresholds = Array.from({ length: 10 }, (_, i) => 100000 * (i + 1)) + const current = thresholds.find(t => marketCap < t) || 1000000 + const key = `mc_hold_${current}` + + if (marketCap >= current && current <= 1000000) { + const start = Number(localStorage.getItem(key)) || Date.now() + localStorage.setItem(key, start.toString()) + setHoldStart(start) + } else { + localStorage.removeItem(key) + setHoldStart(null) + } + }, [marketCap]) + + const countdown = holdStart ? Math.max(0, 24 * 3600 * 1000 - (Date.now() - holdStart)) : 0 + const countdownPct = holdStart ? ((24 * 3600 * 1000 - countdown) / (24 * 3600 * 1000)) * 100 : 0 + const nextThreshold = Math.min(Math.floor(marketCap / 100000) * 100000 + 100000, 1000000) + + const pieData = [ + { name: 'Burned', value: burnedPercent }, + { name: 'Team', value: teamPercent } + ] + const colors = ['#FF3B3B', '#FFA500'] + + return ( +
+ + ) +} + diff --git a/components/Navbar.tsx b/components/Navbar.tsx index f4b8f8e..a4d5da8 100644 --- a/components/Navbar.tsx +++ b/components/Navbar.tsx @@ -23,7 +23,8 @@ export default function Navbar() { { name: "GudMusic", href: "/music" }, { name: "GUD AI", href: "/pfp" }, { name: "Hackathon", href: "/hackathon" }, - { name: "Utility", href: "/utility" } + { name: "Utility", href: "/utility" }, + { name: "Tokenomics", href: "/tokenomics" }, ] // Close mobile menu when route changes From f021a0d43bee855eb6b5083c0dacdab1b58640be Mon Sep 17 00:00:00 2001 From: Khubair Nasir M Nazir <95728079+dcccrypto@users.noreply.github.com> Date: Mon, 7 Jul 2025 03:31:52 +0100 Subject: [PATCH 2/2] Use mock data for tokenomics page --- app/tokenomics/page.tsx | 163 +++++++++------------------------------- 1 file changed, 34 insertions(+), 129 deletions(-) diff --git a/app/tokenomics/page.tsx b/app/tokenomics/page.tsx index d614ac5..88288d8 100644 --- a/app/tokenomics/page.tsx +++ b/app/tokenomics/page.tsx @@ -1,134 +1,47 @@ 'use client' -import { useState, useEffect } from 'react' +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 { getGudtekBalance, GUDTEK_TOKEN_MINT, formatTokenBalance } from '@/lib/wallet' -import { Connection } from '@solana/web3.js' +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 RPC_URL = 'https://mainnet.helius-rpc.com/?api-key=e568033d-06d6-49d1-ba90-b3564c91851b' const TEAM_WALLETS = [ - { name: 'Treasury', address: '3xmpZy4SFCp6wzqgk4Mh7jg1yF3ggvG5Xqkt3F9CEbLj' }, - { name: 'Operations', address: 'CXcG8pN7fT9VqgaJcfH8YzjzD4xfH14RJCdxu2YgSUn9' }, - { name: 'Marketing', address: '4HwME8YgUCnYJdeL9N2qHcNXTc3swaP42gnikLzeGWih' }, - { name: 'Liquidity', address: 'HkENe7A1Ji4VwWUvDdDhcW7UZ5EKeosFVJeFt3PcTJSd' } + { 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 } ] -type Goal = { - name: string - target: number - value: number - burn?: number -} +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 [burnedPercent, setBurnedPercent] = useState(0) - const [teamPercent, setTeamPercent] = useState(0) - const [wallets, setWallets] = useState([]) - const [goals, setGoals] = useState([]) - const [marketCap, setMarketCap] = useState(0) - const [holdStart, setHoldStart] = useState(null) - - // Fetch token supply and wallet balances - useEffect(() => { - const connection = new Connection(RPC_URL, { - commitment: 'confirmed', - confirmTransactionInitialTimeout: 60000 - }) - - const fetchData = async () => { - try { - const supply = await connection.getTokenSupply(GUDTEK_TOKEN_MINT) - const currentSupply = supply.value.uiAmount || 0 - const burned = INITIAL_SUPPLY - currentSupply - setBurnedPercent((burned / INITIAL_SUPPLY) * 100) - - const updatedWallets = [] as typeof TEAM_WALLETS - let teamTotal = 0 - for (const w of TEAM_WALLETS) { - const bal = await getGudtekBalance(w.address) - teamTotal += bal - updatedWallets.push({ ...w, balance: bal }) - } - setTeamPercent((teamTotal / INITIAL_SUPPLY) * 100) - setWallets(updatedWallets as any) - } catch (err) { - console.error('Failed to fetch supply/wallet data', err) - } - } - - fetchData() - }, []) + 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 - // Fetch market cap & social metrics useEffect(() => { - const fetchMetrics = async () => { - try { - const res = await fetch('https://api.dexscreener.com/tokens/v1/solana/5QUgMieD3YQr9sEZjMAHKs1cKJiEhnvRNZatvzvcbonk') - const data = await res.json() - if (data && data.length > 0) { - const cap = data[0].marketCap as number - setMarketCap(cap) - } - } catch (err) { - console.error('Failed to fetch token metrics', err) - } - - try { - const twRes = await fetch('https://cdn.syndication.twimg.com/widgets/followbutton/info.json?screen_names=gudtek_solana') - const twJson = await twRes.json() - const followers = twJson?.[0]?.followers_count || 0 - - const holdersRes = await fetch('https://public-api.solscan.io/token/holders?tokenAddress=5QUgMieD3YQr9sEZjMAHKs1cKJiEhnvRNZatvzvcbonk&offset=0&limit=1') - const holdersJson = await holdersRes.json() - const holders = holdersJson?.total || 0 - - let telegram = 0 - try { - const tgRes = await fetch('/api/telegram-members') - const tgJson = await tgRes.json() - telegram = tgJson?.count || 0 - } catch {} - - setGoals([ - { name: 'Twitter Followers', target: 10000, value: followers, burn: 0.5 }, - { name: 'Token Holders', target: 5000, value: holders }, - { name: 'Telegram Members', target: 2000, value: telegram } - ]) - } catch (err) { - console.error('Failed to fetch social metrics', err) - } - } - - fetchMetrics() - const interval = setInterval(fetchMetrics, 60000) - return () => clearInterval(interval) + const id = setInterval(() => setNow(Date.now()), 1000) + return () => clearInterval(id) }, []) - // Burn countdown logic - useEffect(() => { - const thresholds = Array.from({ length: 10 }, (_, i) => 100000 * (i + 1)) - const current = thresholds.find(t => marketCap < t) || 1000000 - const key = `mc_hold_${current}` - - if (marketCap >= current && current <= 1000000) { - const start = Number(localStorage.getItem(key)) || Date.now() - localStorage.setItem(key, start.toString()) - setHoldStart(start) - } else { - localStorage.removeItem(key) - setHoldStart(null) - } - }, [marketCap]) - - const countdown = holdStart ? Math.max(0, 24 * 3600 * 1000 - (Date.now() - holdStart)) : 0 - const countdownPct = holdStart ? ((24 * 3600 * 1000 - countdown) / (24 * 3600 * 1000)) * 100 : 0 - const nextThreshold = Math.min(Math.floor(marketCap / 100000) * 100000 + 100000, 1000000) + 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 + 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 }, @@ -147,7 +60,6 @@ export default function TokenomicsPage() {

Tokenomics

Live transparency into the GUDTEK ecosystem.

- {/* Pie Chart */}
@@ -161,7 +73,6 @@ export default function TokenomicsPage() {
- {/* Team Wallets */}

Team Wallets

@@ -174,11 +85,11 @@ export default function TokenomicsPage() { - {wallets.map(w => ( + {TEAM_WALLETS.map(w => ( {w.name} - {formatTokenBalance((w as any).balance || 0)} - {(((w as any).balance || 0) / INITIAL_SUPPLY * 100).toFixed(3)}% + {formatTokenBalance(w.balance)} + {((w.balance / INITIAL_SUPPLY) * 100).toFixed(3)}% View @@ -188,7 +99,6 @@ export default function TokenomicsPage() {
- {/* Goals */}

Goals

@@ -201,7 +111,7 @@ export default function TokenomicsPage() { - {goals.map(g => ( + {GOALS.map(g => ( {g.name}{g.burn ? ` – Burn ${g.burn}%` : ''} @@ -215,20 +125,15 @@ export default function TokenomicsPage() {
- {/* Burn Countdown */}

Next Burn

1% burn every $100k in market cap (up to $1M) held for 24h.

Current Market Cap: ${marketCap.toLocaleString()}

Threshold: ${nextThreshold.toLocaleString()}

- {holdStart ? ( -
- -

{(countdown / 3600000).toFixed(1)} hrs remaining

-
- ) : ( -

Cap below threshold

- )} +
+ +

{(countdown / 3600000).toFixed(1)} hrs remaining

+