roger-tracker / index.html
fullstackufo's picture
Add 2 files
e4da769 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Solana Transaction Tracker</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.gradient-bg {
background: linear-gradient(135deg, #00ffbd 0%, #0088ff 100%);
}
.transaction-card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
.address-chip {
font-family: monospace;
background-color: rgba(0, 136, 255, 0.1);
border-left: 3px solid #0088ff;
}
.solana-gradient {
background: linear-gradient(135deg, #00ffbd 0%, #0088ff 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="gradient-bg text-white py-6 px-4 shadow-lg">
<div class="container mx-auto">
<div class="flex justify-between items-center">
<h1 class="text-3xl font-bold flex items-center">
<i class="fas fa-wallet mr-3"></i>
<span class="solana-gradient">Solana Tracker</span>
</h1>
<div class="flex items-center space-x-4">
<div class="bg-white bg-opacity-20 px-4 py-2 rounded-full">
<span id="current-time" class="font-mono"></span>
</div>
<button id="refresh-btn" class="bg-white text-blue-900 px-4 py-2 rounded-full hover:bg-blue-100 transition">
<i class="fas fa-sync-alt mr-2"></i> Refresh
</button>
</div>
</div>
<p class="mt-2 opacity-90">Tracking transactions for Solana address</p>
<div class="mt-3 px-4 py-2 rounded-md address-chip inline-flex items-center">
<span>2HZdRs4Cxkg1WbiPxPZcramcEPo22dzeemMAYZhZpump</span>
<button id="copy-address" class="ml-2 text-blue-200 hover:text-white">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
</div>
<div class="container mx-auto px-4 py-8">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Left Column -->
<div class="lg:col-span-2 space-y-6">
<!-- Summary Cards -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-white rounded-xl shadow p-6">
<div class="flex justify-between items-start">
<div>
<p class="text-gray-500">Total Inflow</p>
<h3 class="text-2xl font-bold mt-1 text-green-600" id="total-inflow">0.00 SOL</h3>
</div>
<div class="bg-green-100 p-3 rounded-full text-green-600">
<i class="fas fa-arrow-down"></i>
</div>
</div>
<p class="text-sm text-gray-500 mt-3" id="inflow-change">+0.00% today</p>
</div>
<div class="bg-white rounded-xl shadow p-6">
<div class="flex justify-between items-start">
<div>
<p class="text-gray-500">Total Outflow</p>
<h3 class="text-2xl font-bold mt-1 text-red-600" id="total-outflow">0.00 SOL</h3>
</div>
<div class="bg-red-100 p-3 rounded-full text-red-600">
<i class="fas fa-arrow-up"></i>
</div>
</div>
<p class="text-sm text-gray-500 mt-3" id="outflow-change">+0.00% today</p>
</div>
<div class="bg-white rounded-xl shadow p-6">
<div class="flex justify-between items-start">
<div>
<p class="text-gray-500">Balance</p>
<h3 class="text-2xl font-bold mt-1 text-blue-600" id="net-flow">0.00 SOL</h3>
</div>
<div class="bg-blue-100 p-3 rounded-full text-blue-600">
<i class="fas fa-coins"></i>
</div>
</div>
<p class="text-sm text-gray-500 mt-3" id="net-change">$0.00 USD</p>
</div>
</div>
<!-- Chart -->
<div class="bg-white rounded-xl shadow p-6">
<div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-semibold">Transaction History</h2>
<div class="flex space-x-2">
<button class="time-btn active bg-blue-100 text-blue-700 px-3 py-1 rounded-full text-sm" data-range="7">7D</button>
<button class="time-btn bg-gray-100 text-gray-700 px-3 py-1 rounded-full text-sm" data-range="30">30D</button>
<button class="time-btn bg-gray-100 text-gray-700 px-3 py-1 rounded-full text-sm" data-range="90">90D</button>
</div>
</div>
<div class="h-80">
<canvas id="flowChart"></canvas>
</div>
</div>
</div>
<!-- Right Column -->
<div class="space-y-6">
<!-- Address Info -->
<div class="bg-white rounded-xl shadow p-6">
<h2 class="text-xl font-semibold mb-4">Wallet Info</h2>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-gray-500">Network</span>
<span class="font-medium">Solana Mainnet</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">First Seen</span>
<span class="font-medium" id="first-seen">Loading...</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">Transactions</span>
<span class="font-medium" id="tx-count">0</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">SOL Price</span>
<span class="font-medium" id="sol-price">$0.00</span>
</div>
</div>
<div class="mt-4 pt-4 border-t border-gray-100">
<a href="https://solscan.io/account/2HZdRs4Cxkg1WbiPxPZcramcEPo22dzeemMAYZhZpump" target="_blank" class="w-full flex items-center justify-center bg-gray-100 text-gray-800 py-2 px-4 rounded-lg hover:bg-gray-200 transition">
<i class="fas fa-external-link-alt mr-2"></i> View on Solscan
</a>
</div>
</div>
<!-- Recent Transactions -->
<div class="bg-white rounded-xl shadow p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold">Recent Transactions</h2>
<div class="text-sm text-gray-500" id="last-updated">Just now</div>
</div>
<div class="space-y-3" id="transactions-list">
<!-- Transactions will be added here -->
<div class="text-center py-8 text-gray-400">
<div class="pulse">
<i class="fas fa-spinner fa-spin text-4xl mb-2"></i>
<p>Loading transactions...</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Transaction Details Modal -->
<div id="tx-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden">
<div class="bg-white rounded-xl p-6 w-full max-w-md max-h-[90vh] overflow-y-auto">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-semibold">Transaction Details</h3>
<button id="close-tx-modal" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<div id="tx-details-content">
<!-- Transaction details will be loaded here -->
</div>
</div>
</div>
<script>
// Initialize variables
const solanaAddress = "2HZdRs4Cxkg1WbiPxPZcramcEPo22dzeemMAYZhZpump";
let transactions = [];
let chart;
let chartRange = 7;
let solPrice = 0;
// DOM Elements
const refreshBtn = document.getElementById('refresh-btn');
const transactionsList = document.getElementById('transactions-list');
const timeButtons = document.querySelectorAll('.time-btn');
const copyAddressBtn = document.getElementById('copy-address');
const txModal = document.getElementById('tx-modal');
const closeTxModalBtn = document.getElementById('close-tx-modal');
// Initialize the app
document.addEventListener('DOMContentLoaded', function() {
updateCurrentTime();
setInterval(updateCurrentTime, 1000);
// Initialize chart
initChart();
// Load transactions
loadTransactions();
fetchSolPrice();
// Set up event listeners
refreshBtn.addEventListener('click', function() {
this.classList.add('animate-spin');
loadTransactions();
fetchSolPrice();
setTimeout(() => {
this.classList.remove('animate-spin');
}, 1000);
});
copyAddressBtn.addEventListener('click', function() {
navigator.clipboard.writeText(solanaAddress);
const originalIcon = this.innerHTML;
this.innerHTML = '<i class="fas fa-check"></i>';
setTimeout(() => {
this.innerHTML = originalIcon;
}, 2000);
});
timeButtons.forEach(btn => {
btn.addEventListener('click', function() {
timeButtons.forEach(b => {
b.classList.remove('active', 'bg-blue-100', 'text-blue-700');
b.classList.add('bg-gray-100', 'text-gray-700');
});
this.classList.add('active', 'bg-blue-100', 'text-blue-700');
this.classList.remove('bg-gray-100', 'text-gray-700');
chartRange = parseInt(this.dataset.range);
updateChart();
});
});
closeTxModalBtn.addEventListener('click', () => {
txModal.classList.add('hidden');
});
});
// Update current time
function updateCurrentTime() {
const now = new Date();
const options = {
weekday: 'short',
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
};
document.getElementById('current-time').textContent = now.toLocaleDateString('en-US', options);
document.getElementById('last-updated').textContent = 'Updated: ' + now.toLocaleTimeString();
}
// Load transactions from Solana API
async function loadTransactions() {
try {
// Show loading state
transactionsList.innerHTML = `
<div class="text-center py-8 text-gray-400">
<div class="pulse">
<i class="fas fa-spinner fa-spin text-4xl mb-2"></i>
<p>Loading transactions...</p>
</div>
</div>
`;
// In a real app, you would fetch from Solana RPC or indexer API
// For this demo, we'll use mock data
const mockTransactions = generateMockTransactions();
transactions = mockTransactions;
updateUI();
updateChart();
// Update first seen date
if (transactions.length > 0) {
const firstTxDate = new Date(transactions[transactions.length - 1].date);
document.getElementById('first-seen').textContent = firstTxDate.toLocaleDateString();
}
document.getElementById('tx-count').textContent = transactions.length;
} catch (error) {
console.error('Error loading transactions:', error);
transactionsList.innerHTML = `
<div class="text-center py-8 text-red-400">
<i class="fas fa-exclamation-triangle text-4xl mb-2"></i>
<p>Failed to load transactions</p>
<button onclick="loadTransactions()" class="mt-2 text-blue-600 hover:text-blue-800">
<i class="fas fa-redo mr-1"></i> Retry
</button>
</div>
`;
}
}
// Generate mock transactions for demo
function generateMockTransactions() {
const mockTxs = [];
const now = new Date();
const types = ['inflow', 'outflow'];
const reasons = [
'Token swap',
'NFT purchase',
'Staking reward',
'Airdrop',
'Payment received',
'Withdrawal',
'Deposit',
'Token transfer'
];
// Generate transactions for the past 90 days
for (let i = 0; i < 30; i++) {
const daysAgo = Math.floor(Math.random() * 90);
const date = new Date(now);
date.setDate(date.getDate() - daysAgo);
const type = types[Math.floor(Math.random() * types.length)];
const amount = parseFloat((Math.random() * 10).toFixed(4));
const reason = reasons[Math.floor(Math.random() * reasons.length)];
const txHash = generateRandomHash();
mockTxs.push({
id: txHash,
type: type,
amount: amount,
date: date.toISOString(),
reason: reason,
sender: type === 'inflow' ? generateRandomAddress() : solanaAddress,
receiver: type === 'inflow' ? solanaAddress : generateRandomAddress(),
fee: parseFloat((Math.random() * 0.001).toFixed(6))
});
}
// Sort by date
return mockTxs.sort((a, b) => new Date(b.date) - new Date(a.date));
}
// Generate random Solana address
function generateRandomAddress() {
const chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
let result = '';
for (let i = 0; i < 44; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
// Generate random transaction hash
function generateRandomHash() {
const chars = '0123456789abcdef';
let result = '';
for (let i = 0; i < 64; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
// Fetch current SOL price (simulated)
async function fetchSolPrice() {
try {
// Simulate API call with random variation
const basePrice = 150; // Base SOL price
const variation = (Math.random() * 30) - 15; // +/- $15
solPrice = parseFloat((basePrice + variation).toFixed(2));
document.getElementById('sol-price').textContent = `$${solPrice}`;
// Update balance in USD
const netFlowElement = document.getElementById('net-flow');
const netFlow = parseFloat(netFlowElement.textContent);
document.getElementById('net-change').textContent = `$${(netFlow * solPrice).toFixed(2)} USD`;
} catch (error) {
console.error('Error fetching SOL price:', error);
}
}
// Update all UI elements
function updateUI() {
updateTransactionSummary();
updateTransactionsList();
}
// Update transaction summary cards
function updateTransactionSummary() {
const totalInflow = transactions
.filter(t => t.type === 'inflow')
.reduce((sum, t) => sum + t.amount, 0);
const totalOutflow = transactions
.filter(t => t.type === 'outflow')
.reduce((sum, t) => sum + t.amount, 0);
const netFlow = totalInflow - totalOutflow;
document.getElementById('total-inflow').textContent = `${totalInflow.toFixed(4)} SOL`;
document.getElementById('total-outflow').textContent = `${totalOutflow.toFixed(4)} SOL`;
document.getElementById('net-flow').textContent = `${netFlow.toFixed(4)} SOL`;
// Update USD values if price is available
if (solPrice > 0) {
document.getElementById('net-change').textContent = `$${(netFlow * solPrice).toFixed(2)} USD`;
}
// Random changes for demo purposes
const inflowChange = (Math.random() * 5).toFixed(2);
const outflowChange = (Math.random() * 5).toFixed(2);
document.getElementById('inflow-change').textContent = `+${inflowChange}% today`;
document.getElementById('outflow-change').textContent = `+${outflowChange}% today`;
}
// Update transactions list
function updateTransactionsList() {
if (transactions.length === 0) {
transactionsList.innerHTML = `
<div class="text-center py-8 text-gray-400">
<i class="fas fa-exchange-alt text-4xl mb-2"></i>
<p>No transactions found</p>
</div>
`;
return;
}
// Show only the most recent 10 transactions
const recentTransactions = transactions.slice(0, 10);
transactionsList.innerHTML = recentTransactions.map(tx => `
<div class="transaction-card bg-white border border-gray-100 rounded-lg p-4 hover:shadow-md transition cursor-pointer" onclick="showTxDetails('${tx.id}')">
<div class="flex justify-between items-center">
<div class="flex items-center">
<div class="${tx.type === 'inflow' ? 'bg-green-100 text-green-600' : 'bg-red-100 text-red-600'} p-2 rounded-full mr-3">
<i class="fas ${tx.type === 'inflow' ? 'fa-arrow-down' : 'fa-arrow-up'}"></i>
</div>
<div>
<p class="font-medium">${tx.reason}</p>
<p class="text-sm text-gray-500">${formatDate(tx.date)}</p>
</div>
</div>
<div class="text-right">
<p class="${tx.type === 'inflow' ? 'text-green-600' : 'text-red-600'} font-semibold">
${tx.type === 'inflow' ? '+' : '-'}${tx.amount.toFixed(4)}
</p>
<p class="text-xs text-gray-500">SOL</p>
</div>
</div>
</div>
`).join('');
}
// Show transaction details modal
function showTxDetails(txId) {
const tx = transactions.find(t => t.id === txId);
if (!tx) return;
const txDetailsContent = document.getElementById('tx-details-content');
txDetailsContent.innerHTML = `
<div class="space-y-4">
<div class="flex justify-between">
<span class="text-gray-500">Status</span>
<span class="font-medium text-green-600">Confirmed</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">Date</span>
<span class="font-medium">${formatDate(tx.date)}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">Type</span>
<span class="font-medium ${tx.type === 'inflow' ? 'text-green-600' : 'text-red-600'}">
${tx.type === 'inflow' ? 'Incoming' : 'Outgoing'} Transfer
</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">Amount</span>
<span class="font-medium">${tx.amount.toFixed(4)} SOL ($${(tx.amount * solPrice).toFixed(2)})</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">Transaction Fee</span>
<span class="font-medium">${tx.fee.toFixed(6)} SOL</span>
</div>
<div>
<p class="text-gray-500 mb-1">Description</p>
<p class="font-medium">${tx.reason}</p>
</div>
<div class="${tx.type === 'inflow' ? 'block' : 'hidden'}">
<p class="text-gray-500 mb-1">From</p>
<p class="font-mono text-sm bg-gray-100 p-2 rounded break-all">${tx.sender}</p>
</div>
<div class="${tx.type === 'outflow' ? 'block' : 'hidden'}">
<p class="text-gray-500 mb-1">To</p>
<p class="font-mono text-sm bg-gray-100 p-2 rounded break-all">${tx.receiver}</p>
</div>
<div class="pt-4 border-t border-gray-100">
<a href="https://solscan.io/tx/${tx.id}" target="_blank" class="w-full flex items-center justify-center bg-blue-100 text-blue-800 py-2 px-4 rounded-lg hover:bg-blue-200 transition">
<i class="fas fa-external-link-alt mr-2"></i> View on Solscan
</a>
</div>
</div>
`;
txModal.classList.remove('hidden');
}
// Initialize chart
function initChart() {
const ctx = document.getElementById('flowChart').getContext('2d');
chart = new Chart(ctx, {
type: 'bar',
data: {
labels: [],
datasets: [
{
label: 'Inflow',
data: [],
backgroundColor: '#10B981',
borderRadius: 6
},
{
label: 'Outflow',
data: [],
backgroundColor: '#EF4444',
borderRadius: 6
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top',
},
tooltip: {
callbacks: {
label: function(context) {
return `${context.dataset.label}: ${context.raw.toFixed(4)} SOL`;
}
}
}
},
scales: {
x: {
grid: {
display: false
}
},
y: {
beginAtZero: true,
ticks: {
callback: function(value) {
return value.toFixed(2);
}
}
}
}
}
});
updateChart();
}
// Update chart data
function updateChart() {
const now = new Date();
const pastDate = new Date();
pastDate.setDate(now.getDate() - chartRange);
// Generate labels for the selected range
const labels = [];
const days = chartRange;
for (let i = days; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
labels.push(formatChartDate(date));
}
// Initialize data arrays with zeros
const inflowData = new Array(labels.length).fill(0);
const outflowData = new Array(labels.length).fill(0);
// Populate data arrays
transactions.forEach(tx => {
const txDate = new Date(tx.date);
if (txDate >= pastDate) {
const dayDiff = Math.floor((now - txDate) / (1000 * 60 * 60 * 24));
const index = labels.length - 1 - dayDiff;
if (index >= 0 && index < labels.length) {
if (tx.type === 'inflow') {
inflowData[index] += tx.amount;
} else {
outflowData[index] += tx.amount;
}
}
}
});
// Update chart
chart.data.labels = labels;
chart.data.datasets[0].data = inflowData;
chart.data.datasets[1].data = outflowData;
chart.update();
}
// Helper functions
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
}
function formatChartDate(date) {
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric'
});
}
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=fullstackufo/roger-tracker" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>