// Configuration const RARIBLE_API_KEY = 'd01c6b48-8f7a-4f58-8a1e-5c5a5d9c5b9a'; // New test API key const RARIBLE_API_BASE = 'https://api.rarible.org/v0.1'; const CONTRACT_ADDRESS = 'ETHEREUM:0x60f80121c31a0d46b5279700f9df786054aa5ee5'; // New test contract // State let state = { walletAddress: null, nfts: [], filteredNFTs: [], isLoading: false, currentFilter: 'all' }; // DOM Elements const elements = { connectWalletBtn: document.getElementById('connectWallet'), disconnectWalletBtn: document.getElementById('disconnectWallet'), walletAddressEl: document.getElementById('walletAddress'), walletInfoEl: document.getElementById('walletInfo'), nftContainer: document.getElementById('nftContainer'), searchInput: document.getElementById('searchInput'), filterButtons: document.querySelectorAll('.filter-btn'), refreshBtn: document.getElementById('refreshNFTs'), statusMessage: document.getElementById('statusMessage') }; // Initialize document.addEventListener('DOMContentLoaded', async () => { await checkWalletConnection(); setupEventListeners(); }); // Check if wallet is already connected async function checkWalletConnection() { if (typeof window.ethereum === 'undefined') { window.demoMode = true; showStatus('No wallet detected - using demo mode', 'info'); return; } try { const accounts = await window.ethereum.request({ method: 'eth_accounts' }); if (accounts.length > 0) { state.walletAddress = accounts[0]; updateWalletDisplay(); showStatus('Wallet connected! Click Refresh to load NFTs.', 'success'); } else { window.demoMode = true; showStatus('Connect wallet or use demo mode', 'info'); } } catch (error) { console.error('Error checking wallet:', error); window.demoMode = true; showStatus('Using demo mode', 'info'); } } // Connect Wallet elements.connectWalletBtn.addEventListener('click', async () => { if (typeof window.ethereum === 'undefined') { window.demoMode = true; showStatus('No wallet detected - using demo mode', 'info'); fetchNFTs(); return; } try { elements.connectWalletBtn.disabled = true; elements.connectWalletBtn.textContent = 'Connecting...'; showStatus('Connecting to wallet...', 'info'); const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); if (accounts.length > 0) { state.walletAddress = accounts[0]; window.demoMode = false; updateWalletDisplay(); showStatus('Wallet connected! Click Refresh to load NFTs.', 'success'); window.ethereum.on('accountsChanged', handleAccountsChanged); } else { window.demoMode = true; showStatus('Using demo mode', 'info'); } } catch (error) { console.error('Error connecting wallet:', error); window.demoMode = true; showStatus('Using demo mode', 'info'); fetchNFTs(); } finally { elements.connectWalletBtn.disabled = false; elements.connectWalletBtn.textContent = 'Connect Wallet'; } }); // Disconnect Wallet elements.disconnectWalletBtn.addEventListener('click', () => { disconnectWallet(); }); // Handle account changes function handleAccountsChanged(accounts) { if (accounts.length === 0) { disconnectWallet(); showStatus('Wallet disconnected', 'info'); } else { state.walletAddress = accounts[0]; updateWalletDisplay(); showStatus('Wallet account changed', 'info'); // Optionally reload NFTs fetchNFTs(); } } // Disconnect wallet function function disconnectWallet() { state.walletAddress = null; state.nfts = []; state.filteredNFTs = []; window.demoMode = true; if (window.ethereum?.removeListener) { window.ethereum.removeListener('accountsChanged', handleAccountsChanged); } elements.walletInfoEl.style.display = 'none'; elements.connectWalletBtn.style.display = 'block'; elements.connectWalletBtn.disabled = false; fetchNFTs(); // Will fall back to demo mode showStatus('Wallet disconnected - using demo mode', 'info'); } // Update wallet display function updateWalletDisplay() { if (state.walletAddress) { const shortAddress = `${state.walletAddress.substring(0, 6)}...${state.walletAddress.substring(38)}`; elements.walletAddressEl.textContent = shortAddress; elements.walletInfoEl.style.display = 'flex'; elements.connectWalletBtn.style.display = 'none'; } } // Show status message function showStatus(message, type = 'info') { elements.statusMessage.textContent = message; elements.statusMessage.className = `status-message ${type}`; elements.statusMessage.style.display = 'block'; // Auto-hide after 5 seconds setTimeout(() => { elements.statusMessage.style.display = 'none'; }, 5000); } // Fetch NFTs from Rarible API async function fetchNFTs() { if (state.isLoading) { showStatus('Already loading NFTs...', 'info'); return; } if (!state.walletAddress && !window.demoMode) { showStatus('Please connect your wallet first', 'error'); return; } try { state.isLoading = true; elements.refreshBtn.disabled = true; elements.refreshBtn.textContent = 'Loading...'; showStatus('Loading NFTs...', 'info'); renderLoadingState(); if (window.demoMode) { // Use demo data const demoNFTs = [ { id: 'demo1', meta: { name: 'Cosmic Explorer #1', description: 'A demo NFT for testing purposes', image: 'http://static.photos/abstract/640x360/1' }, sellOrders: [{ id: 'order1', take: { value: '1000000000000000000', // 1 ETH type: { '@type': 'ETH', decimals: 18 } } }] }, { id: 'demo2', meta: { name: 'Stellar Artwork', description: 'Another demo NFT for testing', image: 'http://static.photos/abstract/640x360/2' }, sellOrders: [] }, { id: 'demo3', meta: { name: 'Quantum Pixel', description: 'Digital art piece', image: 'http://static.photos/abstract/640x360/3' }, sellOrders: [{ id: 'order3', take: { value: '250000000000000000', // 0.25 ETH type: { '@type': 'ETH', decimals: 18 } } }] } ]; state.nfts = demoNFTs; state.filteredNFTs = [...demoNFTs]; applyFilter(state.currentFilter); showStatus('Demo mode: Loaded 3 test NFTs', 'success'); return; } // Real API fetch const encodedWallet = encodeURIComponent(`ETHEREUM:${state.walletAddress}`); const apiUrl = `${RARIBLE_API_BASE}/items/byOwner?owner=${encodedWallet}&size=50`; const response = await fetch(apiUrl, { headers: { 'X-API-KEY': RARIBLE_API_KEY, 'Accept': 'application/json' } }); if (!response.ok) { throw new Error(`API error: ${response.status}`); } const data = await response.json(); state.nfts = data.items || []; state.filteredNFTs = [...state.nfts]; if (state.nfts.length === 0) { renderEmptyState('No NFTs found in your wallet'); showStatus('No NFTs found in your wallet', 'info'); return; } applyFilter(state.currentFilter); showStatus(`Loaded ${state.nfts.length} NFTs`, 'success'); } catch (error) { console.error('Error:', error); window.demoMode = true; fetchNFTs(); // Fallback to demo mode } finally { state.isLoading = false; elements.refreshBtn.disabled = false; elements.refreshBtn.textContent = '🔄 Refresh NFTs'; } } // Enrich NFT with order data async function enrichNFTWithOrderData(nft) { try { const itemId = encodeURIComponent(nft.id); // Get sell orders for this NFT const ordersResponse = await fetch( `${RARIBLE_API_BASE}/orders/items/${itemId}/sell`, { headers: { 'X-API-KEY': RARIBLE_API_KEY, 'Accept': 'application/json' } } ); if (ordersResponse.ok) { const ordersData = await ordersResponse.json(); nft.sellOrders = (ordersData.orders || []).filter(order => order.status === 'ACTIVE' && order.makeStock > 0 // Ensure there's available stock ); // Get best price (lowest) if (nft.sellOrders.length > 0) { nft.bestOrder = nft.sellOrders.reduce((best, current) => { const bestPrice = best ? parseFloat(best.take.value) : Infinity; const currentPrice = current ? parseFloat(current.take.value) : Infinity; return currentPrice < bestPrice ? current : best; }); } } else { nft.sellOrders = []; nft.bestOrder = null; } } catch (error) { console.error('Error enriching NFT:', nft.id, error); nft.sellOrders = []; nft.bestOrder = null; } } // Render NFTs without flickering function renderNFTs() { if (!elements.nftContainer) return; if (state.filteredNFTs.length === 0) { renderEmptyState('No NFTs match your filter'); return; } // Clear and render in one operation to prevent flickering const fragment = document.createDocumentFragment(); state.filteredNFTs.forEach((nft, index) => { const card = createNFTCard(nft, index); fragment.appendChild(card); }); elements.nftContainer.innerHTML = ''; elements.nftContainer.appendChild(fragment); } // Create NFT Card Element function createNFTCard(nft, index) { const card = document.createElement('div'); card.className = 'nft-card'; card.style.animationDelay = `${index * 0.05}s`; // Check if NFT is for sale const isForSale = nft.sellOrders && nft.sellOrders.length > 0 && nft.bestOrder; let price = 'Not Listed'; let priceValue = 0; if (isForSale && nft.bestOrder.take) { const decimals = nft.bestOrder.take.type?.decimals || 18; priceValue = parseFloat(nft.bestOrder.take.value) / Math.pow(10, decimals); const currency = nft.bestOrder.take.type?.['@type'] === 'ETH' ? 'ETH' : nft.bestOrder.take.assetType?.assetClass || 'ETH'; price = `${priceValue.toFixed(4)} ${currency}`; } // Get image URL let imageUrl = getImageUrl(nft); const nftName = nft.meta?.name || `NFT #${nft.tokenId || 'Unknown'}`; const description = nft.meta?.description || 'No description available'; card.innerHTML = `
${escapeHtml(description)}
Loading your NFTs from the blockchain...
${message}
${errorMessage}
Please check your connection and try again.