ubi-wallet / index.html
Paresiast's picture
Peux tu ajouter cet API Etherscan pour récupérer la quantité de token dans l'adresse du wallet : GC8914RSFX9T1Q6FRQ8F6UVW7TBC5M2VIM - Initial Deployment
3c268ed verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MultiChain Wallet Inspector</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<style>
.fade-in {
animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.dark {
background-color: #1a202c;
color: #e2e8f0;
}
.dark .card {
background-color: #2d3748;
border-color: #4a5568;
}
.dark input, .dark select {
background-color: #2d3748;
border-color: #4a5568;
color: #e2e8f0;
}
.dark .modal-content {
background-color: #2d3748;
}
.modal {
display: none;
position: fixed;
z-index: 100;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.4);
}
.modal-content {
background-color: #fefefe;
margin: 10% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 500px;
border-radius: 8px;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: black;
}
</style>
</head>
<body class="bg-gray-100">
<div class="container mx-auto px-4 py-8">
<!-- Header -->
<header class="flex justify-between items-center mb-8">
<h1 class="text-3xl font-bold text-indigo-600">MultiChain Wallet Inspector</h1>
<div class="flex items-center space-x-4">
<button id="themeToggle" class="p-2 rounded-full bg-gray-200 dark:bg-gray-700">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
</svg>
</button>
<button id="addWalletBtn" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition">
Add Wallet
</button>
</div>
</header>
<!-- Dashboard -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Wallet Selector and Summary -->
<div class="lg:col-span-1 space-y-6">
<div class="card bg-white rounded-lg shadow p-6">
<h2 class="text-xl font-semibold mb-4">Wallet Selector</h2>
<div class="space-y-4">
<select id="walletSelector" class="w-full p-2 border rounded">
<option value="">Select a wallet</option>
</select>
<button id="manageWalletsBtn" class="w-full py-2 text-indigo-600 border border-indigo-600 rounded hover:bg-indigo-50 transition">
Manage Wallets
</button>
</div>
</div>
<div class="card bg-white rounded-lg shadow p-6">
<h2 class="text-xl font-semibold mb-4">Portfolio Summary</h2>
<div class="space-y-2">
<div class="flex justify-between">
<span>Total Value:</span>
<span id="totalValue" class="font-bold">$0.00</span>
</div>
<div class="flex justify-between">
<span>24h Change:</span>
<span id="dailyChange" class="font-bold">0.00%</span>
</div>
<div class="flex justify-between">
<span>Wallets:</span>
<span id="walletCount" class="font-bold">0</span>
</div>
<div class="flex justify-between">
<span>Tokens:</span>
<span id="tokenCount" class="font-bold">0</span>
</div>
</div>
<div class="mt-4">
<select id="currencySelector" class="w-full p-2 border rounded">
<option value="usd">USD</option>
<option value="eur">EUR</option>
</select>
</div>
<button id="exportBtn" class="w-full mt-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 transition">
Export CSV
</button>
</div>
</div>
<!-- Main Content -->
<div class="lg:col-span-2 space-y-6">
<div class="card bg-white rounded-lg shadow p-6">
<h2 class="text-xl font-semibold mb-4">Portfolio Value History</h2>
<div class="flex justify-between mb-4">
<select id="timeRangeSelector" class="p-2 border rounded">
<option value="7">7 Days</option>
<option value="30" selected>30 Days</option>
<option value="90">3 Months</option>
<option value="180">6 Months</option>
<option value="365">1 Year</option>
</select>
<select id="intervalSelector" class="p-2 border rounded">
<option value="daily">Daily</option>
<option value="weekly">Weekly</option>
<option value="monthly">Monthly</option>
</select>
</div>
<canvas id="portfolioChart" height="300"></canvas>
</div>
<div class="card bg-white rounded-lg shadow p-6">
<h2 class="text-xl font-semibold mb-4">Token Balances</h2>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead>
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Token</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Blockchain</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Balance</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Value</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">24h Change</th>
</tr>
</thead>
<tbody id="tokenTableBody" class="bg-white divide-y divide-gray-200">
<!-- Tokens will be added here dynamically -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Add Wallet Modal -->
<div id="addWalletModal" class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<h2 class="text-xl font-semibold mb-4">Add New Wallet</h2>
<form id="walletForm" class="space-y-4">
<div>
<label for="walletName" class="block text-sm font-medium">Wallet Name</label>
<input type="text" id="walletName" class="w-full p-2 border rounded" required>
</div>
<div>
<label for="walletAddress" class="block text-sm font-medium">Wallet Address</label>
<input type="text" id="walletAddress" class="w-full p-2 border rounded" required>
</div>
<div>
<label for="blockchainType" class="block text-sm font-medium">Blockchain</label>
<select id="blockchainType" class="w-full p-2 border rounded" required>
<option value="ethereum">Ethereum</option>
<option value="binance-smart-chain">Binance Smart Chain</option>
<option value="polygon">Polygon</option>
<option value="avalanche">Avalanche</option>
<option value="solana">Solana</option>
</select>
</div>
<button type="submit" class="w-full py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700 transition">
Save Wallet
</button>
</form>
</div>
</div>
<!-- Manage Wallets Modal -->
<div id="manageWalletsModal" class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<h2 class="text-xl font-semibold mb-4">Manage Wallets</h2>
<div id="walletsList" class="space-y-2 mb-4">
<!-- Wallets will be listed here -->
</div>
<button id="addNewWalletBtn" class="w-full py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700 transition">
Add New Wallet
</button>
</div>
</div>
<script>
// Initialize app
document.addEventListener('DOMContentLoaded', function() {
// Theme toggle
const themeToggle = document.getElementById('themeToggle');
themeToggle.addEventListener('click', toggleTheme);
// Check for saved theme preference
if (localStorage.getItem('theme') === 'dark') {
document.body.classList.add('dark');
}
// Modal elements
const addWalletModal = document.getElementById('addWalletModal');
const manageWalletsModal = document.getElementById('manageWalletsModal');
const addWalletBtn = document.getElementById('addWalletBtn');
const manageWalletsBtn = document.getElementById('manageWalletsBtn');
const addNewWalletBtn = document.getElementById('addNewWalletBtn');
const closeButtons = document.querySelectorAll('.close');
// Event listeners for modals
addWalletBtn.addEventListener('click', () => addWalletModal.style.display = 'block');
manageWalletsBtn.addEventListener('click', () => {
loadWalletsList();
manageWalletsModal.style.display = 'block';
});
addNewWalletBtn.addEventListener('click', () => {
manageWalletsModal.style.display = 'none';
addWalletModal.style.display = 'block';
});
closeButtons.forEach(btn => {
btn.addEventListener('click', function() {
addWalletModal.style.display = 'none';
manageWalletsModal.style.display = 'none';
});
});
// Close modals when clicking outside
window.addEventListener('click', function(event) {
if (event.target === addWalletModal) {
addWalletModal.style.display = 'none';
}
if (event.target === manageWalletsModal) {
manageWalletsModal.style.display = 'none';
}
});
// Form submission
const walletForm = document.getElementById('walletForm');
walletForm.addEventListener('submit', function(e) {
e.preventDefault();
saveWallet();
addWalletModal.style.display = 'none';
});
// Initialize wallet selector
updateWalletSelector();
// Initialize chart
initChart();
// Load data if wallets exist
if (getWallets().length > 0) {
loadWalletData();
}
// Event listeners for selectors
document.getElementById('walletSelector').addEventListener('change', loadWalletData);
document.getElementById('currencySelector').addEventListener('change', updateTokenValues);
document.getElementById('timeRangeSelector').addEventListener('change', updateChartData);
document.getElementById('intervalSelector').addEventListener('change', updateChartData);
// Export button
document.getElementById('exportBtn').addEventListener('click', exportToCSV);
});
// Theme toggle function
function toggleTheme() {
document.body.classList.toggle('dark');
const isDark = document.body.classList.contains('dark');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
}
// Wallet management functions
function getWallets() {
return JSON.parse(localStorage.getItem('wallets')) || [];
}
function saveWallet() {
const wallets = getWallets();
const walletName = document.getElementById('walletName').value;
const walletAddress = document.getElementById('walletAddress').value;
const blockchainType = document.getElementById('blockchainType').value;
const newWallet = {
id: Date.now().toString(),
name: walletName,
address: walletAddress,
blockchain: blockchainType,
tokens: []
};
wallets.push(newWallet);
localStorage.setItem('wallets', JSON.stringify(wallets));
// Reset form
document.getElementById('walletForm').reset();
// Update UI
updateWalletSelector();
loadWalletData();
updateSummary();
}
function updateWalletSelector() {
const selector = document.getElementById('walletSelector');
selector.innerHTML = '<option value="">Select a wallet</option>';
const wallets = getWallets();
wallets.forEach(wallet => {
const option = document.createElement('option');
option.value = wallet.id;
option.textContent = `${wallet.name} (${wallet.blockchain})`;
selector.appendChild(option);
});
updateSummary();
}
function loadWalletsList() {
const walletsList = document.getElementById('walletsList');
walletsList.innerHTML = '';
const wallets = getWallets();
wallets.forEach(wallet => {
const walletDiv = document.createElement('div');
walletDiv.className = 'flex justify-between items-center p-2 border rounded';
const walletInfo = document.createElement('div');
walletInfo.innerHTML = `
<div class="font-medium">${wallet.name}</div>
<div class="text-sm text-gray-500">${wallet.address.substring(0, 10)}...${wallet.address.substring(wallet.address.length - 5)}</div>
<div class="text-xs">${wallet.blockchain}</div>
`;
const walletActions = document.createElement('div');
walletActions.className = 'flex space-x-2';
const editBtn = document.createElement('button');
editBtn.className = 'px-2 py-1 bg-blue-500 text-white rounded text-xs';
editBtn.textContent = 'Edit';
editBtn.addEventListener('click', () => editWallet(wallet.id));
const deleteBtn = document.createElement('button');
deleteBtn.className = 'px-2 py-1 bg-red-500 text-white rounded text-xs';
deleteBtn.textContent = 'Delete';
deleteBtn.addEventListener('click', () => deleteWallet(wallet.id));
walletActions.appendChild(editBtn);
walletActions.appendChild(deleteBtn);
walletDiv.appendChild(walletInfo);
walletDiv.appendChild(walletActions);
walletsList.appendChild(walletDiv);
});
}
function editWallet(walletId) {
const wallets = getWallets();
const wallet = wallets.find(w => w.id === walletId);
if (wallet) {
document.getElementById('walletName').value = wallet.name;
document.getElementById('walletAddress').value = wallet.address;
document.getElementById('blockchainType').value = wallet.blockchain;
// We need to store the wallet ID for updating
document.getElementById('walletForm').dataset.editingId = walletId;
manageWalletsModal.style.display = 'none';
addWalletModal.style.display = 'block';
}
}
function deleteWallet(walletId) {
if (confirm('Are you sure you want to delete this wallet?')) {
let wallets = getWallets();
wallets = wallets.filter(w => w.id !== walletId);
localStorage.setItem('wallets', JSON.stringify(wallets));
loadWalletsList();
updateWalletSelector();
// If we deleted the currently selected wallet, clear the display
if (document.getElementById('walletSelector').value === walletId) {
document.getElementById('walletSelector').value = '';
clearTokenTable();
updateSummary();
}
}
}
// API configurations
const CMC_API_KEY = '2bece8bf-223e-40a7-8b27-f30244d87888';
const CMC_API_URL = 'https://pro-api.coinmarketcap.com/v1/cryptocurrency';
const ETHERSCAN_API_KEY = 'GC8914RSFX9T1Q6FRQ8F6UVW7TBC5M2VIM';
const ETHERSCAN_API_URL = 'https://api.etherscan.io/api';
// Data loading functions
async function loadWalletData() {
const walletId = document.getElementById('walletSelector').value;
if (!walletId) {
clearTokenTable();
updateSummary();
return;
}
const wallets = getWallets();
const wallet = wallets.find(w => w.id === walletId);
if (!wallet) return;
// Show loading state
const tokenTableBody = document.getElementById('tokenTableBody');
tokenTableBody.innerHTML = '<tr><td colspan="5" class="text-center py-4">Loading wallet data...</td></tr>';
try {
let tokens = [];
if (wallet.blockchain === 'ethereum') {
// Fetch real token balances from Etherscan for Ethereum wallets
tokens = await getEthereumTokenBalances(wallet.address);
} else {
// For other blockchains, use mock data
tokens = getMockTokens(wallet.blockchain);
}
// Get token prices
const tokenPrices = await getTokenPrices(tokens.map(t => t.id));
// Update wallet with tokens and prices
wallet.tokens = mockTokens.map(token => {
return {
...token,
price: tokenPrices[token.id]?.usd || 0,
priceChange24h: tokenPrices[token.id]?.usd_24h_change || 0
};
});
// Save updated wallet
const updatedWallets = wallets.map(w => w.id === wallet.id ? wallet : w);
localStorage.setItem('wallets', JSON.stringify(updatedWallets));
// Update UI
updateTokenTable(wallet);
updateSummary();
updateChartData();
} catch (error) {
console.error('Error loading wallet data:', error);
tokenTableBody.innerHTML = '<tr><td colspan="5" class="text-center py-4 text-red-500">Error loading wallet data</td></tr>';
}
}
async function getEthereumTokenBalances(walletAddress) {
try {
// First get ETH balance
const ethResponse = await axios.get(ETHERSCAN_API_URL, {
params: {
module: 'account',
action: 'balance',
address: walletAddress,
tag: 'latest',
apikey: ETHERSCAN_API_KEY
}
});
const ethBalance = parseFloat(ethResponse.data.result) / 1e18;
// Then get token balances
const tokensResponse = await axios.get(ETHERSCAN_API_URL, {
params: {
module: 'account',
action: 'tokentx',
address: walletAddress,
startblock: 0,
endblock: 99999999,
sort: 'asc',
apikey: ETHERSCAN_API_KEY
}
});
// Extract unique tokens from transactions
const tokenMap = {};
tokensResponse.data.result.forEach(tx => {
if (tx.contractAddress && !tokenMap[tx.contractAddress]) {
tokenMap[tx.contractAddress] = {
address: tx.contractAddress,
symbol: tx.tokenSymbol,
name: tx.tokenName,
decimals: parseInt(tx.tokenDecimal)
};
}
});
// Get balances for each token
const tokens = [];
// Add ETH first
tokens.push({
id: 'ethereum',
symbol: 'ETH',
name: 'Ethereum',
balance: ethBalance,
address: '0x'
});
// Get balances for other tokens
for (const [address, tokenInfo] of Object.entries(tokenMap)) {
const balanceResponse = await axios.get(ETHERSCAN_API_URL, {
params: {
module: 'account',
action: 'tokenbalance',
contractaddress: address,
address: walletAddress,
tag: 'latest',
apikey: ETHERSCAN_API_KEY
}
});
const balance = parseFloat(balanceResponse.data.result) / (10 ** tokenInfo.decimals);
if (balance > 0) {
tokens.push({
id: tokenInfo.symbol.toLowerCase(),
symbol: tokenInfo.symbol,
name: tokenInfo.name,
balance: balance,
address: address
});
}
}
return tokens;
} catch (error) {
console.error('Error fetching token balances from Etherscan:', error);
// Fall back to mock data if API fails
return getMockTokens('ethereum');
}
}
function getMockTokens(blockchain) {
// Mock data for demo purposes
const tokensByChain = {
'ethereum': [
{ id: 'ethereum', symbol: 'ETH', name: 'Ethereum', balance: Math.random() * 10, address: '0x' },
{ id: 'usd-coin', symbol: 'USDC', name: 'USD Coin', balance: Math.random() * 1000, address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' },
{ id: 'chainlink', symbol: 'LINK', name: 'Chainlink', balance: Math.random() * 100, address: '0x514910771af9ca656af840dff83e8264ecf986ca' }
],
'binance-smart-chain': [
{ id: 'binancecoin', symbol: 'BNB', name: 'Binance Coin', balance: Math.random() * 5, address: '0x' },
{ id: 'pancakeswap-token', symbol: 'CAKE', name: 'PancakeSwap', balance: Math.random() * 100, address: '0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82' },
{ id: 'usd-coin', symbol: 'USDC', name: 'USD Coin', balance: Math.random() * 500, address: '0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d' }
],
'polygon': [
{ id: 'matic-network', symbol: 'MATIC', name: 'Polygon', balance: Math.random() * 1000, address: '0x' },
{ id: 'aave', symbol: 'AAVE', name: 'Aave', balance: Math.random() * 10, address: '0xd6df932a45c0f255f85145f286ea0b292b21c90b' },
{ id: 'usd-coin', symbol: 'USDC', name: 'USD Coin', balance: Math.random() * 500, address: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174' }
],
'avalanche': [
{ id: 'avalanche-2', symbol: 'AVAX', name: 'Avalanche', balance: Math.random() * 50, address: '0x' },
{ id: 'aave', symbol: 'AAVE', name: 'Aave', balance: Math.random() * 5, address: '0x63a72806098bd3d9520cc43356dd78afe5d386d9' },
{ id: 'usd-coin', symbol: 'USDC', name: 'USD Coin', balance: Math.random() * 500, address: '0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e' }
],
'solana': [
{ id: 'solana', symbol: 'SOL', name: 'Solana', balance: Math.random() * 20, address: '' },
{ id: 'serum', symbol: 'SRM', name: 'Serum', balance: Math.random() * 100, address: 'SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt' },
{ id: 'usd-coin', symbol: 'USDC', name: 'USD Coin', balance: Math.random() * 500, address: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' }
]
};
return tokensByChain[blockchain] || [];
}
async function getTokenPrices(tokenIds) {
try {
// Map our token IDs to CoinMarketCap IDs
const cmcIdMap = {
'ethereum': 1027,
'usd-coin': 3408,
'chainlink': 1975,
'binancecoin': 1839,
'pancakeswap-token': 7186,
'matic-network': 3890,
'aave': 7278,
'avalanche-2': 5805,
'solana': 5426,
'serum': 6187
};
const ids = tokenIds.map(id => cmcIdMap[id]).filter(id => id);
if (ids.length === 0) return {};
const response = await axios.get(`${CMC_API_URL}/quotes/latest`, {
params: {
id: ids.join(','),
convert: 'USD,EUR'
},
headers: {
'X-CMC_PRO_API_KEY': CMC_API_KEY
}
});
const result = {};
tokenIds.forEach(id => {
if (cmcIdMap[id] && response.data.data[cmcIdMap[id]]) {
const data = response.data.data[cmcIdMap[id]];
result[id] = {
usd: data.quote.USD.price,
eur: data.quote.EUR.price,
usd_24h_change: data.quote.USD.percent_change_24h
};
}
});
return result;
} catch (error) {
console.error('Error fetching prices from CoinMarketCap:', error);
return {};
}
}
// UI update functions
function updateTokenTable(wallet) {
const tokenTableBody = document.getElementById('tokenTableBody');
tokenTableBody.innerHTML = '';
if (!wallet || !wallet.tokens || wallet.tokens.length === 0) {
tokenTableBody.innerHTML = '<tr><td colspan="5" class="text-center py-4">No tokens found in this wallet</td></tr>';
return;
}
const currency = document.getElementById('currencySelector').value;
wallet.tokens.forEach(token => {
const value = token.price * token.balance;
const change24h = token.priceChange24h;
const row = document.createElement('tr');
row.className = 'fade-in';
row.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="flex-shrink-0 h-10 w-10 rounded-full bg-gray-200 flex items-center justify-center">
${token.symbol}
</div>
<div class="ml-4">
<div class="text-sm font-medium">${token.name}</div>
<div class="text-sm text-gray-500">${token.symbol}</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">${wallet.blockchain}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">${token.balance.toFixed(6)}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">${currency === 'usd' ? '$' : '€'}${value.toFixed(2)}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm ${change24h >= 0 ? 'text-green-600' : 'text-red-600'}">
${change24h >= 0 ? '↑' : '↓'} ${Math.abs(change24h).toFixed(2)}%
</td>
`;
tokenTableBody.appendChild(row);
});
}
function clearTokenTable() {
const tokenTableBody = document.getElementById('tokenTableBody');
tokenTableBody.innerHTML = '<tr><td colspan="5" class="text-center py-4">Select a wallet to view tokens</td></tr>';
}
function updateTokenValues() {
const walletId = document.getElementById('walletSelector').value;
if (!walletId) return;
const wallets = getWallets();
const wallet = wallets.find(w => w.id === walletId);
if (wallet) {
updateTokenTable(wallet);
updateSummary();
}
}
function updateSummary() {
const wallets = getWallets();
const walletId = document.getElementById('walletSelector').value;
const currency = document.getElementById('currencySelector').value;
document.getElementById('walletCount').textContent = wallets.length;
if (!walletId) {
document.getElementById('totalValue').textContent = currency === 'usd' ? '$0.00' : '€0.00';
document.getElementById('dailyChange').textContent = '0.00%';
document.getElementById('tokenCount').textContent = '0';
return;
}
const wallet = wallets.find(w => w.id === walletId);
if (!wallet || !wallet.tokens) {
document.getElementById('totalValue').textContent = currency === 'usd' ? '$0.00' : '€0.00';
document.getElementById('dailyChange').textContent = '0.00%';
document.getElementById('tokenCount').textContent = '0';
return;
}
document.getElementById('tokenCount').textContent = wallet.tokens.length;
let totalValue = 0;
let weightedChange = 0;
let totalWeight = 0;
wallet.tokens.forEach(token => {
const value = token.price * token.balance;
totalValue += value;
if (token.priceChange24h) {
weightedChange += value * token.priceChange24h;
totalWeight += value;
}
});
const averageChange = totalWeight > 0 ? weightedChange / totalWeight : 0;
document.getElementById('totalValue').textContent =
(currency === 'usd' ? '$' : '€') + totalValue.toFixed(2);
document.getElementById('dailyChange').textContent =
(averageChange >= 0 ? '+' : '') + averageChange.toFixed(2) + '%';
document.getElementById('dailyChange').className =
averageChange >= 0 ? 'font-bold text-green-600' : 'font-bold text-red-600';
}
// Chart functions
let portfolioChart = null;
function initChart() {
const ctx = document.getElementById('portfolioChart').getContext('2d');
portfolioChart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Portfolio Value',
data: [],
borderColor: 'rgb(79, 70, 229)',
backgroundColor: 'rgba(79, 70, 229, 0.1)',
tension: 0.1,
fill: true
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'top',
},
tooltip: {
mode: 'index',
intersect: false,
}
},
scales: {
y: {
beginAtZero: false
}
}
}
});
}
async function updateChartData() {
const walletId = document.getElementById('walletSelector').value;
if (!walletId) {
portfolioChart.data.labels = [];
portfolioChart.data.datasets[0].data = [];
portfolioChart.update();
return;
}
const timeRange = parseInt(document.getElementById('timeRangeSelector').value);
const interval = document.getElementById('intervalSelector').value;
const currency = document.getElementById('currencySelector').value;
// Show loading state
portfolioChart.data.labels = ['Loading...'];
portfolioChart.data.datasets[0].data = [0];
portfolioChart.update();
try {
const chartData = await generateChartData(timeRange, interval);
if (chartData.labels.length > 0) {
portfolioChart.data.labels = chartData.labels;
portfolioChart.data.datasets[0].data = chartData.values;
portfolioChart.data.datasets[0].label = `Portfolio Value (${currency.toUpperCase()})`;
} else {
portfolioChart.data.labels = ['No data available'];
portfolioChart.data.datasets[0].data = [0];
}
portfolioChart.update();
} catch (error) {
console.error('Error loading chart data:', error);
portfolioChart.data.labels = ['Error loading data'];
portfolioChart.data.datasets[0].data = [0];
portfolioChart.update();
}
}
async function generateChartData(days, interval) {
try {
const walletId = document.getElementById('walletSelector').value;
if (!walletId) return { labels: [], values: [] };
const wallets = getWallets();
const wallet = wallets.find(w => w.id === walletId);
if (!wallet || !wallet.tokens) return { labels: [], values: [] };
// Get the main token ID for the wallet's blockchain
const mainTokenId = {
'ethereum': 'ethereum',
'binance-smart-chain': 'binancecoin',
'polygon': 'matic-network',
'avalanche': 'avalanche-2',
'solana': 'solana'
}[wallet.blockchain];
if (!mainTokenId) return { labels: [], values: [] };
const cmcIdMap = {
'ethereum': 1027,
'binancecoin': 1839,
'matic-network': 3890,
'avalanche-2': 5805,
'solana': 5426
};
const cmcId = cmcIdMap[mainTokenId];
if (!cmcId) return { labels: [], values: [] };
const currency = document.getElementById('currencySelector').value;
const timeRange = days <= 90 ? 'daily' : days <= 365 ? 'weekly' : 'monthly';
const response = await axios.get(`${CMC_API_URL}/market-chart`, {
params: {
id: cmcId,
convert: currency.toUpperCase(),
time_period: timeRange,
time_start: new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString(),
time_end: new Date().toISOString()
},
headers: {
'X-CMC_PRO_API_KEY': CMC_API_KEY
}
});
const labels = response.data.data.timestamps.map(t =>
new Date(t).toLocaleDateString());
const values = response.data.data.prices.map(p => p[currency.toLowerCase()]);
return { labels, values };
} catch (error) {
console.error('Error fetching chart data:', error);
return { labels: [], values: [] };
}
}
// Export function
function exportToCSV() {
const walletId = document.getElementById('walletSelector').value;
if (!walletId) {
alert('Please select a wallet first');
return;
}
const wallets = getWallets();
const wallet = wallets.find(w => w.id === walletId);
if (!wallet || !wallet.tokens || wallet.tokens.length === 0) {
alert('No data to export');
return;
}
const currency = document.getElementById('currencySelector').value;
const currencySymbol = currency === 'usd' ? '$' : '€';
let csvContent = "data:text/csv;charset=utf-8,";
// Header
csvContent += "Token,Symbol,Blockchain,Balance,Value,24h Change\n";
// Data rows
wallet.tokens.forEach(token => {
const value = token.price * token.balance;
const change24h = token.priceChange24h;
csvContent += `"${token.name}","${token.symbol}","${wallet.blockchain}",${token.balance.toFixed(6)},${currencySymbol}${value.toFixed(2)},${change24h.toFixed(2)}%\n`;
});
// Total row
const totalValue = wallet.tokens.reduce((sum, token) => sum + (token.price * token.balance), 0);
csvContent += `"Total","","",,${currencySymbol}${totalValue.toFixed(2)},\n`;
// Create download link
const encodedUri = encodeURI(csvContent);
const link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", `${wallet.name}_portfolio_${new Date().toISOString().split('T')[0]}.csv`);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
</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=Paresiast/ubi-wallet" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>