Orcania

BitNaft: Advanced NFT Analytics & Trading Platform

Developed a comprehensive NFT platform with real-time analytics, portfolio tracking, and advanced charting capabilities. Built with Chart.js visualizations and Web3 wallet integration for seamless NFT trading experience.

Duration: May 2022 - Nov 2022
Role: Frontend Lead & Web3 Integration
ReactNext.jsChart.jsWeb3.jsRedux SagaAxiosSCSSBigNumber.jsBulma
Chart Types
Multiple Analytics
Real-time Data
Live NFT Tracking
Wallet Integration
Seamless Web3
User Experience
Production Ready
Published October 1, 2024
May 2022 - Nov 2022

The NFT Analytics Challenge

As the NFT market exploded in 2022, traders and collectors needed sophisticated tools to analyze market trends, track portfolio performance, and make informed trading decisions. Existing platforms lacked comprehensive analytics, real-time data visualization, and seamless Web3 integration. Orcania identified this gap and tasked me with building BitNaft - an advanced NFT analytics and trading platform.

Platform Architecture

Core Technology Stack

Built on a modern React foundation with specialized libraries for NFT market needs:

// package.json - Core dependencies
{
    "dependencies": {
        "react": "^18.2.0",
        "next": "^12.2.0",
        "chart.js": "^3.9.1",
        "react-chartjs-2": "^4.3.1",
        "bignumber.js": "^9.1.0",
        "axios": "^0.27.2",
        "redux": "^4.2.0",
        "redux-saga": "^1.1.3",
        "bulma": "^0.9.4",
        "react-toastify": "^9.0.7"
    }
}

The platform emphasized real-time data processing, advanced visualizations, and seamless Web3 integration for NFT market analysis.

Advanced Analytics Engine

1. Multi-Chart Dashboard System

Implemented comprehensive charting system using Chart.js for NFT market analysis:

import {
    Chart as ChartJS,
    CategoryScale,
    LinearScale,
    PointElement,
    LineElement,
    Title,
    Tooltip,
    Legend,
} from "chart.js";
import { Line, Bar, Doughnut } from "react-chartjs-2";

ChartJS.register(
    CategoryScale,
    LinearScale,
    PointElement,
    LineElement,
    Title,
    Tooltip,
    Legend
);

const NFTAnalyticsDashboard = () => {
    const [priceHistory, setPriceHistory] = useState([]);
    const [volumeData, setVolumeData] = useState([]);
    const [distributionData, setDistributionData] = useState([]);

    // Price trend analysis
    const priceChartData = {
        labels: priceHistory.map((item) => item.date),
        datasets: [
            {
                label: "Floor Price (ETH)",
                data: priceHistory.map((item) => item.floorPrice),
                borderColor: "rgb(255, 99, 132)",
                backgroundColor: "rgba(255, 99, 132, 0.2)",
                tension: 0.4,
            },
        ],
    };

    // Volume analysis
    const volumeChartData = {
        labels: volumeData.map((item) => item.date),
        datasets: [
            {
                label: "Daily Volume (ETH)",
                data: volumeData.map((item) => item.volume),
                backgroundColor: "rgba(53, 162, 235, 0.5)",
            },
        ],
    };

    // Collection distribution
    const distributionChartData = {
        labels: ["Rare", "Epic", "Legendary", "Common"],
        datasets: [
            {
                data: distributionData,
                backgroundColor: ["#FF6384", "#36A2EB", "#FFCE56", "#4BC0C0"],
            },
        ],
    };

    return (
        <div className="analytics-dashboard">
            <div className="chart-grid">
                <ChartContainer title="Price History">
                    <Line data={priceChartData} options={chartOptions} />
                </ChartContainer>

                <ChartContainer title="Volume Analysis">
                    <Bar data={volumeChartData} options={volumeOptions} />
                </ChartContainer>

                <ChartContainer title="Rarity Distribution">
                    <Doughnut
                        data={distributionChartData}
                        options={doughnutOptions}
                    />
                </ChartContainer>
            </div>
        </div>
    );
};

2. Real-Time Data Processing

Built sophisticated data fetching and processing system:

import BigNumber from "bignumber.js";

class NFTDataProcessor {
    constructor() {
        this.apiClient = axios.create({
            baseURL: process.env.NEXT_PUBLIC_API_BASE_URL,
            timeout: 10000,
        });
    }

