Skip to content

Instantly share code, notes, and snippets.

@jeftarmascarenhas
Created September 4, 2024 02:14
Show Gist options
  • Save jeftarmascarenhas/ae8fe59da95da08fe6f84347d5a48fc2 to your computer and use it in GitHub Desktop.
Save jeftarmascarenhas/ae8fe59da95da08fe6f84347d5a48fc2 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NFT Timeline</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
background-color: #333;
color: #fff;
padding: 20px;
}
.content {
display: flex;
align-items: flex-start;
gap: 20px;
}
.title {
font-size: 30px;
text-align: center;
margin-bottom: 10px;
}
.collection-name {
text-align: center;
margin-bottom: 20px;
color: #007bff;
}
.token-image {
display: block;
width: 100%;
max-width: 600px;
}
.timeline {
position: relative;
flex: 1;
list-style: none;
display: flex;
flex-direction: column;
}
.timeline-item {
position: relative;
display: flex;
align-items: center;
gap: 6px;
}
.timeline-item__content {
padding: 20px 40px;
background-color: #444;
position: relative;
color: white;
text-decoration: none;
cursor: pointer;
flex: 1;
transition: all 0.2s ease-in;
}
.timeline-item__content:hover {
background-color: #4d4d4d;
}
.timeline-item__content::before {
content: "";
position: absolute;
width: 6px;
background-color: #007bff;
top: 0;
bottom: 0;
left: 0;
margin-left: -3px;
}
.timeline-item__content::after {
content: "";
position: absolute;
width: 24px;
height: 24px;
left: -12px;
background-color: #444;
border: 4px solid #007bff;
top: 24px;
border-radius: 50%;
z-index: 1;
}
.timeline-item h2 {
margin: 0 0 5px 0;
font-size: 16px;
}
.timeline-item p {
margin: 0;
}
.date {
width: 100px;
font-size: 14px;
color: #ccc;
}
</style>
</head>
<body>
<h1 class="title">NFT Timeline</h1>
<h2 id="collection-name" class="collection-name"></h2>
<div class="content">
<img src="" alt="" id="token-image" class="token-image" />
<ul id="timeline" class="timeline"></ul>
</div>
<script type="module">
// Importa a biblioteca Ethers.js do CDN
import { ethers } from "https://cdnjs.cloudflare.com/ajax/libs/ethers/6.7.0/ethers.min.js";
// Define a chave da API do Alchemy e a URL para o provedor de RPC
const alchemyApiKey = "QVP6zs1zABCVBo9XerQ4MOyVGei_b52m";
const alchemyUrl = `https://eth-mainnet.alchemyapi.io/v2/${alchemyApiKey}`;
const provider = new ethers.JsonRpcProvider(alchemyUrl);
// Função para buscar os metadados do token usando IPFS
async function fetchTokenMetadata(tokenId) {
const url = `https://ipfs.io/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/${tokenId}`;
const response = await fetch(url).then((res) => res.json());
return response; // Retorna os dados do token como um objeto JSON
}
// Função para atualizar a imagem do token na interface
function updateTokenImage(imageUrl) {
const imgTarget = document.querySelector("#token-image");
const imgSrc = imageUrl.startsWith("ipfs://")
? imageUrl.replace("ipfs://", "https://ipfs.io/ipfs/") // Substitui o prefixo IPFS por uma URL acessível
: imageUrl;
imgTarget.src = imgSrc; // Define a URL da imagem como fonte da tag <img>
}
// Função para obter detalhes do contrato e eventos de transferência
async function getContractDetails(contractAddress, tokenId) {
const abi = [
// Define a ABI com o evento Transfer e a função name do contrato
"event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)",
"function name() external view returns(string memory)",
];
const contract = new ethers.Contract(contractAddress, abi, provider);
const name = await contract.name(); // Obtém o nome da coleção de NFTs
// Cria um filtro para capturar eventos de transferência específicos do tokenId
const filter = contract.filters.Transfer(null, null, tokenId);
const transferEvents = await contract.queryFilter(filter); // Busca os eventos de transferência
return { name, transferEvents }; // Retorna o nome da coleção e os eventos de transferência
}
// Função para enriquecer os eventos com informações de data
async function getEventsWithTimestamps(events) {
return Promise.all(
events.map(async (event) => {
const block = await provider.getBlock(event.blockNumber); // Obtém o bloco associado ao evento
const date = new Date(block.timestamp * 1000)
.toISOString()
.split("T")[0]; // Converte o timestamp em uma data legível
// Retorna um objeto com informações detalhadas do evento
return {
from: event.args.from,
to: event.args.to,
date,
blockNumber: event.blockNumber,
transactionHash: event.transactionHash,
};
})
);
}
// Função para exibir a timeline dos eventos na interface
function displayTimeline(events, collectionName) {
const timeline = document.getElementById("timeline");
document.getElementById("collection-name").textContent = collectionName; // Exibe o nome da coleção
events.forEach((event) => {
const listItem = createTimelineItem(event); // Cria um item da timeline para cada evento
timeline.appendChild(listItem); // Adiciona o item à timeline
});
}
// Função para criar um item da timeline
function createTimelineItem(event) {
const listItem = document.createElement("li");
listItem.className = "timeline-item";
const dateElement = document.createElement("div");
dateElement.className = "date";
dateElement.textContent = event.date; // Exibe a data do evento
const contentElement = document.createElement("a");
contentElement.href = `https://etherscan.io/tx/${event.transactionHash}`;
contentElement.target = "_blank"; // Link para o Etherscan com detalhes da transação
contentElement.className = "timeline-item__content";
contentElement.innerHTML = `<h2>From: ${event.from} <br /> To: ${event.to}</h2><p>Block Number: ${event.blockNumber}</p>`;
listItem.appendChild(dateElement);
listItem.appendChild(contentElement);
return listItem; // Retorna o item completo da timeline
}
// Função principal para carregar e exibir a timeline
async function loadTimeline() {
const contractAddress = "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D"; // Endereço do contrato NFT
const tokenId = 10; // ID do token que queremos rastrear
const tokenMetadata = await fetchTokenMetadata(tokenId); // Busca metadados do token
updateTokenImage(tokenMetadata.image); // Atualiza a imagem do token na interface
const { name, transferEvents } = await getContractDetails(
contractAddress,
tokenId
);
const eventsWithTimestamps = await getEventsWithTimestamps(
transferEvents
); // Adiciona timestamps aos eventos
displayTimeline(eventsWithTimestamps, name); // Exibe a timeline na interface
}
// Inicia o processo de carregar a timeline
loadTimeline().catch(console.error);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment