HTML TRACKET WALLET

 



<html lang="es">

<head>

<meta charset="UTF-8"></meta>

<title>Wallet Tracker Hedera – CRYPTOTRANSFER con grupos por fecha</title>

<style>

body { font-family: Arial; background:#f4f4f4; padding:20px; }

table { width:100%; border-collapse:collapse; background:#fff; }

th,td { border:1px solid #ccc; padding:6px; font-size:13px; vertical-align:top; }

th { background:#222; color:#fff; }

.in { color:green; font-weight:bold; }

.out { color:red; font-weight:bold; }

.controls { margin-bottom:10px; }

button, input { padding:6px; margin:0 5px 10px 0; }

.small { font-size:11px; color:#555; }

.total-row { background:#eee; font-weight:bold; }

.group-header { font-weight:bold; background:#ddd; }

.simultaneous { background:#e8f0ff; } 

</style>

</head>

<body>


<h2>CRYPTOTRANSFER – Wallet Tracker Hedera</h2>


<div class="controls">

  <label>Wallet: <input id="walletInput" style="width: 150px;" value="0.0.4345311" /></label>

  <label>Token ID: <input id="tokenInput" placeholder="0.0.12345" style="width: 150px;" /></label>

  <label>Desde: <input id="dateFrom" type="datetime-local" /></label>

  <label>Hasta: <input id="dateTo" type="datetime-local" /></label>

  <button onclick="loadFilteredTransactions()">Cargar</button>

</div>


<div class="controls">

  <button onclick="prevPage()">⬅ PREV</button>

  <button onclick="nextPage()">NEXT ➡</button>

</div>


<table>

<thead>

<tr>

  <th>Fecha</th>

  <th>Tx</th>

  <th>Token</th>

  <th>Nombre</th>

  <th>Precio USD</th>

  <th>Cantidad</th>

  <th>USD total tx</th>

</tr>

</thead>

<tbody id="txBody"></tbody>

<tfoot>

<tr class="total-row">

  <td colspan="6">Total Balance USD de esta página</td>

  <td id="totalUSD">0</td>

</tr>

</tfoot>

</table>


<script>

let ACCOUNT_ID = document.getElementById("walletInput").value;

let FILTER_TOKEN = document.getElementById("tokenInput").value || null;

let DATE_FROM = null;

let DATE_TO = null;


const MIRROR = "https://mainnet-public.mirrornode.hedera.com";

const SAUCER_API_KEY = "875e1017-87b8-4b12-8301-6aa1f1aa073b";

const LIMIT = 100;

let hbarPrice = 0;

let allTxs = [];

let currentPage = 0;

const tokenInfoCache = {};

const tokenPriceCache = {};

const colors = ["#f0f8ff","#f5f5dc"];


// Formatear números

function formatNumber(value, decimals=4){

  return value.toLocaleString('es-ES', {minimumFractionDigits: decimals, maximumFractionDigits: decimals});

}


// Obtener precio HBAR

async function loadHbarPrice(){

  try {

    const res = await fetch("https://api.coingecko.com/api/v3/simple/price?ids=hedera-hashgraph&vs_currencies=usd");

    const data = await res.json();

    hbarPrice = data["hedera-hashgraph"].usd || 0;

  } catch { hbarPrice = 0; }

}


// Información de token

async function getTokenInfo(tokenId){

  if(tokenInfoCache[tokenId]) return tokenInfoCache[tokenId];

  try{

    const res = await fetch(`${MIRROR}/api/v1/tokens/${tokenId}`);

    const data = await res.json();

    const info = { name: data.name||tokenId, decimals: data.decimals||0 };

    tokenInfoCache[tokenId] = info;

    return info;

  } catch {

    const fallback = { name: tokenId, decimals: 0 };

    tokenInfoCache[tokenId] = fallback;

    return fallback;

  }

}


// Precio USD de token

async function getTokenUSD(tokenId){

  if(tokenPriceCache[tokenId] !== undefined) return tokenPriceCache[tokenId];

  try {

    const res = await fetch(`https://api.saucerswap.finance/tokens/${tokenId}`, {

      headers: { "x-api-key": SAUCER_API_KEY }

    });

    const data = await res.json();

    const price = Number(data.priceUsd || 0);

    tokenPriceCache[tokenId] = price;

    return price;

  } catch { tokenPriceCache[tokenId]=0; return 0; }

}


// Agrupar por fecha completa: año-mes-dia-hora-minuto-segundo

function getFullDateKey(ts){

  const d = new Date(ts * 1000);

  const pad=(n)=>n.toString().padStart(2,'0');

  return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}-${pad(d.getHours())}-${pad(d.getMinutes())}-${pad(d.getSeconds())}`;

}


// Cargar transacciones filtradas y grupos completos

async function loadFilteredTransactions(){

  ACCOUNT_ID = document.getElementById("walletInput").value;

  FILTER_TOKEN = document.getElementById("tokenInput").value || null;

  const dateFromVal = document.getElementById("dateFrom").value;

  const dateToVal = document.getElementById("dateTo").value;

  DATE_FROM = dateFromVal ? new Date(dateFromVal).getTime()/1000 : null;

  DATE_TO = dateToVal ? new Date(dateToVal).getTime()/1000 : null;


  currentPage = 0;

  let url = `${MIRROR}/api/v1/transactions?account.id=${ACCOUNT_ID}&limit=${LIMIT}&order=desc`;

  let txs = [];


  while(url && txs.length < 2000){

    const res = await fetch(url);

    const data = await res.json();

    txs = txs.concat(data.transactions);

    url = data.links?.next ? MIRROR + data.links.next : null;

  }


  // 1️⃣ Detectar todos los timestamps que tengan el token buscado

  let matchingTimestamps = new Set();

  txs.forEach(tx=>{

    if(FILTER_TOKEN){

      const tokenMatch = tx.token_transfers?.some(x=>x.account===ACCOUNT_ID && x.token_id===FILTER_TOKEN);

      const hbarMatch = FILTER_TOKEN==="HBAR" && tx.transfers?.some(x=>x.account===ACCOUNT_ID);

      if(tokenMatch || hbarMatch){

        matchingTimestamps.add(getFullDateKey(tx.consensus_timestamp));

      }

    } else {

      matchingTimestamps.add(getFullDateKey(tx.consensus_timestamp));

    }

  });


  // 2️⃣ Filtrar todos los tx que tengan el timestamp completo coincidente

  allTxs = txs.filter(tx=>matchingTimestamps.has(getFullDateKey(tx.consensus_timestamp)));


  // 3️⃣ Aplicar filtro de fechas si existe

  if(DATE_FROM || DATE_TO){

    allTxs = allTxs.filter(tx=>{

      const ts=parseFloat(tx.consensus_timestamp);

      if(DATE_FROM && ts < DATE_FROM) return false;

      if(DATE_TO && ts > DATE_TO) return false;

      return true;

    });

  }


  renderPage();

}


// Renderizado de grupos completos

async function renderPage(){

  const body = document.getElementById("txBody");

  body.innerHTML = "";

  let totalPageUSD = 0;


  const groups = {};

  allTxs.forEach(tx=>{

    const key = getFullDateKey(tx.consensus_timestamp);

    if(!groups[key]) groups[key]=[];

    groups[key].push(tx);

  });


  const groupKeys = Object.keys(groups).sort((a,b)=>b.localeCompare(a));

  const pageGroups = groupKeys.slice(currentPage*20, (currentPage+1)*20);

  let colorIndex = 0;


  for(const key of pageGroups){

    const group = groups[key];

    const bg = colors[colorIndex%2];

    colorIndex++;


    const d = new Date(parseFloat(group[0].consensus_timestamp)*1000);

    body.innerHTML += `<tr class="group-header" style="background:${bg}">

      <td colspan="7">Grupo de transacciones: ${d.toLocaleString()}</td>

    </tr>`;


    for(const tx of group){

      const dateStr = new Date(tx.consensus_timestamp*1000).toLocaleString();

      let txTotalUSD = 0;

      let isSimultaneousExtra = false;


      if(tx.transfers){

        const t = tx.transfers.find(x=>x.account===ACCOUNT_ID);

        if(t) txTotalUSD += t.amount/1e8 * hbarPrice;

      }


      if(tx.token_transfers){

        const promises = tx.token_transfers.filter(x=>x.account===ACCOUNT_ID).map(async t=>{

          const info = await getTokenInfo(t.token_id);

          const realAmount = t.amount/Math.pow(10, info.decimals);

          const tokenPrice = await getTokenUSD(t.token_id);

          txTotalUSD += realAmount * tokenPrice;

        });

        await Promise.all(promises);

      }


      totalPageUSD += txTotalUSD;


      // Detectar si fila agregada por coincidencia de fecha

      if(FILTER_TOKEN){

        const tokenMatch = tx.token_transfers?.some(x=>x.account===ACCOUNT_ID && x.token_id===FILTER_TOKEN);

        const hbarMatch = FILTER_TOKEN==="HBAR" && tx.transfers?.some(x=>x.account===ACCOUNT_ID);

        if(!tokenMatch && !hbarMatch) isSimultaneousExtra = true;

      }


      const rowClass = isSimultaneousExtra ? "simultaneous" : "";

      const clsHBAR = tx.transfers?.find(x=>x.account===ACCOUNT_ID)?.amount > 0 ? "in" : "out";


      if(tx.transfers){

        const t = tx.transfers.find(x=>x.account===ACCOUNT_ID);

        if(t){

          const amount = t.amount/1e8;

          body.innerHTML += `<tr style="background:${bg}" class="${rowClass}">

            <td>${dateStr}</td>

            <td><a href="https://hashscan.io/mainnet/transaction/${tx.transaction_id}" target="_blank">${tx.transaction_id}</a></td>

            <td>HBAR</td>

            <td>Hedera</td>

            <td>$${formatNumber(hbarPrice,6)}</td>

            <td class="${clsHBAR}">${formatNumber(amount,6)}</td>

            <td>$${formatNumber(txTotalUSD,2)}</td>

          </tr>`;

        }

      }


      if(tx.token_transfers){

        const promises = tx.token_transfers.filter(x=>x.account===ACCOUNT_ID).map(async t=>{

          const info = await getTokenInfo(t.token_id);

          if(FILTER_TOKEN && t.token_id!==FILTER_TOKEN && !isSimultaneousExtra) return;

          const realAmount = t.amount/Math.pow(10, info.decimals);

          const cls = realAmount>0?"in":"out";

          const tokenPrice = await getTokenUSD(t.token_id);

          body.innerHTML += `<tr style="background:${bg}" class="${rowClass}">

            <td>${dateStr}</td>

            <td><a href="https://hashscan.io/mainnet/transaction/${tx.transaction_id}" target="_blank">${tx.transaction_id}</a></td>

            <td>${t.token_id}</td>

            <td class="small">${info.name}</td>

            <td>$${tokenPrice>0?formatNumber(tokenPrice,6):'-'}</td>

            <td class="${cls}">${formatNumber(realAmount,info.decimals)}</td>

            <td>$${formatNumber(txTotalUSD,2)}</td>

          </tr>`;

        });

        await Promise.all(promises);

      }

    }

  }


  document.getElementById("totalUSD").innerText = "$"+formatNumber(totalPageUSD,2);

}


function nextPage(){

  const totalGroups = Object.keys(allTxs.reduce((acc,tx)=>{ acc[getFullDateKey(tx.consensus_timestamp)]=1; return acc; },{})).length;

  if((currentPage+1)*20 < totalGroups){ currentPage++; renderPage(); }

}

function prevPage(){ if(currentPage>0){ currentPage--; renderPage(); }}


(async()=>{

  await loadHbarPrice();

  await loadFilteredTransactions();

})();

</script>


</body>

</html>

<div><br /></div><div><br /></div><div><br /></div>

Comentarios

Entradas populares de este blog

POOLS DE LIQUIDEZ Y TOKENS DE ISLAS CANARIAS HEDERA HASHGRAPH .

Bitcoin.ℏ Parody Meme

Apple.ℏ Parody Meme