    async fetchCollectionData(contractAddress) {
        try {
            const response = await this.apiClient.get(
                `/collections/${contractAddress}`
            );
            return this.processCollectionData(response.data);
        } catch (error) {
            console.error("Failed to fetch collection data:", error);
            throw new Error("Unable to load collection data");
        }
    }

    processCollectionData(rawData) {
        return {
            floorPrice: new BigNumber(rawData.floor_price).dividedBy("1e18"),
            totalVolume: new BigNumber(rawData.total_volume).dividedBy("1e18"),
            owners: rawData.owners,
            totalSupply: rawData.total_supply,
            priceHistory: rawData.price_history.map((item) => ({
                date: new Date(item.timestamp),
                price: new BigNumber(item.price).dividedBy("1e18").toNumber(),
                volume: new BigNumber(item.volume).dividedBy("1e18").toNumber(),
            })),
        };
    }

    calculateMetrics(collectionData) {
        const { priceHistory } = collectionData;

        const priceChange24h = this.calculatePriceChange(priceHistory, 24);
        const priceChange7d = this.calculatePriceChange(priceHistory, 168);
        const volatility = this.calculateVolatility(priceHistory);

        return {
            priceChange24h,
            priceChange7d,
            volatility,
            trend: this.analyzeTrend(priceHistory),
        };
    }

    calculatePriceChange(history, hours) {
        if (history.length < 2) return 0;

        const now = Date.now();
        const cutoff = now - hours * 60 * 60 * 1000;

        const recent = history.filter((item) => item.date.getTime() > cutoff);
        if (recent.length < 2) return 0;

        const oldPrice = recent[0].price;
        const newPrice = recent[recent.length - 1].price;

        return ((newPrice - oldPrice) / oldPrice) * 100;
    }
}

Web3 Integration & Wallet Features

Seamless Wallet Connection

Integrated comprehensive Web3 functionality for NFT interactions:

import { useWeb3React } from "@web3-react/core";
import { ethers } from "ethers";

const WalletIntegration = () => {
    const { account, library, activate, deactivate } = useWeb3React();
    const [userNFTs, setUserNFTs] = useState([]);
    const [portfolioValue, setPortfolioValue] = useState(new BigNumber(0));

    const fetchUserNFTs = async () => {
        if (!account || !library) return;

        try {
            const provider = library;
            const nftContracts = await getUserNFTContracts(account);

            const userTokens = await Promise.all(
                nftContracts.map(async (contract) => {
                    const nftContract = new ethers.Contract(
                        contract.address,
                        ERC721_ABI,
                        provider
                    );

                    const balance = await nftContract.balanceOf(account);
                    const tokens = [];

                    for (let i = 0; i < balance.toNumber(); i++) {
                        const tokenId = await nftContract.tokenOfOwnerByIndex(
                            account,
                            i
                        );
                        const tokenURI = await nftContract.tokenURI(tokenId);
                        const metadata = await fetchTokenMetadata(tokenURI);

                        tokens.push({
                            contractAddress: contract.address,
                            tokenId: tokenId.toString(),
                            metadata,
                            currentValue: await estimateTokenValue(
                                contract.address,
                                tokenId
                            ),
                        });
                    }

                    return tokens;
                })
            );

            const allTokens = userTokens.flat();
            setUserNFTs(allTokens);

            const totalValue = allTokens.reduce(
                (sum, token) => sum.plus(token.currentValue),
                new BigNumber(0)
            );
            setPortfolioValue(totalValue);
        } catch (error) {
            console.error("Failed to fetch user NFTs:", error);
            showNotification("Failed to load your NFT portfolio", "error");
        }
    };

    return (
        <div className="wallet-integration">
            {account ? (
                <div className="connected-state">
                    <div className="wallet-info">
                        <p>
                            Connected:{" "}
                            {`${account.slice(0, 6)}...${account.slice(-4)}`}
                        </p>
                        <p>Portfolio Value: {portfolioValue.toFixed(4)} ETH</p>
                    </div>
                    <NFTPortfolio nfts={userNFTs} />
                </div>
            ) : (
                <ConnectWalletButton onClick={connectWallet} />
            )}
        </div>
    );
};

NFT Portfolio Tracking

Developed comprehensive portfolio management features:

