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
⚛️ Production-Ready React Demo
Water-focused flood monitoring dashboard with 7-day historical data visualization. Complete rebuild for 99.8% accuracy!
Perfect for learning blockchain integration and understanding sensor data structure. Clean, minimal interface with real-time updates.
Enhanced with historical data visualization using Chart.js. Shows 7-day water level trends with battery monitoring for production use.
Production-grade IoT Factory Dashboard with 3D sensor visualization, multi-chain support, and enterprise-level features.
Simple Version:
Chart Version:
Advanced Dashboard:
See the code in action! This is exactly what you'll get when you copy and run the code above.
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.
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> 🏭 Production-Grade Dashboard
Complete IoT factory management system with 3D sensor visualization, multi-chain support, and professional UI/UX design.
Experience the full IoT Factory Dashboard with 3D sensor visualization, multi-chain blockchain integration, and professional data management tools.
Frontend Stack:
Blockchain Integration:
Full production-ready IoT Factory Dashboard (147KB, 2,554 lines). Save as blockchain-dashboard.html
<!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. | 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 |