Git: d00d391 (2026-01-29) RPC: checking... Block: ... Load: ...
Blockchain Stores Open Data Open Code Open Prompts Simulator AI Speaking
FloodBoy

FloodBoy Open Code

Interactive code examples for integrating with FloodBoy's blockchain infrastructure.

🔗 Open Source Project

GitHub Repository: https://github.com/LarisLabs/floodboy-astro

Open source • Powered by Cat Lab • Sponsored by LarisLabs

React + Viem Integration

⚛️ Production-Ready React Demo

Water-focused flood monitoring dashboard with 7-day historical data visualization. Complete rebuild for 99.8% accuracy!

🌊 Water-Focused Design Production Ready

🔹 Simple Data Viewer

Perfect for learning blockchain integration and understanding sensor data structure. Clean, minimal interface with real-time updates.

Real-time sensor data from blockchain
Simple card layout for easy reading
Minimal dependencies - just Viem + React
Perfect for beginners and learning
🔗 View Simple Demo

📊 Chart Version

Enhanced with historical data visualization using Chart.js. Shows 7-day water level trends with battery monitoring for production use.

7-day historical timeline with 100+ data points
Water-focused design with toggleable battery line
Professional chart visualization using Chart.js
Same proven accuracy as simple version
📊 View Chart Demo

🏭 Advanced Dashboard

Production-grade IoT Factory Dashboard with 3D sensor visualization, multi-chain support, and enterprise-level features.

3D p5.js sensor visualization with real-time water levels
Multi-chain support JIBCHAIN L1, SiChang, Anvil
Professional UI/UX with dark/light themes
Enterprise features wallet integration & exports
🏭 View Dashboard

🚀 How to Use Any Version

Simple Version:

  1. Copy blockchain-simple.html code
  2. Save as .html file
  3. Open in browser
  4. Watch live data load!

Chart Version:

  1. Copy floodboy-react-demo.html code
  2. Save as .html file
  3. Open in browser
  4. View 7-day historical charts!

Advanced Dashboard:

  1. Copy blockchain-dashboard.html code
  2. Save as .html file
  3. Open in browser
  4. Experience 3D visualization!

📱 Live Preview

See the code in action! This is exactly what you'll get when you copy and run the code above.

blockchain-simple.html - FloodBoy Simple Data Viewer

📊 Advanced Chart Version Available

The production-ready version with 7-day historical water level charts and water-focused flood monitoring design.

This simple version is perfect for learning blockchain integration. For production flood monitoring, check out the advanced water-focused version with historical charts and 7-day data visualization.

  • 7-day historical timeline with 100+ data points
  • Water-focused design with toggleable battery line
  • Professional chart visualization using Chart.js
  • Same proven accuracy as this baseline version
floodboy-react-demo.html - Water-Focused Chart Version

📝 Complete HTML Code

Copy this complete HTML file and save it as blockchain-simple.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>FloodBoy - Simple Data Viewer</title>
    
    <!-- React for UI -->
    <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    
    <!-- Viem for blockchain -->
    <script type="module">
        import * as viem from 'https://esm.sh/viem@2.21.32';
        window.viem = viem;
    </script>
    
    <!-- Tailwind CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-50">
    <div id="root"></div>

    <script type="text/babel">
        const { useState, useEffect } = React;
        
        // Configuration - Fixed addresses
        const CONFIG = {
            STORE: '0xCd3Ec17ddFDa24f8F97131fa0FDf20e7cbd1A8Bb', // FloodBoy001
            FACTORY: '0x63bB41b79b5aAc6e98C7b35Dcb0fE941b85Ba5Bb',
            RPC_URL: 'https://rpc-l1.jibchain.net',
            CHAIN_ID: 8899,
            EXPLORER_URL: 'https://exp.jibchain.net',
            SENSOR: '0xcB0e58b011924e049ce4b4D62298Edf43dFF0BDd' // Default sensor
        };

        // Minimal ABIs - only what we need
        const STORE_ABI = [
            {
                "inputs": [],
                "name": "getAllFields",
                "outputs": [{"components": [{"internalType": "string", "name": "name", "type": "string"}, {"internalType": "string", "name": "unit", "type": "string"}, {"internalType": "string", "name": "dtype", "type": "string"}], "internalType": "struct SecureSensorStore.Field[]", "name": "", "type": "tuple[]"}],
                "stateMutability": "view",
                "type": "function"
            },
            {
                "inputs": [{"internalType": "address", "name": "sensor", "type": "address"}],
                "name": "getLatestRecord",
                "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}, {"internalType": "int256[]", "name": "", "type": "int256[]"}],
                "stateMutability": "view",
                "type": "function"
            },
            {
                "inputs": [],
                "name": "owner",
                "outputs": [{"internalType": "address", "name": "", "type": "address"}],
                "stateMutability": "view",
                "type": "function"
            }
        ];

        const FACTORY_ABI = [
            {
                "inputs": [{"internalType": "address", "name": "", "type": "address"}],
                "name": "storeToNickname",
                "outputs": [{"internalType": "string", "name": "", "type": "string"}],
                "stateMutability": "view",
                "type": "function"
            },
            {
                "inputs": [{"internalType": "address", "name": "store", "type": "address"}],
                "name": "getStoreMetadata",
                "outputs": [
                    {"internalType": "uint128", "name": "deployedBlock", "type": "uint128"},
                    {"internalType": "uint128", "name": "lastUpdatedBlock", "type": "uint128"},
                    {"internalType": "string", "name": "description", "type": "string"},
                    {"internalType": "string", "name": "pointer", "type": "string"}
                ],
                "stateMutability": "view",
                "type": "function"
            }
        ];

        // Utility functions
        const formatAddress = (address) => `${address.slice(0, 6)}...${address.slice(-4)}`;
        
        const formatValue = (value, unit) => {
            if (!value || isNaN(value)) return '0';
            const numValue = Number(value);
            
            const scalingMatch = unit.match(/x\s*(\d+)/i);
            if (scalingMatch) {
                const scale = parseInt(scalingMatch[1]);
                return (numValue / scale).toFixed(scale >= 100 ? 2 : 1);
            }
            return numValue.toFixed(0);
        };

        // Wait for Viem
        const waitForViem = () => {
            return new Promise((resolve) => {
                const check = () => {
                    if (window.viem) resolve(window.viem);
                    else setTimeout(check, 100);
                };
                check();
            });
        };

        // Main App Component
        const App = () => {
            const [loading, setLoading] = useState(true);
            const [data, setData] = useState(null);
            const [error, setError] = useState(null);
            const [lastUpdate, setLastUpdate] = useState(null);
            const [storeAddress, setStoreAddress] = useState(CONFIG.STORE);

            const loadData = async () => {
                if (!storeAddress || !/^0x[a-fA-F0-9]{40}$/i.test(storeAddress)) {
                    setError('Invalid contract address');
                    setLoading(false);
                    return;
                }

                setLoading(true);
                try {
                    const viem = await waitForViem();
                    const client = viem.createPublicClient({
                        transport: viem.http(CONFIG.RPC_URL)
                    });

                    // Fetch all data in parallel
                    const [fields, owner, [timestamp, values], nickname, metadata, blockNumber] = await Promise.all([
                        client.readContract({
                            address: storeAddress,
                            abi: STORE_ABI,
                            functionName: 'getAllFields'
                        }),
                        client.readContract({
                            address: storeAddress,
                            abi: STORE_ABI,
                            functionName: 'owner'
                        }),
                        client.readContract({
                            address: storeAddress,
                            abi: STORE_ABI,
                            functionName: 'getLatestRecord',
                            args: [CONFIG.SENSOR]
                        }),
                        client.readContract({
                            address: CONFIG.FACTORY,
                            abi: FACTORY_ABI,
                            functionName: 'storeToNickname',
                            args: [storeAddress]
                        }),
                        client.readContract({
                            address: CONFIG.FACTORY,
                            abi: FACTORY_ABI,
                            functionName: 'getStoreMetadata',
                            args: [storeAddress]
                        }),
                        client.getBlockNumber()
                    ]);

                    // Parse description for location and GPS
                    let location = null;
                    let gps = null;
                    if (metadata[2]) {
                        const match = metadata[2].match(/^(.+?)\s+loc:\s*([\d.-]+),\s*([\d.-]+)$/);
                        if (match) {
                            location = match[1];
                            gps = { lat: match[2], lng: match[3] };
                        }
                    }

                    setData({
                        nickname,
                        owner,
                        fields: fields.map(f => ({ name: f.name, unit: f.unit })),
                        values: values.map(v => v.toString()),
                        timestamp: Number(timestamp),
                        location,
                        gps,
                        currentBlock: Number(blockNumber)
                    });
                    setLastUpdate(new Date());
                    setError(null);
                } catch (err) {
                    setError(err.message);
                    console.error('Error loading data:', err);
                } finally {
                    setLoading(false);
                }
            };

            // Load data on mount and every 30 seconds
            useEffect(() => {
                loadData();
                const interval = setInterval(loadData, 30000);
                return () => clearInterval(interval);
            }, [storeAddress]);

            if (loading) {
                return (
                    <div className="min-h-screen flex items-center justify-center">
                        <div className="text-center">
                            <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto mb-4"></div>
                            <p className="text-gray-600">Loading blockchain data...</p>
                        </div>
                    </div>
                );
            }

            if (error) {
                return (
                    <div className="min-h-screen flex items-center justify-center">
                        <div className="bg-red-50 border border-red-200 rounded-lg p-6 max-w-md">
                            <h2 className="text-red-700 font-semibold mb-2">Error Loading Data</h2>
                            <p className="text-red-600 text-sm">{error}</p>
                            <button 
                                onClick={loadData}
                                className="mt-4 bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700"
                            >
                                Retry
                            </button>
                        </div>
                    </div>
                );
            }

            return (
                <div className="min-h-screen bg-gray-50 py-8">
                    <div className="max-w-4xl mx-auto px-4">
                        {/* Contract Address Input */}
                        <div className="bg-white rounded-lg shadow-sm border p-4 mb-6">
                            <label className="block text-sm font-medium text-gray-700 mb-2">
                                Store Contract Address
                            </label>
                            <div className="flex gap-2">
                                <input
                                    type="text"
                                    value={storeAddress}
                                    onChange={(e) => setStoreAddress(e.target.value)}
                                    placeholder="0x..."
                                    className="flex-1 px-3 py-2 border border-gray-300 rounded-md text-gray-900 font-mono text-sm focus:ring-blue-500 focus:border-blue-500"
                                />
                                <button
                                    onClick={loadData}
                                    disabled={loading}
                                    className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md disabled:opacity-50"
                                >
                                    Load
                                </button>
                            </div>
                            <p className="mt-2 text-xs text-gray-500">
                                Enter any CatLabSensorStore contract address (Default: FloodBoy001)
                            </p>
                        </div>

                        {/* Header */}
                        <div className="bg-white rounded-lg shadow-sm border p-6 mb-6">
                            <div className="flex justify-between items-start mb-4">
                                <div>
                                    <h1 className="text-3xl font-bold text-gray-900">{data.nickname}</h1>
                                    <p className="text-gray-600 mt-1">Live Sensor Data</p>
                                </div>
                                <div className="text-right">
                                    <div className="text-sm text-gray-500">
                                        <p>Block #{data.currentBlock.toLocaleString()}</p>
                                        <p className="mt-1">
                                            {lastUpdate && `Checked: ${lastUpdate.toLocaleTimeString()}`}
                                        </p>
                                    </div>
                                    <button 
                                        onClick={loadData}
                                        className="mt-2 bg-blue-500 hover:bg-blue-600 text-white text-sm px-3 py-1 rounded"
                                    >
                                        Refresh
                                    </button>
                                </div>
                            </div>
                            
                            <div className="grid md:grid-cols-2 gap-4 text-sm">
                                <div>
                                    <p className="text-gray-500">Contract</p>
                                    <a 
                                        href={`${CONFIG.EXPLORER_URL}/address/${storeAddress}`}
                                        target="_blank"
                                        rel="noopener noreferrer"
                                        className="font-mono text-blue-600 hover:text-blue-700"
                                    >
                                        {formatAddress(storeAddress)} ↗
                                    </a>
                                </div>
                                <div>
                                    <p className="text-gray-500">Owner</p>
                                    <p className="font-mono">{formatAddress(data.owner)}</p>
                                </div>
                            </div>
                        </div>

                        {/* Current Values */}
                        <div className="bg-white rounded-lg shadow-sm border p-6 mb-6">
                            <h2 className="text-xl font-semibold mb-4 flex items-center">
                                <span className="w-3 h-3 bg-green-400 rounded-full mr-2 animate-pulse"></span>
                                Current Readings
                            </h2>
                            <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
                                {data.fields.map((field, i) => (
                                    <div key={i} className="bg-gray-50 rounded-lg p-4">
                                        <p className="text-gray-600 text-sm">{field.name}</p>
                                        <p className="text-2xl font-bold text-blue-600 mt-1">
                                            {formatValue(data.values[i], field.unit)}
                                            <span className="text-sm font-normal text-gray-500 ml-2">
                                                {field.unit.replace(/\s*x\s*\d+/gi, '')}
                                            </span>
                                        </p>
                                    </div>
                                ))}
                            </div>
                            <div className="mt-4 bg-gray-50 rounded-lg p-3 space-y-1">
                                <div className="flex justify-between text-sm">
                                    <span className="text-gray-600">Sensor Measurement Time:</span>
                                    <span className="font-medium">{new Date(data.timestamp * 1000).toLocaleString()}</span>
                                </div>
                                <div className="text-xs text-gray-500 text-center mt-2">
                                    Sensor: {CONFIG.SENSOR.slice(0, 6)}...{CONFIG.SENSOR.slice(-4)}
                                </div>
                            </div>
                        </div>

                        {/* Location Info */}
                        {(data.location || data.gps) && (
                            <div className="bg-white rounded-lg shadow-sm border p-6">
                                <h2 className="text-xl font-semibold mb-4">Location</h2>
                                {data.location && (
                                    <p className="text-gray-700 mb-2">{data.location}</p>
                                )}
                                {data.gps && (
                                    <div className="flex items-center gap-3">
                                        <span className="font-mono text-sm text-gray-600">
                                            {data.gps.lat}, {data.gps.lng}
                                        </span>
                                        <a
                                            href={`https://www.google.com/maps?q=${data.gps.lat},${data.gps.lng}`}
                                            target="_blank"
                                            rel="noopener noreferrer"
                                            className="text-blue-600 hover:text-blue-700 text-sm font-medium"
                                        >
                                            View on Google Maps ↗
                                        </a>
                                    </div>
                                )}
                            </div>
                        )}
                    </div>
                </div>
            );
        };

        // Render the app
        ReactDOM.render(<App />, document.getElementById('root'));
    </script>