const NFTPortfolio = ({ nfts }) => {
    const [sortBy, setSortBy] = useState("value");
    const [filterBy, setFilterBy] = useState("all");
    const [viewMode, setViewMode] = useState("grid");

    const processPortfolioData = () => {
        const grouped = nfts.reduce((acc, nft) => {
            const collection = nft.metadata.collection;
            if (!acc[collection]) {
                acc[collection] = [];
            }
            acc[collection].push(nft);
            return acc;
        }, {});

        return Object.entries(grouped).map(([collection, tokens]) => ({
            collection,
            count: tokens.length,
            totalValue: tokens.reduce(
                (sum, token) => sum.plus(token.currentValue),
                new BigNumber(0)
            ),
            tokens,
        }));
    };

    const portfolioData = processPortfolioData();

    return (
        <div className="nft-portfolio">
            <div className="portfolio-controls">
                <select
                    value={sortBy}
                    onChange={(e) => setSortBy(e.target.value)}
                >
                    <option value="value">Sort by Value</option>
                    <option value="date">Sort by Date Acquired</option>
                    <option value="rarity">Sort by Rarity</option>
                </select>

                <div className="view-toggle">
                    <button
                        className={viewMode === "grid" ? "active" : ""}
                        onClick={() => setViewMode("grid")}
                    >
                        Grid View
                    </button>
                    <button
                        className={viewMode === "list" ? "active" : ""}
                        onClick={() => setViewMode("list")}
                    >
                        List View
                    </button>
                </div>
            </div>

            <div className={`portfolio-${viewMode}`}>
                {portfolioData.map((collection) => (
                    <CollectionCard
                        key={collection.collection}
                        collection={collection}
                        viewMode={viewMode}
                    />
                ))}
            </div>
        </div>
    );
};

Advanced Features Implementation

1. Price Prediction Algorithm

Developed basic price prediction using historical data analysis:

class NFTPricePredictor {
    analyzeCollection(priceHistory, volumeHistory) {
        const priceMA = this.calculateMovingAverage(priceHistory, 7);
        const volumeMA = this.calculateMovingAverage(volumeHistory, 7);

        const priceVolatility = this.calculateVolatility(priceHistory);
        const trendDirection = this.calculateTrend(priceHistory);
        const volumeTrend = this.calculateTrend(volumeHistory);

        return {
            prediction: this.generatePrediction(
                priceMA,
                volumeMA,
                trendDirection
            ),
            confidence: this.calculateConfidence(priceVolatility, volumeTrend),
            signals: this.generateTradingSignals(
                priceMA,
                volumeMA,
                trendDirection
            ),
        };
    }

    calculateMovingAverage(data, period) {
        const result = [];
        for (let i = period - 1; i < data.length; i++) {
            const sum = data
                .slice(i - period + 1, i + 1)
                .reduce((a, b) => a + b.value, 0);
            result.push(sum / period);
        }
        return result;
    }

    generateTradingSignals(priceMA, volumeMA, trend) {
        const signals = [];

        if (
            trend === "bullish" &&
            volumeMA[volumeMA.length - 1] > volumeMA[volumeMA.length - 7]
        ) {
            signals.push({
                type: "BUY",
                strength: "STRONG",
                reason: "Bullish trend with increasing volume",
            });
        }

        if (
            trend === "bearish" &&
            volumeMA[volumeMA.length - 1] < volumeMA[volumeMA.length - 7]
        ) {
            signals.push({
                type: "SELL",
                strength: "MODERATE",
                reason: "Bearish trend with decreasing volume",
            });
        }

        return signals;
    }
}

2. Advanced Filtering & Search

Implemented sophisticated filtering system for NFT discovery:

