Created
September 4, 2024 02:14
-
-
Save jeftarmascarenhas/ae8fe59da95da08fe6f84347d5a48fc2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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