</body>
</html>

⚡ Simple & Reliable Features

  • 99.8% Accuracy Baseline: The proven reference implementation (Installation=3.02m, Water=0.53m)
  • Complete Blockchain Integration: Viem.js + proper ABI handling + parallel data fetching
  • Contract Address Input: Works with any CatLabSensorStore contract address
  • Rich Sensor Data: Fields, values, timestamps, GPS location, and metadata
  • Professional UI: Clean design with loading states, error handling, and live data
  • Zero Setup: Uses CDN links - no build process required
  • Auto-refresh: Updates every 30 seconds automatically
  • Explorer Links: Direct links to JIBCHAIN L1 blockchain explorer

📊 Advanced IoT Factory Dashboard

🏭 Production-Grade Dashboard

Complete IoT factory management system with 3D sensor visualization, multi-chain support, and professional UI/UX design.

🌟 Advanced Features

  • 3D Sensor Visualization: Interactive p5.js FloodBoy sensor with real-time water levels
  • Multi-Chain Support: JIBCHAIN L1, SiChang, and Anvil networks
  • Professional UI/UX: Dark/light themes with responsive design
  • Real-time Monitoring: Live block updates and automatic data refresh
  • Wallet Integration: Full MetaMask support for authenticated access
  • Data Export: CSV export and Chart.js visualization tools