const NFTSearchAndFilter = () => {
    const [searchQuery, setSearchQuery] = useState("");
    const [filters, setFilters] = useState({
        priceRange: { min: 0, max: 1000 },
        rarity: [],
        collections: [],
        traits: {},
    });
    const [results, setResults] = useState([]);

    const searchNFTs = async () => {
        const searchParams = {
            query: searchQuery,
            price_min: filters.priceRange.min,
            price_max: filters.priceRange.max,
            rarity: filters.rarity,
            collections: filters.collections,
            traits: filters.traits,
        };

        try {
            const response = await apiClient.post("/search/nfts", searchParams);
            setResults(response.data.nfts);
        } catch (error) {
            console.error("Search failed:", error);
        }
    };

    const handleFilterChange = (filterType, value) => {
        setFilters((prev) => ({
            ...prev,
            [filterType]: value,
        }));
    };

    return (
        <div className="search-filter-container">
            <div className="search-bar">
                <input
                    type="text"
                    placeholder="Search NFTs, collections, or traits..."
                    value={searchQuery}
                    onChange={(e) => setSearchQuery(e.target.value)}
                    onKeyPress={(e) => e.key === "Enter" && searchNFTs()}
                />
                <button onClick={searchNFTs}>Search</button>
            </div>

            <div className="filters">
                <PriceRangeFilter
                    range={filters.priceRange}
                    onChange={(range) =>
                        handleFilterChange("priceRange", range)
                    }
                />

                <RarityFilter
                    selected={filters.rarity}
                    onChange={(rarity) => handleFilterChange("rarity", rarity)}
                />

                <CollectionFilter
                    selected={filters.collections}
                    onChange={(collections) =>
                        handleFilterChange("collections", collections)
                    }
                />
            </div>

            <SearchResults results={results} />
        </div>
    );
};

State Management Architecture

Redux Saga Implementation

Built sophisticated state management for complex NFT data flows:

// sagas/nftSagas.js
import { call, put, takeEvery, select } from "redux-saga/effects";

function* fetchCollectionDataSaga(action) {
    try {
        yield put({ type: "FETCH_COLLECTION_START" });

        const { contractAddress } = action.payload;
        const collectionData = yield call(
            apiClient.get,
            `/collections/${contractAddress}`
        );
        const processedData = yield call(
            processCollectionData,
            collectionData.data
        );

        yield put({
            type: "FETCH_COLLECTION_SUCCESS",
            payload: processedData,
        });

        // Fetch related analytics
        yield call(fetchCollectionAnalyticsSaga, {
            payload: { contractAddress },
        });
    } catch (error) {
        yield put({
            type: "FETCH_COLLECTION_FAILURE",
            payload: error.message,
        });
    }
}

function* updatePortfolioSaga(action) {
    try {
        const { account } = action.payload;
        const currentPortfolio = yield select((state) => state.portfolio);

        // Fetch updated values for all NFTs
        const updatedNFTs = yield call(updateNFTValues, currentPortfolio.nfts);

        const totalValue = updatedNFTs.reduce(
            (sum, nft) => sum.plus(nft.currentValue),
            new BigNumber(0)
        );

        yield put({
            type: "UPDATE_PORTFOLIO_SUCCESS",
            payload: {
                nfts: updatedNFTs,
                totalValue: totalValue.toString(),
            },
        });
    } catch (error) {
        yield put({
            type: "UPDATE_PORTFOLIO_FAILURE",
            payload: error.message,
        });
    }
}

export default function* nftSagas() {
    yield takeEvery("FETCH_COLLECTION_REQUEST", fetchCollectionDataSaga);
    yield takeEvery("UPDATE_PORTFOLIO_REQUEST", updatePortfolioSaga);
}

Production Optimizations

Performance Enhancements

Implemented several optimization strategies for handling large datasets:

// Virtual scrolling for large NFT lists
import { FixedSizeList as List } from "react-window";

const VirtualizedNFTGrid = ({ nfts }) => {
    const itemsPerRow = 4;
    const itemHeight = 300;

    const Row = ({ index, style }) => {
        const startIndex = index * itemsPerRow;
        const endIndex = Math.min(startIndex + itemsPerRow, nfts.length);
        const rowItems = nfts.slice(startIndex, endIndex);

        return (
            <div style={style} className="nft-row">
                {rowItems.map((nft) => (
                    <NFTCard
                        key={`${nft.contractAddress}-${nft.tokenId}`}
                        nft={nft}
                    />
                ))}
            </div>
        );
    };

    return (
        <List
            height={600}
            itemCount={Math.ceil(nfts.length / itemsPerRow)}
            itemSize={itemHeight}
            width="100%"
        >
            {Row}
        </List>
    );
};