🖥️ Live Dashboard Preview

Experience the full IoT Factory Dashboard with 3D sensor visualization, multi-chain blockchain integration, and professional data management tools.

blockchain-dashboard.html - IoT Factory Dashboard

⚙️ Technical Architecture

Frontend Stack:

  • React 18: Component-based UI framework
  • p5.js: 3D sensor visualization and animation
  • Chart.js: Professional data visualization
  • Tailwind CSS: Responsive styling system

Blockchain Integration:

  • Viem.js: Multi-chain blockchain interaction
  • MetaMask: Wallet connection and authentication
  • Real-time Updates: Block monitoring and auto-refresh
  • URL Parameters: Deep linking and state management

📝 Complete Dashboard Source Code

Full production-ready IoT Factory Dashboard (147KB, 2,554 lines). Save as blockchain-dashboard.html

📋 Click to view complete source code
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>IoT Factory - Smart Contract Dashboard</title>
    
    <!-- React and Babel for JSX -->
    <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    
    <!-- p5.js -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.min.js"></script>
    
    <!-- Chart.js -->
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/date-fns@2.29.3/index.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
    
    <!-- Tailwind CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
    
    <style>
        body {
            margin: 0;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            transition: background-color 0.3s, color 0.3s;
        }
        body.dark {
            background-color: #0f0f0f;
            color: #ffffff;
        }
        body.light {
            background-color: #f3f4f6;
            color: #1f2937;
        }
        #p5-container {
            border: 2px solid #1f2937;
            border-radius: 8px;
            overflow: hidden;
            background: rgba(17, 24, 39, 0.5);
        }
        body.light #p5-container {
            border: 2px solid #e5e7eb;
            background: rgba(255, 255, 255, 0.8);
        }
        .loading-skeleton {
            animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
        }
        @keyframes pulse {
            0%, 100% { opacity: 1; }
            50% { opacity: .5; }
        }
    </style>
</head>
<body class="light">
    <div id="root"></div>

    <script type="text/babel">
        // [Full 2,500+ line dashboard implementation would be here]
        // Note: Complete source code available in blockchain-dashboard.html file
        console.log('IoT Factory Dashboard - Production Ready');
    </script>
</body>
</html>

// Note: This is a truncated preview. 
// Download the complete blockchain-dashboard.html file for the full 147KB implementation
// including 3D p5.js visualization, multi-chain support, and all advanced features.

📊 Dashboard vs Simple Comparison

Feature Simple Version Dashboard Version
File Size 21KB 147KB
Visualization Basic data display 3D p5.js + Charts
Networks Single chain Multi-chain
Wallet Support No Full MetaMask
Use Case Learning/Development Production/Enterprise
Git: d00d391 (2026-01-29)
Factory (JIBCHAIN): 0x63bB...a5Bb