// Image lazy loading and optimization
const OptimizedNFTImage = ({ src, alt, tokenId }) => {
    const [isLoaded, setIsLoaded] = useState(false);
    const [imageSrc, setImageSrc] = useState(null);

    useEffect(() => {
        const img = new Image();
        img.onload = () => {
            setImageSrc(src);
            setIsLoaded(true);
        };
        img.onerror = () => {
            setImageSrc("/placeholder-nft.png");
            setIsLoaded(true);
        };
        img.src = src;
    }, [src]);

    return (
        <div className="nft-image-container">
            {!isLoaded && <div className="loading-skeleton" />}
            {isLoaded && (
                <img
                    src={imageSrc}
                    alt={alt}
                    loading="lazy"
                    className="nft-image"
                />
            )}
        </div>
    );
};

Technical Achievements

1. Real-Time Analytics Engine

  • Chart.js Integration: Advanced charting with multiple visualization types
  • BigNumber.js: Precise financial calculations for ETH/USD conversions
  • Performance: Optimized rendering for datasets with 10,000+ data points

2. Web3 Integration Excellence

  • Multi-Wallet Support: MetaMask, WalletConnect, and injected providers
  • Smart Contract Interactions: ERC-721 and ERC-1155 standard support
  • Transaction Management: Comprehensive error handling and retry logic

3. User Experience Innovation

  • Responsive Design: Mobile-first approach with Bulma CSS framework
  • Real-Time Updates: WebSocket integration for live market data
  • Progressive Loading: Skeleton screens and lazy loading for better UX

Platform Impact

Market Analysis Capabilities

  • Price Tracking: Real-time floor price monitoring for 500+ collections
  • Volume Analysis: Historical trading volume with trend analysis
  • Rarity Scoring: Algorithmic rarity calculation for trait-based valuation

Portfolio Management

  • Multi-Collection Support: Track NFTs across multiple smart contracts
  • Value Calculation: Real-time portfolio valuation in ETH and USD
  • Performance Metrics: Gain/loss tracking with historical comparison

Trading Intelligence

  • Market Signals: Algorithmic buy/sell recommendations
  • Price Predictions: Basic ML models for short-term price forecasting
  • Risk Assessment: Volatility analysis and risk scoring

Technical Stack Summary

// Complete technology stack
const techStack = {
    frontend: {
        framework: "Next.js 12.2.0",
        ui: "React 18.2.0",
        styling: "Bulma + Custom SCSS",
        charts: "Chart.js + react-chartjs-2",
        state: "Redux + Redux Saga",
        web3: "Web3.js + ethers.js",
    },
    development: {
        language: "JavaScript ES2021",
        testing: "Jest + React Testing Library",
        linting: "ESLint (Airbnb) + Prettier",
        bundling: "Next.js Webpack",
        deployment: "Vercel + GitHub Pages",
    },
    apis: {
        http: "Axios with interceptors",
        blockchain: "Infura + Alchemy RPC",
        notifications: "react-toastify",
        math: "BigNumber.js for precision",
    },
};

Lessons Learned

1. NFT Market Complexity

  • Data Inconsistency: NFT metadata varies significantly across collections
  • Price Volatility: Extreme price swings require sophisticated error handling
  • API Limitations: Rate limits and data availability challenges

2. Web3 UX Challenges

  • Wallet Friction: Complex connection flows impact user adoption
  • Transaction Costs: Gas fees create barriers for frequent interactions
  • Network Congestion: Ethereum scalability affects user experience

3. Performance at Scale

  • Data Volume: Large NFT collections require virtualization strategies
  • Real-Time Updates: Balancing freshness with performance considerations
  • Mobile Performance: Chart rendering optimization for mobile devices

Project Legacy

BitNaft demonstrated the potential for advanced NFT analytics platforms and established patterns for professional Web3 application development. The project showcased expertise in:

  • Complex Data Visualization with Chart.js
  • Production Web3 Integration with comprehensive wallet support
  • Performance Optimization for large-scale NFT data
  • Modern React Architecture with sophisticated state management

The platform provided valuable insights into NFT market dynamics and established a foundation for future blockchain analytics tools.


This case study demonstrates advanced Web3 frontend development, showcasing expertise in NFT platforms, data visualization, and production-ready blockchain applications.

Technologies Used

ReactNext.jsChart.jsWeb3.jsRedux SagaAxiosSCSSBigNumber.jsBulma

Want Similar Results?

Let's discuss how I can help your business achieve growth through strategic development.