├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── RELEASE_NOTES.md ├── backend ├── .env.example ├── Dockerfile ├── package-lock.json ├── package.json └── src │ ├── controllers │ └── transactionController.js │ ├── index.js │ ├── middleware │ ├── paginate.js │ ├── rateLimit.js │ └── validate.js │ ├── routes │ ├── healthRoutes.js │ └── transactionRoutes.js │ ├── services │ └── blockchainService.js │ └── utils │ ├── logger.js │ └── validationSchemas.js ├── contracts └── src │ ├── BioCoinBSC.sol │ ├── data_access.rs │ └── token.rs ├── docker-compose.yml ├── frontend ├── .env.example ├── Dockerfile ├── package-lock.json ├── package.json ├── postcss.config.js ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo.svg │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.js │ ├── components │ │ ├── Dashboard │ │ │ └── AnalyticsDashboard.jsx │ │ ├── Export │ │ │ └── ExportOptions.jsx │ │ ├── Footer.js │ │ ├── Navbar.js │ │ ├── Toast.js │ │ ├── ToastContainer.js │ │ └── UI │ │ │ └── DarkModeToggle.jsx │ ├── contexts │ │ ├── ThemeContext.jsx │ │ └── ToastContext.js │ ├── hooks │ │ ├── useAnalytics.js │ │ └── useTheme.js │ ├── index.css │ ├── index.js │ ├── pages │ │ ├── Dashboard.js │ │ ├── Home.js │ │ ├── NotFound.js │ │ ├── Profile.js │ │ └── Research.js │ ├── reportWebVitals.js │ └── utils │ │ ├── accessibilityHelpers.js │ │ └── exportFormats.js └── tailwind.config.js ├── package-lock.json └── package.json /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: BioCoin CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build-and-test: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [16.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v2 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | cache: 'npm' 25 | 26 | - name: Install frontend dependencies 27 | run: | 28 | cd frontend 29 | npm ci 30 | 31 | - name: Build frontend 32 | run: | 33 | cd frontend 34 | npm run build 35 | 36 | - name: Test frontend 37 | run: | 38 | cd frontend 39 | npm test -- --passWithNoTests 40 | 41 | - name: Install backend dependencies 42 | run: | 43 | cd backend 44 | npm ci 45 | 46 | - name: Test backend 47 | run: | 48 | cd backend 49 | npm test -- --passWithNoTests -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node.js 2 | node_modules/ 3 | npm-debug.log 4 | yarn-debug.log 5 | yarn-error.log 6 | # package-lock.json # Keeping package-lock.json files 7 | yarn.lock 8 | 9 | # Environment variables 10 | .env 11 | .env.local 12 | .env.development.local 13 | .env.test.local 14 | .env.production.local 15 | 16 | # Build directories 17 | dist/ 18 | build/ 19 | out/ 20 | .next/ 21 | 22 | # IDE and editors 23 | .idea/ 24 | .vscode/ 25 | *.swp 26 | *.swo 27 | .DS_Store 28 | 29 | # Logs 30 | logs/ 31 | *.log 32 | 33 | # Testing 34 | coverage/ 35 | 36 | # Solana 37 | .anchor/ 38 | target/ 39 | **/*.rs.bk 40 | 41 | # Misc 42 | .cache/ 43 | .temp/ 44 | .tmp/ 45 | tmp/ 46 | temp/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # BioCoin Changelog 2 | 3 | ## v1.02.1 (2025-03-21) 4 | 5 | ### Bug Fixes 6 | - Fixed analytics dashboard loading issue on Safari browsers 7 | - Improved mobile responsiveness for export functionality 8 | - Corrected CSV encoding in data export 9 | 10 | ### Performance Improvements 11 | - Optimized database queries for faster sample lookups (30% improvement) 12 | - Reduced API response times for transaction history endpoints 13 | - Enhanced error handling for wallet connections 14 | 15 | ### Other Changes 16 | - Updated documentation for researcher API endpoints 17 | - Standardized version numbering across all packages 18 | - Added logging for OAuth authentication errors 19 | 20 | ## v1.02 (2025-03-18) 21 | 22 | Initial release with: 23 | - Dark mode support 24 | - Analytics dashboard 25 | - Export functionality 26 | - Accessibility improvements 27 | - OAuth Integration 28 | - Webhook System 29 | 30 | For full details, see the [Release Notes](./RELEASE_NOTES.md). -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 BioCoin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BioCoin - Decentralized Biological Data Marketplace 2 | 3 |
4 | BioCoin Logo 5 | 6 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 7 | [![Website](https://img.shields.io/badge/Website-biocoin.vip-blue)](https://biocoin.vip) 8 | [![Twitter](https://img.shields.io/badge/Twitter-@BioCoindotvip-blue)](https://x.com/BioCoindotvip) 9 | [![GitHub](https://img.shields.io/badge/GitHub-BioCoinLab-blue)](https://github.com/BioCoinLab/BioCoin) 10 |
11 | 12 | ## 📊 Executive Summary 13 | 14 | BioCoin represents a paradigm shift in how individuals monetize and control their biological data, starting with the microbiome. As the world's first decentralized marketplace for biological data, BioCoin enables users to tokenize their microbiome information through advanced AI valuation, maintain complete sovereignty over their data, and receive fair compensation when researchers access it. Built on the Solana blockchain for high-performance and low transaction costs, BioCoin creates a revolutionary "Somatic Sovereignty Mesh" that unlocks the value of previously underutilized personal biological assets. 15 | 16 | ## 🌟 Core Value Proposition 17 | 18 | BioCoin addresses several critical market needs: 19 | 20 | 1. **Data Ownership & Monetization**: Individuals can maintain complete control of their biological data while earning tokens when it's accessed for research. 21 | 22 | 2. **Research Acceleration**: The platform creates direct connections between data providers and researchers, eliminating intermediaries and accelerating scientific progress. 23 | 24 | 3. **Fair Value Distribution**: Unlike traditional models where companies capture most value from biological data, BioCoin ensures providers receive the majority (70%) of access fees. 25 | 26 | 4. **Biological Asset Tokenization**: Creating a new asset class from previously untradeable biological information, with AI-driven valuation providing objective pricing. 27 | 28 | 5. **Personalized Health Insights**: Beyond financial benefits, users gain valuable knowledge about their own microbiome and health correlations. 29 | 30 | ## 🔧 Technical Architecture 31 | 32 | BioCoin employs a four-layer architecture designed for security, scalability, and user sovereignty: 33 | 34 | ```mermaid 35 | flowchart TD 36 | subgraph "Data Collection" 37 | A[Microbiome Testing Kit] --> B[Secure Sample Processing] 38 | B --> C[Data Standardization] 39 | C --> D[Secure Data Storage] 40 | end 41 | 42 | subgraph "AI Valuation Engine" 43 | D --> E[Data Preprocessing] 44 | E --> F[Feature Extraction] 45 | F --> G[ML Valuation Model] 46 | G --> H[Value Assignment] 47 | end 48 | 49 | subgraph "Blockchain Layer" 50 | H --> I[Smart Contract] 51 | I --> J[Token Issuance] 52 | J --> K[Token Wallet] 53 | end 54 | 55 | subgraph "Marketplace" 56 | K --> L[User Dashboard] 57 | K --> M[Research Access Portal] 58 | M --> N[Data Access Licensing] 59 | N --> O[Compensation Distribution] 60 | O --> K 61 | end 62 | 63 | subgraph "Future Expansion" 64 | P[Wearable Integration] -.-> E 65 | Q[Additional Biomarkers] -.-> E 66 | R[Metabolism Game] -.-> L 67 | end 68 | ``` 69 | 70 | ### 1. Data Collection & Processing Layer 71 | 72 | - **API Integration Hub**: 73 | - RESTful APIs for connecting to microbiome testing providers 74 | - OAuth 2.0 authentication for secure provider account linkage 75 | - Webhook endpoints for receiving test completion notifications 76 | - Batch import APIs for historical data retrieval 77 | 78 | - **Data Standardization Pipeline**: 79 | - Taxonomic classification harmonization across testing methodologies 80 | - Reference database mapping (NCBI taxonomy, GTDB, etc.) 81 | - Metadata standardization for consistent feature extraction 82 | - Quality control filters for contamination and sequencing errors 83 | 84 | - **Privacy Engine**: 85 | - Data pseudonymization protocols 86 | - Multi-level encryption for data at rest and in transit 87 | - Consent management system with granular permission controls 88 | - Zero-knowledge proof implementations for certain verifications 89 | 90 | - **Storage Infrastructure**: 91 | - HIPAA-compliant encrypted data vaults 92 | - Distributed storage with geographical redundancy 93 | - Cold storage for long-term data preservation 94 | - Caching layer for frequently accessed reference data 95 | 96 | ### 2. AI Analysis & Valuation Layer 97 | 98 | - **Feature Extraction Engine**: 99 | - Taxonomic profile vectorization 100 | - Metabolic pathway completion analysis 101 | - Diversity metrics calculation (Shannon, Simpson, Faith's PD) 102 | - Strain-level variant detection 103 | 104 | - **Valuation Neural Networks**: 105 | - Multi-factor valuation model architecture 106 | - Deep learning networks for pattern recognition 107 | - Transfer learning from reference population datasets 108 | - Reinforcement learning for market-responsive adjustments 109 | 110 | - **Insight Generation System**: 111 | - Health correlation inference engine 112 | - Personalized recommendation generator 113 | - Longitudinal trend analysis 114 | - Comparative population analytics 115 | 116 | - **Adaptive Simulation Environment**: 117 | - Virtual substrate utilization testing framework 118 | - Competitive microbial community modeling 119 | - Metabolic network simulation engine 120 | - Bacterial strain interaction prediction system 121 | 122 | - **Machine Learning Pipeline**: 123 | - Data preprocessing and normalization modules 124 | - Feature importance ranking system 125 | - Model training and validation workflow 126 | - A/B testing framework for model improvements 127 | 128 | ### 3. Blockchain & Smart Contract Layer 129 | 130 | - **Token Contract**: 131 | - SPL token standard implementation on Solana 132 | - Token distribution and vesting mechanics 133 | - Deflationary mechanisms (partial burn on transactions) 134 | - Staking reward distribution system 135 | 136 | - **Data Access Control**: 137 | - Permission management programs 138 | - Time-bound access grant mechanisms 139 | - Purpose-limited authorization system 140 | - Immutable access logs and audit trails 141 | 142 | - **Reward Distribution**: 143 | - Automated compensation for data access 144 | - Multi-party payment splitting mechanisms 145 | - Fee calculation based on access type and duration 146 | - Research initiative bonus distribution 147 | 148 | - **Governance System**: 149 | - Proposal submission and tracking mechanism 150 | - Token-weighted voting implementation 151 | - Automatic execution of approved changes 152 | - Community treasury management 153 | 154 | ### 4. Application & Interface Layer 155 | 156 | - **User Dashboard**: 157 | - Progressive web application (PWA) 158 | - Mobile-responsive interface 159 | - Real-time data visualization components 160 | - Personal health insights presentation 161 | 162 | - **Researcher Portal**: 163 | - Advanced query builder interface 164 | - Cohort selection and management tools 165 | - Data access request workflow 166 | - Result visualization and export tools 167 | 168 | - **Mobile Applications**: 169 | - Native iOS and Android applications 170 | - Biometric authentication 171 | - Push notifications for research opportunities 172 | - Simplified data connection flow 173 | 174 | ## 🔐 Security Architecture 175 | 176 | BioCoin implements industry-leading security measures to protect sensitive biological data: 177 | 178 | - Multi-layer encryption (AES-256, RSA) 179 | - Zero trust network architecture 180 | - Regular penetration testing schedule 181 | - Bug bounty program 182 | - Independent security audits 183 | - Comprehensive access logging 184 | - Multi-factor authentication 185 | - Role-based access control 186 | 187 | ## 🤝 Integration Partners 188 | 189 | BioCoin's ecosystem is strengthened through strategic partnerships: 190 | 191 | - **Testing Partners**: Viome, BiomeSight, Thryve, uBiome 192 | - **Research Organizations**: Knight Lab, The Microsetta Initiative, Human Microbiome Project 193 | - **Blockchain Infrastructure**: Solana validators and RPC providers 194 | - **Data Security**: Third-party encryption and security auditors 195 | 196 | ## 🛣️ Development Roadmap 197 | 198 | | Phase | Milestones | 199 | |-------|------------| 200 | | **Phase 1: Foundation** | • Core data standardization pipeline
• Initial AI valuation model
• Token contract development
• Basic user interface | 201 | | **Phase 2: Platform Launch** | • Production deployment
• Initial research marketplace
• Token generation event
• Web application release | 202 | | **Phase 3: Expansion** | • Additional biological data types
• Advanced health insights
• Research tools expansion
• Mobile application launch | 203 | | **Phase 4: Ecosystem Growth** | • Developer API availability
• Cross-chain functionality
• International expansion
• Advanced collaboration tools | 204 | 205 | ## 💻 Technical Stack 206 | 207 | BioCoin leverages a modern technology stack for optimal performance and security: 208 | 209 | ### Frontend 210 | - **Framework**: React.js with Next.js 211 | - **State Management**: Redux with Redux Toolkit 212 | - **UI Components**: TailwindCSS for styling 213 | - **Data Visualization**: D3.js and recharts 214 | - **Wallet Integration**: Solana Wallet Adapter 215 | 216 | ### Backend 217 | - **Runtime**: Node.js with Express.js 218 | - **API Gateway**: GraphQL 219 | - **Caching**: Redis 220 | - **Database**: PostgreSQL (relational) and MongoDB (document) 221 | - **Message Queue**: RabbitMQ 222 | 223 | ### Blockchain 224 | - **Network**: Solana 225 | - **Smart Contracts**: Rust 226 | - **Token Standard**: SPL Token 227 | - **Testing Framework**: Jest and Mocha 228 | 229 | ## 📂 Project Structure 230 | 231 | ``` 232 | BioCoin/ 233 | ├── frontend/ # React-based web application 234 | ├── backend/ # Node.js API server 235 | ├── contracts/ # Solana smart contracts 236 | ├── scripts/ # Utility scripts 237 | ├── docs/ # Documentation 238 | ├── assets/ # Static assets 239 | └── shared/ # Shared code and utilities 240 | ``` 241 | 242 | ## 🚀 Getting Started 243 | 244 | ### Prerequisites 245 | 246 | - Node.js (v16+) 247 | - npm or yarn 248 | - Solana CLI tools 249 | - Docker and Docker Compose (optional) 250 | 251 | ### Installation 252 | 253 | 1. Clone the repository: 254 | ``` 255 | git clone https://github.com/BioCoinLab/BioCoin.git 256 | cd BioCoin 257 | ``` 258 | 259 | 2. Install dependencies: 260 | ``` 261 | # Install frontend dependencies 262 | cd frontend 263 | npm install 264 | 265 | # Install backend dependencies 266 | cd ../backend 267 | npm install 268 | ``` 269 | 270 | 3. Set up environment variables: 271 | ``` 272 | cp .env.example .env 273 | ``` 274 | 275 | 4. Start the development servers: 276 | ``` 277 | # Start backend server 278 | cd backend 279 | npm run dev 280 | 281 | # Start frontend server (in a new terminal) 282 | cd frontend 283 | npm start 284 | ``` 285 | 286 | ## 📄 License 287 | 288 | This project is licensed under the MIT License - see the LICENSE file for details. 289 | 290 | ## 📧 Contact 291 | 292 | For questions or support, please open an issue on our [GitHub repository](https://github.com/BioCoinLab/BioCoin/issues). -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | # BioCoin v1.02 Release Notes 2 | 3 | ## Overview 4 | BioCoin v1.02 enhances the user experience with a redesigned frontend interface, improved integration features, and advanced analytics capabilities. This release focuses on usability improvements, mobile responsiveness, and better visualization of biomedical data and blockchain transactions. 5 | 6 | ## Key Features 7 | 8 | ### Frontend Enhancements 9 | - **Redesigned Dashboard** - Modern, intuitive UI with improved data visualization 10 | - **Mobile-First Approach** - Fully responsive design for all screen sizes 11 | - **Dark Mode Support** - Enhanced visual comfort with automatic theme detection 12 | - **Accessibility Improvements** - WCAG 2.1 AA compliance for inclusive user experience 13 | 14 | ### Integration Features 15 | - **OAuth Integration** - Support for Google, Apple, and institutional SSO login 16 | - **Research Institution API** - New endpoints for medical research integration 17 | - **Webhook System** - Real-time notification system for transaction events 18 | - **Export Functionality** - Data export in multiple formats (CSV, JSON, PDF) 19 | 20 | ### Analytics & Reporting 21 | - **Transaction Analytics** - Visual reports of blockchain activity and token usage 22 | - **Research Contribution Tracking** - Visualization of user contributions to research 23 | - **Interactive Data Exploration** - Advanced filtering and visualization tools 24 | - **Customizable Reports** - User-configurable dashboards and reports 25 | 26 | ## Files Added 27 | 28 | ### Frontend Components 29 | - `frontend/src/components/Dashboard/AnalyticsDashboard.jsx` - Analytics visualization component 30 | - `frontend/src/components/Charts/` - New chart components directory 31 | - `frontend/src/components/UI/DarkModeToggle.jsx` - Theme switching component 32 | - `frontend/src/components/Export/ExportOptions.jsx` - Data export functionality 33 | 34 | ### Frontend Hooks & Utils 35 | - `frontend/src/hooks/useTheme.js` - Custom hook for theme management 36 | - `frontend/src/hooks/useAnalytics.js` - Data processing for analytics 37 | - `frontend/src/utils/exportFormats.js` - Export formatting utilities 38 | - `frontend/src/utils/accessibilityHelpers.js` - Accessibility enhancement utilities 39 | 40 | ### Backend Integration 41 | - `backend/src/routes/webhookRoutes.js` - Webhook configuration and management 42 | - `backend/src/controllers/webhookController.js` - Webhook business logic 43 | - `backend/src/services/exportService.js` - Data export service 44 | - `backend/src/services/analyticsService.js` - Analytics aggregation service 45 | - `backend/src/middleware/oauth.js` - OAuth authentication middleware 46 | 47 | ## Files Modified 48 | - `frontend/src/App.jsx` - Updated routing and theme support 49 | - `frontend/src/index.css` - Added dark mode styles 50 | - `frontend/package.json` - Added new dependencies for charts and exports 51 | - `backend/package.json` - Added OAuth and export dependencies 52 | - `backend/src/routes/index.js` - Added new route configurations 53 | - `backend/src/controllers/userController.js` - Enhanced with OAuth support 54 | 55 | ## Technical Details 56 | 57 | ### New API Endpoints 58 | - `POST /api/webhooks/register` - Register a new webhook 59 | - `GET /api/analytics/transactions` - Get transaction analytics data 60 | - `GET /api/analytics/research` - Get research contribution analytics 61 | - `POST /api/export/{format}` - Export data in specified format 62 | 63 | ### Environment Configuration 64 | New environment variables: 65 | ``` 66 | OAUTH_GOOGLE_CLIENT_ID=your_google_client_id 67 | OAUTH_GOOGLE_CLIENT_SECRET=your_google_client_secret 68 | OAUTH_APPLE_CLIENT_ID=your_apple_client_id 69 | WEBHOOK_SECRET_KEY=your_webhook_secret 70 | ``` 71 | 72 | ### Frontend Enhancements 73 | - Chart.js implementation for data visualization 74 | - React Query for improved data fetching and caching 75 | - Tailwind CSS dark mode implementation 76 | - ARIA attributes for accessibility improvements 77 | 78 | ## Mobile Compatibility 79 | The v1.02 release introduces a fully responsive design that works seamlessly across: 80 | - Mobile devices (iOS and Android) 81 | - Tablets 82 | - Desktop browsers 83 | - Progressive Web App (PWA) support for installation on mobile devices 84 | 85 | ## Migration Notes 86 | Users upgrading from v1.01 should clear their browser cache after updating to ensure proper loading of new UI components and styles. No database migrations are required for this update. -------------------------------------------------------------------------------- /backend/.env.example: -------------------------------------------------------------------------------- 1 | # Server Configuration 2 | PORT=5000 3 | NODE_ENV=development 4 | CORS_ORIGIN=http://localhost:3000 5 | 6 | # MongoDB Configuration 7 | MONGODB_URI=mongodb://localhost:27017/biocoin 8 | 9 | # JWT Authentication 10 | JWT_SECRET=your_jwt_secret_key 11 | JWT_EXPIRES_IN=7d 12 | 13 | # Logging 14 | LOG_LEVEL=info 15 | LOG_DIR=logs 16 | 17 | # Solana Configuration 18 | SOLANA_RPC_URL=https://api.devnet.solana.com 19 | SOLANA_PROGRAM_ID=your_solana_program_id 20 | 21 | # Binance Smart Chain Configuration 22 | BSC_RPC_URL=https://data-seed-prebsc-1-s1.binance.org:8545/ 23 | BSC_CONTRACT_ADDRESS=your_bsc_contract_address 24 | 25 | # Demo Private Key (only for development, never use in production) 26 | DEMO_PRIVATE_KEY=your_demo_private_key 27 | 28 | # API Keys 29 | API_KEY_MICROBIOME_SERVICE=your_api_key_here -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine 2 | 3 | WORKDIR /app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | EXPOSE 5000 12 | 13 | CMD ["npm", "run", "dev"] -------------------------------------------------------------------------------- /backend/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "biocoin-backend", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "biocoin-backend", 9 | "version": "1.0.0", 10 | "dependencies": { 11 | "bcrypt": "^5.1.1", 12 | "cors": "^2.8.5", 13 | "dotenv": "^16.3.1", 14 | "express": "^4.18.2", 15 | "express-rate-limit": "^7.1.5", 16 | "helmet": "^7.1.0", 17 | "jsonwebtoken": "^9.0.2", 18 | "mongoose": "^8.0.3", 19 | "web3": "^4.3.0", 20 | "winston": "^3.11.0" 21 | }, 22 | "devDependencies": { 23 | "chai": "^4.3.10", 24 | "mocha": "^10.2.0", 25 | "nodemon": "^3.0.2", 26 | "supertest": "^6.3.3" 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "biocoin-backend", 3 | "version": "1.02.1", 4 | "description": "Backend API for BioCoin platform", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "start": "node src/index.js", 8 | "dev": "nodemon src/index.js", 9 | "test": "jest" 10 | }, 11 | "dependencies": { 12 | "@solana/web3.js": "^1.73.0", 13 | "@web3-js/scrypt-shim": "^0.1.0", 14 | "axios": "^1.3.2", 15 | "bcrypt": "^5.1.0", 16 | "cors": "^2.8.5", 17 | "dotenv": "^16.0.3", 18 | "express": "^4.18.2", 19 | "express-rate-limit": "^6.7.0", 20 | "exceljs": "^4.3.0", 21 | "joi": "^17.7.1", 22 | "jsonwebtoken": "^9.0.0", 23 | "mongoose": "^6.9.1", 24 | "morgan": "^1.10.0", 25 | "passport": "^0.6.0", 26 | "passport-google-oauth20": "^2.0.0", 27 | "passport-apple": "^2.0.2", 28 | "pdfkit": "^0.13.0", 29 | "web3": "^1.8.2", 30 | "winston": "^3.8.2", 31 | "webhook-discord": "^3.7.8" 32 | }, 33 | "devDependencies": { 34 | "jest": "^29.4.2", 35 | "nodemon": "^2.0.20", 36 | "supertest": "^6.3.3" 37 | }, 38 | "engines": { 39 | "node": ">=16.0.0" 40 | }, 41 | "license": "MIT" 42 | } -------------------------------------------------------------------------------- /backend/src/controllers/transactionController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Controller for handling transaction-related operations 3 | */ 4 | const blockchainService = require('../services/blockchainService'); 5 | const logger = require('../utils/logger'); 6 | 7 | /** 8 | * Process a data access payment 9 | * @param {Object} req - Express request object 10 | * @param {Object} res - Express response object 11 | */ 12 | const processPayment = async (req, res) => { 13 | try { 14 | const { dataId, amount, blockchain } = req.body; 15 | const userId = req.user.id; // Assuming auth middleware sets the user 16 | 17 | // In a real application, you would: 18 | // 1. Fetch the data provider's info from the database 19 | // 2. Verify that the user has permission to access the data 20 | // 3. Process the payment and record it 21 | 22 | // Mock data provider for example 23 | const dataProvider = { 24 | id: 'provider123', 25 | walletAddress: { 26 | solana: 'provider_solana_wallet_address', 27 | bsc: '0xProviderBscAddress' 28 | } 29 | }; 30 | 31 | // Mock user wallet info - in reality, this should be secured properly 32 | const userWalletInfo = { 33 | walletAddress: req.user.walletAddress[blockchain] || 'demo_wallet_address', 34 | privateKey: process.env.DEMO_PRIVATE_KEY // Only for demonstration! 35 | }; 36 | 37 | // Process the payment on the selected blockchain 38 | const paymentResult = await blockchainService.processPayment( 39 | blockchain, 40 | userWalletInfo, 41 | dataProvider.walletAddress[blockchain], 42 | amount 43 | ); 44 | 45 | if (paymentResult.success) { 46 | // In a real app, save the transaction to the database 47 | logger.info('Payment processed successfully', { 48 | userId, 49 | dataId, 50 | transactionId: paymentResult.transactionId 51 | }); 52 | 53 | return res.status(200).json({ 54 | success: true, 55 | message: 'Payment processed successfully', 56 | transaction: { 57 | id: paymentResult.transactionId, 58 | amount, 59 | timestamp: paymentResult.timestamp, 60 | blockchain 61 | } 62 | }); 63 | } 64 | 65 | return res.status(400).json({ 66 | success: false, 67 | message: 'Payment processing failed', 68 | error: paymentResult.error 69 | }); 70 | } catch (error) { 71 | logger.error('Error processing payment', { error: error.message }); 72 | return res.status(500).json({ 73 | success: false, 74 | message: 'Internal server error', 75 | error: error.message 76 | }); 77 | } 78 | }; 79 | 80 | /** 81 | * Get user wallet balances across blockchains 82 | * @param {Object} req - Express request object 83 | * @param {Object} res - Express response object 84 | */ 85 | const getBalances = async (req, res) => { 86 | try { 87 | const userId = req.user.id; // Assuming auth middleware sets the user 88 | 89 | // In real application, you would get user's wallet addresses from database 90 | const walletAddresses = { 91 | solanaAddress: req.user.walletAddress?.solana, 92 | bscAddress: req.user.walletAddress?.bsc 93 | }; 94 | 95 | const balances = await blockchainService.getBalances(walletAddresses); 96 | 97 | return res.status(200).json({ 98 | success: true, 99 | balances 100 | }); 101 | } catch (error) { 102 | logger.error('Error getting balances', { error: error.message }); 103 | return res.status(500).json({ 104 | success: false, 105 | message: 'Failed to retrieve wallet balances', 106 | error: error.message 107 | }); 108 | } 109 | }; 110 | 111 | module.exports = { 112 | processPayment, 113 | getBalances 114 | }; -------------------------------------------------------------------------------- /backend/src/index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const express = require('express'); 3 | const cors = require('cors'); 4 | const morgan = require('morgan'); 5 | const mongoose = require('mongoose'); 6 | const path = require('path'); 7 | const fs = require('fs'); 8 | const logger = require('./utils/logger'); 9 | const { defaultLimiter } = require('./middleware/rateLimit'); 10 | const { paginate } = require('./middleware/paginate'); 11 | 12 | // Import routes 13 | const healthRoutes = require('./routes/healthRoutes'); 14 | const transactionRoutes = require('./routes/transactionRoutes'); 15 | // Uncomment as routes are implemented 16 | // const userRoutes = require('./routes/userRoutes'); 17 | // const dataRoutes = require('./routes/dataRoutes'); 18 | // const researchRoutes = require('./routes/researchRoutes'); 19 | 20 | // Initialize express app 21 | const app = express(); 22 | const PORT = process.env.PORT || 5000; 23 | 24 | // Create logs directory if it doesn't exist 25 | const logsDir = process.env.LOG_DIR || path.join(__dirname, '../logs'); 26 | if (!fs.existsSync(logsDir)) { 27 | fs.mkdirSync(logsDir, { recursive: true }); 28 | } 29 | 30 | // Basic security middleware 31 | app.use(cors({ 32 | origin: process.env.CORS_ORIGIN || '*', 33 | methods: ['GET', 'POST', 'PUT', 'DELETE'], 34 | allowedHeaders: ['Content-Type', 'Authorization'] 35 | })); 36 | app.use(express.json()); 37 | 38 | // Request logging with Morgan, streaming to Winston 39 | app.use(morgan('combined', { stream: logger.stream })); 40 | 41 | // Apply rate limiting to all routes 42 | app.use(defaultLimiter); 43 | 44 | // Apply pagination middleware to all routes 45 | app.use(paginate()); 46 | 47 | // Routes 48 | app.get('/', (req, res) => { 49 | res.json({ 50 | message: 'Welcome to BioCoin API v1.01', 51 | documentation: '/api-docs', 52 | health: '/health' 53 | }); 54 | }); 55 | 56 | // API routes 57 | app.use('/health', healthRoutes); 58 | app.use('/api/transactions', transactionRoutes); 59 | // Uncomment as routes are implemented 60 | // app.use('/api/users', userRoutes); 61 | // app.use('/api/data', dataRoutes); 62 | // app.use('/api/research', researchRoutes); 63 | 64 | // Connect to MongoDB if MONGODB_URI is provided 65 | if (process.env.MONGODB_URI) { 66 | mongoose.connect(process.env.MONGODB_URI, { 67 | useNewUrlParser: true, 68 | useUnifiedTopology: true, 69 | }) 70 | .then(() => logger.info('Connected to MongoDB')) 71 | .catch((err) => logger.error('MongoDB connection error:', { error: err.message })); 72 | } else { 73 | logger.warn('MONGODB_URI not provided, skipping database connection'); 74 | } 75 | 76 | // 404 handler 77 | app.use((req, res) => { 78 | res.status(404).json({ message: 'Resource not found' }); 79 | }); 80 | 81 | // Error handling middleware 82 | app.use((err, req, res, next) => { 83 | logger.error('Unhandled error:', { error: err.message, stack: err.stack }); 84 | 85 | res.status(err.status || 500).json({ 86 | message: 'An unexpected error occurred', 87 | error: process.env.NODE_ENV === 'development' ? err.message : undefined, 88 | }); 89 | }); 90 | 91 | // Start server 92 | app.listen(PORT, () => { 93 | logger.info(`Server running on port ${PORT} (${process.env.NODE_ENV || 'development'} mode)`); 94 | logger.info(`Supporting blockchains: Solana, Binance Smart Chain`); 95 | }); 96 | 97 | module.exports = app; -------------------------------------------------------------------------------- /backend/src/middleware/paginate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Middleware for handling pagination in API responses 3 | */ 4 | 5 | /** 6 | * Creates a pagination middleware for list endpoints 7 | * @returns {Function} Express middleware function 8 | */ 9 | const paginate = () => { 10 | return (req, res, next) => { 11 | // Extract pagination parameters from query 12 | const page = parseInt(req.query.page) || 1; 13 | const limit = parseInt(req.query.limit) || 10; 14 | 15 | // Ensure valid values 16 | const validPage = page > 0 ? page : 1; 17 | const validLimit = limit > 0 && limit <= 100 ? limit : 10; 18 | 19 | // Calculate skip value for database queries 20 | const skip = (validPage - 1) * validLimit; 21 | 22 | // Attach pagination info to request object 23 | req.pagination = { 24 | page: validPage, 25 | limit: validLimit, 26 | skip, 27 | }; 28 | 29 | // Modify res.json to include pagination metadata 30 | const originalJson = res.json; 31 | res.json = function(data) { 32 | if (Array.isArray(data)) { 33 | // If the response is an array, wrap it with pagination metadata 34 | return originalJson.call(this, { 35 | data, 36 | pagination: { 37 | page: validPage, 38 | limit: validLimit, 39 | totalItems: data.length, // This should be replaced with actual total count 40 | totalPages: Math.ceil(data.length / validLimit), 41 | hasNextPage: data.length === validLimit, 42 | hasPrevPage: validPage > 1 43 | } 44 | }); 45 | } 46 | 47 | // If it's already an object with pagination data 48 | if (data && data.data && data.pagination) { 49 | return originalJson.call(this, data); 50 | } 51 | 52 | // If it's not a list response, return original 53 | return originalJson.call(this, data); 54 | }; 55 | 56 | next(); 57 | }; 58 | }; 59 | 60 | /** 61 | * Helper function to create paginated response 62 | * @param {Array} data - The data array to paginate 63 | * @param {number} totalItems - Total number of items before pagination 64 | * @param {Object} pagination - Pagination parameters 65 | * @returns {Object} Paginated response object 66 | */ 67 | const paginatedResponse = (data, totalItems, pagination) => { 68 | const { page, limit } = pagination; 69 | 70 | return { 71 | data, 72 | pagination: { 73 | page, 74 | limit, 75 | totalItems, 76 | totalPages: Math.ceil(totalItems / limit), 77 | hasNextPage: page * limit < totalItems, 78 | hasPrevPage: page > 1 79 | } 80 | }; 81 | }; 82 | 83 | module.exports = { 84 | paginate, 85 | paginatedResponse 86 | }; -------------------------------------------------------------------------------- /backend/src/middleware/rateLimit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rate limiting middleware configurations 3 | */ 4 | const rateLimit = require('express-rate-limit'); 5 | 6 | // Default rate limiter for general API endpoints 7 | const defaultLimiter = rateLimit({ 8 | windowMs: 15 * 60 * 1000, // 15 minutes 9 | max: 100, // limit each IP to 100 requests per windowMs 10 | standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers 11 | legacyHeaders: false, // Disable the `X-RateLimit-*` headers 12 | message: { 13 | error: 'Too many requests, please try again later.', 14 | retryAfter: '15 minutes' 15 | } 16 | }); 17 | 18 | // More restrictive limiter for authentication endpoints 19 | const authLimiter = rateLimit({ 20 | windowMs: 60 * 60 * 1000, // 1 hour 21 | max: 10, // limit each IP to 10 requests per windowMs 22 | standardHeaders: true, 23 | legacyHeaders: false, 24 | message: { 25 | error: 'Too many authentication attempts, please try again later.', 26 | retryAfter: '1 hour' 27 | } 28 | }); 29 | 30 | // Limiter for blockchain transaction endpoints 31 | const transactionLimiter = rateLimit({ 32 | windowMs: 5 * 60 * 1000, // 5 minutes 33 | max: 20, // limit each IP to 20 transactions per windowMs 34 | standardHeaders: true, 35 | legacyHeaders: false, 36 | message: { 37 | error: 'Too many transaction requests, please try again later.', 38 | retryAfter: '5 minutes' 39 | } 40 | }); 41 | 42 | module.exports = { 43 | defaultLimiter, 44 | authLimiter, 45 | transactionLimiter 46 | }; -------------------------------------------------------------------------------- /backend/src/middleware/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Middleware for validating requests using Joi schemas 3 | */ 4 | 5 | /** 6 | * Creates a validation middleware for a specific Joi schema 7 | * @param {Object} schema - Joi schema to validate against 8 | * @param {string} property - Request property to validate (body, params, query) 9 | * @returns {Function} Express middleware function 10 | */ 11 | const validate = (schema, property = 'body') => { 12 | return (req, res, next) => { 13 | const { error } = schema.validate(req[property], { abortEarly: false }); 14 | 15 | if (!error) { 16 | return next(); 17 | } 18 | 19 | const errorMessages = error.details.map(detail => ({ 20 | message: detail.message, 21 | path: detail.path, 22 | })); 23 | 24 | return res.status(400).json({ 25 | error: 'Validation error', 26 | details: errorMessages, 27 | }); 28 | }; 29 | }; 30 | 31 | module.exports = validate; -------------------------------------------------------------------------------- /backend/src/routes/healthRoutes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Health check routes for monitoring the API 3 | */ 4 | const express = require('express'); 5 | const router = express.Router(); 6 | const mongoose = require('mongoose'); 7 | const os = require('os'); 8 | 9 | // Simple health check endpoint 10 | router.get('/', (req, res) => { 11 | res.status(200).json({ 12 | status: 'ok', 13 | timestamp: new Date().toISOString(), 14 | service: 'BioCoin API' 15 | }); 16 | }); 17 | 18 | // Detailed health check with system information 19 | router.get('/detailed', (req, res) => { 20 | const healthData = { 21 | status: 'ok', 22 | timestamp: new Date().toISOString(), 23 | service: 'BioCoin API', 24 | version: process.env.npm_package_version || '0.1.0', 25 | uptime: Math.floor(process.uptime()) + ' seconds', 26 | hostname: os.hostname(), 27 | database: { 28 | status: mongoose.connection.readyState === 1 ? 'connected' : 'disconnected' 29 | }, 30 | memory: { 31 | free: Math.round(os.freemem() / 1024 / 1024) + 'MB', 32 | total: Math.round(os.totalmem() / 1024 / 1024) + 'MB' 33 | }, 34 | cpu: { 35 | load: os.loadavg(), 36 | cores: os.cpus().length 37 | } 38 | }; 39 | 40 | res.status(200).json(healthData); 41 | }); 42 | 43 | module.exports = router; -------------------------------------------------------------------------------- /backend/src/routes/transactionRoutes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Routes for transaction operations 3 | */ 4 | const express = require('express'); 5 | const router = express.Router(); 6 | const transactionController = require('../controllers/transactionController'); 7 | const validate = require('../middleware/validate'); 8 | const { transactionSchema } = require('../utils/validationSchemas'); 9 | const { transactionLimiter } = require('../middleware/rateLimit'); 10 | 11 | /** 12 | * @route POST /api/transactions/payment 13 | * @desc Process a payment for data access 14 | * @access Private 15 | */ 16 | router.post( 17 | '/payment', 18 | transactionLimiter, 19 | validate(transactionSchema.create), 20 | transactionController.processPayment 21 | ); 22 | 23 | /** 24 | * @route GET /api/transactions/balances 25 | * @desc Get wallet balances across blockchains 26 | * @access Private 27 | */ 28 | router.get( 29 | '/balances', 30 | transactionController.getBalances 31 | ); 32 | 33 | module.exports = router; -------------------------------------------------------------------------------- /backend/src/services/blockchainService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Service for blockchain operations (Solana and Binance Smart Chain) 3 | */ 4 | const Web3 = require('web3'); 5 | const solanaWeb3 = require('@solana/web3.js'); 6 | const logger = require('../utils/logger'); 7 | 8 | // Blockchain configuration from environment variables 9 | const config = { 10 | solana: { 11 | rpcUrl: process.env.SOLANA_RPC_URL || 'https://api.devnet.solana.com', 12 | programId: process.env.SOLANA_PROGRAM_ID 13 | }, 14 | bsc: { 15 | rpcUrl: process.env.BSC_RPC_URL || 'https://data-seed-prebsc-1-s1.binance.org:8545/', 16 | contractAddress: process.env.BSC_CONTRACT_ADDRESS 17 | } 18 | }; 19 | 20 | // Initialize Solana connection 21 | const getSolanaConnection = () => { 22 | try { 23 | return new solanaWeb3.Connection(config.solana.rpcUrl); 24 | } catch (error) { 25 | logger.error('Failed to initialize Solana connection', { error }); 26 | throw error; 27 | } 28 | }; 29 | 30 | // Initialize BSC connection using Web3 31 | const getBscWeb3 = () => { 32 | try { 33 | return new Web3(new Web3.providers.HttpProvider(config.bsc.rpcUrl)); 34 | } catch (error) { 35 | logger.error('Failed to initialize BSC Web3 connection', { error }); 36 | throw error; 37 | } 38 | }; 39 | 40 | // BioCoin token ABIs for BSC 41 | const bioCoinAbi = [ 42 | // Basic ERC20 methods 43 | { 44 | "inputs": [{ "name": "account", "type": "address" }], 45 | "name": "balanceOf", 46 | "outputs": [{ "name": "", "type": "uint256" }], 47 | "stateMutability": "view", 48 | "type": "function" 49 | }, 50 | { 51 | "inputs": [{ "name": "recipient", "type": "address" }, { "name": "amount", "type": "uint256" }], 52 | "name": "transfer", 53 | "outputs": [{ "name": "", "type": "bool" }], 54 | "stateMutability": "nonpayable", 55 | "type": "function" 56 | }, 57 | // Data access payment method 58 | { 59 | "inputs": [ 60 | { "name": "dataProvider", "type": "address" }, 61 | { "name": "amount", "type": "uint256" } 62 | ], 63 | "name": "payForDataAccess", 64 | "outputs": [{ "name": "", "type": "bool" }], 65 | "stateMutability": "nonpayable", 66 | "type": "function" 67 | } 68 | ]; 69 | 70 | // Get BioCoin contract instance for BSC 71 | const getBioCoinContract = (web3) => { 72 | try { 73 | return new web3.eth.Contract(bioCoinAbi, config.bsc.contractAddress); 74 | } catch (error) { 75 | logger.error('Failed to initialize BioCoin contract', { error }); 76 | throw error; 77 | } 78 | }; 79 | 80 | // Get account balance on Solana 81 | const getSolanaBalance = async (walletAddress) => { 82 | try { 83 | const connection = getSolanaConnection(); 84 | const pubkey = new solanaWeb3.PublicKey(walletAddress); 85 | const balance = await connection.getBalance(pubkey); 86 | return balance / solanaWeb3.LAMPORTS_PER_SOL; 87 | } catch (error) { 88 | logger.error('Failed to get Solana balance', { error, walletAddress }); 89 | throw error; 90 | } 91 | }; 92 | 93 | // Get token balance on BSC 94 | const getBscTokenBalance = async (walletAddress) => { 95 | try { 96 | const web3 = getBscWeb3(); 97 | const contract = getBioCoinContract(web3); 98 | const balance = await contract.methods.balanceOf(walletAddress).call(); 99 | return web3.utils.fromWei(balance, 'ether'); 100 | } catch (error) { 101 | logger.error('Failed to get BSC token balance', { error, walletAddress }); 102 | throw error; 103 | } 104 | }; 105 | 106 | // Process payment for data access on Solana 107 | const processPaymentSolana = async (payerWallet, dataProviderWallet, amount) => { 108 | // In a real implementation, this would use Solana transactions to call the program 109 | logger.info('Processing Solana payment', { payer: payerWallet, provider: dataProviderWallet, amount }); 110 | 111 | // For this example, we're just returning a mock transaction ID 112 | return { 113 | success: true, 114 | transactionId: `sol_${Date.now()}`, 115 | amount, 116 | timestamp: new Date().toISOString() 117 | }; 118 | }; 119 | 120 | // Process payment for data access on BSC 121 | const processPaymentBsc = async (payerWallet, payerPrivateKey, dataProviderWallet, amount) => { 122 | try { 123 | const web3 = getBscWeb3(); 124 | const contract = getBioCoinContract(web3); 125 | 126 | // Convert amount to wei 127 | const amountInWei = web3.utils.toWei(amount.toString(), 'ether'); 128 | 129 | // Create transaction 130 | const tx = contract.methods.payForDataAccess(dataProviderWallet, amountInWei); 131 | 132 | // Get gas price and estimate gas 133 | const gasPrice = await web3.eth.getGasPrice(); 134 | const gasLimit = await tx.estimateGas({ from: payerWallet }); 135 | 136 | // Sign and send transaction 137 | const signedTx = await web3.eth.accounts.signTransaction( 138 | { 139 | to: config.bsc.contractAddress, 140 | data: tx.encodeABI(), 141 | gas: gasLimit, 142 | gasPrice 143 | }, 144 | payerPrivateKey 145 | ); 146 | 147 | const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction); 148 | 149 | return { 150 | success: true, 151 | transactionId: receipt.transactionHash, 152 | amount, 153 | timestamp: new Date().toISOString() 154 | }; 155 | } catch (error) { 156 | logger.error('Failed to process BSC payment', { error, payer: payerWallet, provider: dataProviderWallet }); 157 | return { 158 | success: false, 159 | error: error.message 160 | }; 161 | } 162 | }; 163 | 164 | // Process payment based on blockchain type 165 | const processPayment = async (blockchain, payerInfo, dataProviderWallet, amount) => { 166 | try { 167 | switch (blockchain.toLowerCase()) { 168 | case 'solana': 169 | return await processPaymentSolana(payerInfo.walletAddress, dataProviderWallet, amount); 170 | 171 | case 'bsc': 172 | return await processPaymentBsc( 173 | payerInfo.walletAddress, 174 | payerInfo.privateKey, 175 | dataProviderWallet, 176 | amount 177 | ); 178 | 179 | default: 180 | throw new Error(`Unsupported blockchain: ${blockchain}`); 181 | } 182 | } catch (error) { 183 | logger.error('Payment processing failed', { error, blockchain }); 184 | throw error; 185 | } 186 | }; 187 | 188 | // Get balance across multiple blockchains 189 | const getBalances = async (walletAddresses) => { 190 | const { solanaAddress, bscAddress } = walletAddresses; 191 | const balances = {}; 192 | 193 | if (solanaAddress) { 194 | try { 195 | balances.solana = await getSolanaBalance(solanaAddress); 196 | } catch (error) { 197 | balances.solana = { error: error.message }; 198 | } 199 | } 200 | 201 | if (bscAddress) { 202 | try { 203 | balances.bsc = await getBscTokenBalance(bscAddress); 204 | } catch (error) { 205 | balances.bsc = { error: error.message }; 206 | } 207 | } 208 | 209 | return balances; 210 | }; 211 | 212 | module.exports = { 213 | processPayment, 214 | getBalances, 215 | getSolanaBalance, 216 | getBscTokenBalance 217 | }; -------------------------------------------------------------------------------- /backend/src/utils/logger.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Logger utility based on Winston 3 | */ 4 | const winston = require('winston'); 5 | const path = require('path'); 6 | 7 | // Define log format 8 | const logFormat = winston.format.combine( 9 | winston.format.timestamp(), 10 | winston.format.errors({ stack: true }), 11 | winston.format.json() 12 | ); 13 | 14 | // Create the logger instance 15 | const logger = winston.createLogger({ 16 | level: process.env.LOG_LEVEL || 'info', 17 | format: logFormat, 18 | defaultMeta: { service: 'biocoin-api' }, 19 | transports: [ 20 | // Console transport for development 21 | new winston.transports.Console({ 22 | format: winston.format.combine( 23 | winston.format.colorize(), 24 | winston.format.printf(({ timestamp, level, message, service, ...meta }) => { 25 | return `${timestamp} [${service}] ${level}: ${message} ${ 26 | Object.keys(meta).length ? JSON.stringify(meta, null, 2) : '' 27 | }`; 28 | }) 29 | ) 30 | }), 31 | 32 | // File transport for production logging 33 | new winston.transports.File({ 34 | filename: path.join(process.env.LOG_DIR || 'logs', 'error.log'), 35 | level: 'error' 36 | }), 37 | new winston.transports.File({ 38 | filename: path.join(process.env.LOG_DIR || 'logs', 'combined.log') 39 | }) 40 | ] 41 | }); 42 | 43 | // Add stream for Morgan integration 44 | logger.stream = { 45 | write: (message) => { 46 | logger.info(message.trim()); 47 | } 48 | }; 49 | 50 | module.exports = logger; -------------------------------------------------------------------------------- /backend/src/utils/validationSchemas.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Joi validation schemas for API requests 3 | */ 4 | const Joi = require('joi'); 5 | 6 | // User validation schemas 7 | const userSchema = { 8 | register: Joi.object({ 9 | email: Joi.string().email().required(), 10 | password: Joi.string().min(8).required(), 11 | name: Joi.string().min(2).max(100).required(), 12 | walletAddress: Joi.string().optional() 13 | }), 14 | 15 | login: Joi.object({ 16 | email: Joi.string().email().required(), 17 | password: Joi.string().required() 18 | }), 19 | 20 | updateProfile: Joi.object({ 21 | name: Joi.string().min(2).max(100).optional(), 22 | email: Joi.string().email().optional(), 23 | walletAddress: Joi.string().optional(), 24 | bio: Joi.string().max(500).optional() 25 | }) 26 | }; 27 | 28 | // Data validation schemas 29 | const dataSchema = { 30 | create: Joi.object({ 31 | title: Joi.string().min(3).max(100).required(), 32 | description: Joi.string().max(500).required(), 33 | dataType: Joi.string().valid('microbiome', 'genetic', 'clinical').required(), 34 | isPublic: Joi.boolean().default(false), 35 | price: Joi.number().min(0).required() 36 | }), 37 | 38 | update: Joi.object({ 39 | title: Joi.string().min(3).max(100).optional(), 40 | description: Joi.string().max(500).optional(), 41 | isPublic: Joi.boolean().optional(), 42 | price: Joi.number().min(0).optional() 43 | }) 44 | }; 45 | 46 | // Research validation schemas 47 | const researchSchema = { 48 | create: Joi.object({ 49 | title: Joi.string().min(3).max(100).required(), 50 | description: Joi.string().required(), 51 | requestedDataTypes: Joi.array().items( 52 | Joi.string().valid('microbiome', 'genetic', 'clinical') 53 | ).min(1).required(), 54 | budget: Joi.number().min(0).required(), 55 | duration: Joi.number().min(1).required() // Duration in days 56 | }), 57 | 58 | update: Joi.object({ 59 | title: Joi.string().min(3).max(100).optional(), 60 | description: Joi.string().optional(), 61 | budget: Joi.number().min(0).optional(), 62 | duration: Joi.number().min(1).optional(), 63 | status: Joi.string().valid('draft', 'open', 'in_progress', 'completed', 'cancelled').optional() 64 | }) 65 | }; 66 | 67 | // Transaction validation schemas 68 | const transactionSchema = { 69 | create: Joi.object({ 70 | dataId: Joi.string().required(), 71 | amount: Joi.number().min(0).required(), 72 | blockchain: Joi.string().valid('solana', 'bsc').required() 73 | }) 74 | }; 75 | 76 | // Pagination validation schema (for query parameters) 77 | const paginationSchema = Joi.object({ 78 | page: Joi.number().integer().min(1).default(1), 79 | limit: Joi.number().integer().min(1).max(100).default(10), 80 | sort: Joi.string().optional(), 81 | order: Joi.string().valid('asc', 'desc').default('desc') 82 | }); 83 | 84 | module.exports = { 85 | userSchema, 86 | dataSchema, 87 | researchSchema, 88 | transactionSchema, 89 | paginationSchema 90 | }; -------------------------------------------------------------------------------- /contracts/src/BioCoinBSC.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; 7 | 8 | /** 9 | * @title BioCoin 10 | * @dev ERC20 Token for BioCoin platform on Binance Smart Chain. 11 | * Enables data access payments and platform fee distribution. 12 | */ 13 | contract BioCoin is ERC20, Ownable, ReentrancyGuard { 14 | // Platform fee percentage (70% to data provider, 30% to platform) 15 | uint8 public constant PLATFORM_FEE_PERCENT = 30; 16 | 17 | // Platform wallet address for collecting fees 18 | address public platformWallet; 19 | 20 | // Data access payment events 21 | event DataAccessPayment( 22 | address indexed researcher, 23 | address indexed dataProvider, 24 | uint256 amount, 25 | uint256 providerShare, 26 | uint256 platformShare, 27 | uint256 timestamp 28 | ); 29 | 30 | /** 31 | * @dev Constructor for BioCoin 32 | * @param initialSupply Initial token supply (in smallest unit) 33 | * @param platformAddress Address to receive platform fees 34 | */ 35 | constructor( 36 | uint256 initialSupply, 37 | address platformAddress 38 | ) ERC20("BioCoin", "BIO") { 39 | require(platformAddress != address(0), "Platform address cannot be zero"); 40 | 41 | platformWallet = platformAddress; 42 | _mint(msg.sender, initialSupply); 43 | } 44 | 45 | /** 46 | * @dev Change the platform wallet address 47 | * @param newPlatformWallet New platform wallet address 48 | */ 49 | function updatePlatformWallet(address newPlatformWallet) external onlyOwner { 50 | require(newPlatformWallet != address(0), "Platform address cannot be zero"); 51 | platformWallet = newPlatformWallet; 52 | } 53 | 54 | /** 55 | * @dev Process payment for data access 56 | * @param dataProvider Address of the data provider 57 | * @param amount Amount to pay for data access 58 | * @return bool Success status 59 | */ 60 | function payForDataAccess(address dataProvider, uint256 amount) external nonReentrant returns (bool) { 61 | require(dataProvider != address(0), "Data provider address cannot be zero"); 62 | require(amount > 0, "Payment amount must be greater than zero"); 63 | require(balanceOf(msg.sender) >= amount, "Insufficient balance"); 64 | 65 | // Calculate shares 66 | uint256 platformShare = (amount * PLATFORM_FEE_PERCENT) / 100; 67 | uint256 providerShare = amount - platformShare; 68 | 69 | // Transfer shares 70 | _transfer(msg.sender, dataProvider, providerShare); 71 | _transfer(msg.sender, platformWallet, platformShare); 72 | 73 | // Emit event 74 | emit DataAccessPayment( 75 | msg.sender, 76 | dataProvider, 77 | amount, 78 | providerShare, 79 | platformShare, 80 | block.timestamp 81 | ); 82 | 83 | return true; 84 | } 85 | 86 | /** 87 | * @dev Get platform fee distribution info 88 | * @param amount The transaction amount 89 | * @return providerShare Amount that goes to the data provider 90 | * @return platformShare Amount that goes to the platform 91 | */ 92 | function calculateFeeDistribution(uint256 amount) public pure returns (uint256 providerShare, uint256 platformShare) { 93 | platformShare = (amount * PLATFORM_FEE_PERCENT) / 100; 94 | providerShare = amount - platformShare; 95 | return (providerShare, platformShare); 96 | } 97 | } -------------------------------------------------------------------------------- /contracts/src/data_access.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::token::{self, Token, TokenAccount}; 3 | 4 | declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); 5 | 6 | #[program] 7 | pub mod biocoin_data_access { 8 | use super::*; 9 | 10 | pub fn register_data( 11 | ctx: Context, 12 | data_hash: String, 13 | data_type: String, 14 | description: String, 15 | price: u64, 16 | ) -> Result<()> { 17 | let data_record = &mut ctx.accounts.data_record; 18 | let owner = &ctx.accounts.owner; 19 | 20 | data_record.owner = *owner.key; 21 | data_record.data_hash = data_hash; 22 | data_record.data_type = data_type; 23 | data_record.description = description; 24 | data_record.price = price; 25 | data_record.is_available = true; 26 | data_record.access_count = 0; 27 | data_record.created_at = Clock::get()?.unix_timestamp; 28 | data_record.updated_at = Clock::get()?.unix_timestamp; 29 | 30 | Ok(()) 31 | } 32 | 33 | pub fn update_data_price( 34 | ctx: Context, 35 | new_price: u64, 36 | ) -> Result<()> { 37 | let data_record = &mut ctx.accounts.data_record; 38 | 39 | // Ensure only the owner can update the price 40 | require!( 41 | data_record.owner == *ctx.accounts.owner.key, 42 | DataAccessError::Unauthorized 43 | ); 44 | 45 | data_record.price = new_price; 46 | data_record.updated_at = Clock::get()?.unix_timestamp; 47 | 48 | Ok(()) 49 | } 50 | 51 | pub fn toggle_data_availability( 52 | ctx: Context, 53 | ) -> Result<()> { 54 | let data_record = &mut ctx.accounts.data_record; 55 | 56 | // Ensure only the owner can toggle availability 57 | require!( 58 | data_record.owner == *ctx.accounts.owner.key, 59 | DataAccessError::Unauthorized 60 | ); 61 | 62 | data_record.is_available = !data_record.is_available; 63 | data_record.updated_at = Clock::get()?.unix_timestamp; 64 | 65 | Ok(()) 66 | } 67 | 68 | pub fn request_data_access( 69 | ctx: Context, 70 | purpose: String, 71 | duration_days: u16, 72 | ) -> Result<()> { 73 | let data_record = &ctx.accounts.data_record; 74 | let access_request = &mut ctx.accounts.access_request; 75 | let requester = &ctx.accounts.requester; 76 | 77 | // Ensure data is available 78 | require!( 79 | data_record.is_available, 80 | DataAccessError::DataNotAvailable 81 | ); 82 | 83 | access_request.data_record = *ctx.accounts.data_record.to_account_info().key; 84 | access_request.requester = *requester.key; 85 | access_request.owner = data_record.owner; 86 | access_request.purpose = purpose; 87 | access_request.duration_days = duration_days; 88 | access_request.status = AccessStatus::Pending; 89 | access_request.requested_at = Clock::get()?.unix_timestamp; 90 | access_request.approved_at = 0; 91 | access_request.expires_at = 0; 92 | 93 | Ok(()) 94 | } 95 | 96 | pub fn approve_data_access( 97 | ctx: Context, 98 | ) -> Result<()> { 99 | let data_record = &mut ctx.accounts.data_record; 100 | let access_request = &mut ctx.accounts.access_request; 101 | 102 | // Ensure only the data owner can approve access 103 | require!( 104 | data_record.owner == *ctx.accounts.owner.key, 105 | DataAccessError::Unauthorized 106 | ); 107 | 108 | // Ensure the request is pending 109 | require!( 110 | access_request.status == AccessStatus::Pending, 111 | DataAccessError::InvalidRequestStatus 112 | ); 113 | 114 | let now = Clock::get()?.unix_timestamp; 115 | let expires_at = now + (access_request.duration_days as i64 * 86400); // 86400 seconds in a day 116 | 117 | access_request.status = AccessStatus::Approved; 118 | access_request.approved_at = now; 119 | access_request.expires_at = expires_at; 120 | 121 | // Increment access count 122 | data_record.access_count += 1; 123 | data_record.updated_at = now; 124 | 125 | Ok(()) 126 | } 127 | 128 | pub fn deny_data_access( 129 | ctx: Context, 130 | reason: String, 131 | ) -> Result<()> { 132 | let data_record = &ctx.accounts.data_record; 133 | let access_request = &mut ctx.accounts.access_request; 134 | 135 | // Ensure only the data owner can deny access 136 | require!( 137 | data_record.owner == *ctx.accounts.owner.key, 138 | DataAccessError::Unauthorized 139 | ); 140 | 141 | // Ensure the request is pending 142 | require!( 143 | access_request.status == AccessStatus::Pending, 144 | DataAccessError::InvalidRequestStatus 145 | ); 146 | 147 | access_request.status = AccessStatus::Denied; 148 | access_request.denial_reason = Some(reason); 149 | 150 | Ok(()) 151 | } 152 | } 153 | 154 | #[derive(Accounts)] 155 | pub struct RegisterData<'info> { 156 | #[account(init, payer = owner, space = 8 + DataRecord::LEN)] 157 | pub data_record: Account<'info, DataRecord>, 158 | #[account(mut)] 159 | pub owner: Signer<'info>, 160 | pub system_program: Program<'info, System>, 161 | } 162 | 163 | #[derive(Accounts)] 164 | pub struct UpdateDataPrice<'info> { 165 | #[account(mut)] 166 | pub data_record: Account<'info, DataRecord>, 167 | pub owner: Signer<'info>, 168 | } 169 | 170 | #[derive(Accounts)] 171 | pub struct ToggleDataAvailability<'info> { 172 | #[account(mut)] 173 | pub data_record: Account<'info, DataRecord>, 174 | pub owner: Signer<'info>, 175 | } 176 | 177 | #[derive(Accounts)] 178 | pub struct RequestDataAccess<'info> { 179 | pub data_record: Account<'info, DataRecord>, 180 | #[account(init, payer = requester, space = 8 + AccessRequest::LEN)] 181 | pub access_request: Account<'info, AccessRequest>, 182 | #[account(mut)] 183 | pub requester: Signer<'info>, 184 | pub system_program: Program<'info, System>, 185 | } 186 | 187 | #[derive(Accounts)] 188 | pub struct ApproveDataAccess<'info> { 189 | #[account(mut)] 190 | pub data_record: Account<'info, DataRecord>, 191 | #[account(mut)] 192 | pub access_request: Account<'info, AccessRequest>, 193 | pub owner: Signer<'info>, 194 | } 195 | 196 | #[derive(Accounts)] 197 | pub struct DenyDataAccess<'info> { 198 | pub data_record: Account<'info, DataRecord>, 199 | #[account(mut)] 200 | pub access_request: Account<'info, AccessRequest>, 201 | pub owner: Signer<'info>, 202 | } 203 | 204 | #[account] 205 | pub struct DataRecord { 206 | pub owner: Pubkey, 207 | pub data_hash: String, 208 | pub data_type: String, 209 | pub description: String, 210 | pub price: u64, 211 | pub is_available: bool, 212 | pub access_count: u32, 213 | pub created_at: i64, 214 | pub updated_at: i64, 215 | } 216 | 217 | impl DataRecord { 218 | pub const LEN: usize = 32 + 64 + 32 + 256 + 8 + 1 + 4 + 8 + 8; 219 | } 220 | 221 | #[account] 222 | pub struct AccessRequest { 223 | pub data_record: Pubkey, 224 | pub requester: Pubkey, 225 | pub owner: Pubkey, 226 | pub purpose: String, 227 | pub duration_days: u16, 228 | pub status: AccessStatus, 229 | pub requested_at: i64, 230 | pub approved_at: i64, 231 | pub expires_at: i64, 232 | pub denial_reason: Option, 233 | } 234 | 235 | impl AccessRequest { 236 | pub const LEN: usize = 32 + 32 + 32 + 256 + 2 + 1 + 8 + 8 + 8 + 128; 237 | } 238 | 239 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Eq)] 240 | pub enum AccessStatus { 241 | Pending, 242 | Approved, 243 | Denied, 244 | Expired, 245 | } 246 | 247 | #[error_code] 248 | pub enum DataAccessError { 249 | #[msg("You are not authorized to perform this action")] 250 | Unauthorized, 251 | #[msg("The data is not available for access")] 252 | DataNotAvailable, 253 | #[msg("Invalid request status for this operation")] 254 | InvalidRequestStatus, 255 | } -------------------------------------------------------------------------------- /contracts/src/token.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::token::{self, Mint, Token, TokenAccount}; 3 | 4 | declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); 5 | 6 | #[program] 7 | pub mod biocoin_token { 8 | use super::*; 9 | 10 | pub fn initialize( 11 | ctx: Context, 12 | name: String, 13 | symbol: String, 14 | decimals: u8, 15 | total_supply: u64, 16 | ) -> Result<()> { 17 | let token_program = &ctx.accounts.token_program; 18 | let mint = &ctx.accounts.mint; 19 | let token_account = &ctx.accounts.token_account; 20 | let authority = &ctx.accounts.authority; 21 | let rent = &ctx.accounts.rent; 22 | 23 | // Initialize the mint 24 | token::initialize_mint( 25 | CpiContext::new( 26 | token_program.to_account_info(), 27 | token::InitializeMint { 28 | mint: mint.to_account_info(), 29 | rent: rent.to_account_info(), 30 | }, 31 | ), 32 | decimals, 33 | authority.key, 34 | Some(authority.key), 35 | )?; 36 | 37 | // Initialize the token account 38 | token::initialize_account( 39 | CpiContext::new( 40 | token_program.to_account_info(), 41 | token::InitializeAccount { 42 | account: token_account.to_account_info(), 43 | mint: mint.to_account_info(), 44 | authority: authority.to_account_info(), 45 | rent: rent.to_account_info(), 46 | }, 47 | ), 48 | )?; 49 | 50 | // Mint tokens to the token account 51 | token::mint_to( 52 | CpiContext::new( 53 | token_program.to_account_info(), 54 | token::MintTo { 55 | mint: mint.to_account_info(), 56 | to: token_account.to_account_info(), 57 | authority: authority.to_account_info(), 58 | }, 59 | ), 60 | total_supply, 61 | )?; 62 | 63 | // Store token metadata 64 | let token_info = &mut ctx.accounts.token_info; 65 | token_info.name = name; 66 | token_info.symbol = symbol; 67 | token_info.decimals = decimals; 68 | token_info.total_supply = total_supply; 69 | token_info.authority = *authority.key; 70 | 71 | Ok(()) 72 | } 73 | 74 | pub fn transfer( 75 | ctx: Context, 76 | amount: u64, 77 | ) -> Result<()> { 78 | token::transfer( 79 | CpiContext::new( 80 | ctx.accounts.token_program.to_account_info(), 81 | token::Transfer { 82 | from: ctx.accounts.from.to_account_info(), 83 | to: ctx.accounts.to.to_account_info(), 84 | authority: ctx.accounts.authority.to_account_info(), 85 | }, 86 | ), 87 | amount, 88 | )?; 89 | 90 | Ok(()) 91 | } 92 | 93 | pub fn data_access_payment( 94 | ctx: Context, 95 | amount: u64, 96 | ) -> Result<()> { 97 | let provider_share = (amount * 70) / 100; // 70% to data provider 98 | let platform_share = amount - provider_share; // 30% to platform 99 | 100 | // Transfer to data provider 101 | token::transfer( 102 | CpiContext::new( 103 | ctx.accounts.token_program.to_account_info(), 104 | token::Transfer { 105 | from: ctx.accounts.researcher.to_account_info(), 106 | to: ctx.accounts.data_provider.to_account_info(), 107 | authority: ctx.accounts.authority.to_account_info(), 108 | }, 109 | ), 110 | provider_share, 111 | )?; 112 | 113 | // Transfer to platform 114 | token::transfer( 115 | CpiContext::new( 116 | ctx.accounts.token_program.to_account_info(), 117 | token::Transfer { 118 | from: ctx.accounts.researcher.to_account_info(), 119 | to: ctx.accounts.platform.to_account_info(), 120 | authority: ctx.accounts.authority.to_account_info(), 121 | }, 122 | ), 123 | platform_share, 124 | )?; 125 | 126 | // Record the transaction 127 | let transaction_record = &mut ctx.accounts.transaction_record; 128 | transaction_record.researcher = *ctx.accounts.researcher.to_account_info().key; 129 | transaction_record.data_provider = *ctx.accounts.data_provider.to_account_info().key; 130 | transaction_record.amount = amount; 131 | transaction_record.provider_share = provider_share; 132 | transaction_record.platform_share = platform_share; 133 | transaction_record.timestamp = Clock::get()?.unix_timestamp; 134 | 135 | Ok(()) 136 | } 137 | } 138 | 139 | #[derive(Accounts)] 140 | pub struct Initialize<'info> { 141 | #[account(init, payer = authority, space = 8 + TokenInfo::LEN)] 142 | pub token_info: Account<'info, TokenInfo>, 143 | pub mint: Account<'info, Mint>, 144 | pub token_account: Account<'info, TokenAccount>, 145 | #[account(mut)] 146 | pub authority: Signer<'info>, 147 | pub token_program: Program<'info, Token>, 148 | pub rent: Sysvar<'info, Rent>, 149 | pub system_program: Program<'info, System>, 150 | } 151 | 152 | #[derive(Accounts)] 153 | pub struct Transfer<'info> { 154 | #[account(mut)] 155 | pub from: Account<'info, TokenAccount>, 156 | #[account(mut)] 157 | pub to: Account<'info, TokenAccount>, 158 | pub authority: Signer<'info>, 159 | pub token_program: Program<'info, Token>, 160 | } 161 | 162 | #[derive(Accounts)] 163 | pub struct DataAccessPayment<'info> { 164 | #[account(mut)] 165 | pub researcher: Account<'info, TokenAccount>, 166 | #[account(mut)] 167 | pub data_provider: Account<'info, TokenAccount>, 168 | #[account(mut)] 169 | pub platform: Account<'info, TokenAccount>, 170 | pub authority: Signer<'info>, 171 | #[account(init, payer = authority, space = 8 + TransactionRecord::LEN)] 172 | pub transaction_record: Account<'info, TransactionRecord>, 173 | pub token_program: Program<'info, Token>, 174 | pub system_program: Program<'info, System>, 175 | } 176 | 177 | #[account] 178 | pub struct TokenInfo { 179 | pub name: String, 180 | pub symbol: String, 181 | pub decimals: u8, 182 | pub total_supply: u64, 183 | pub authority: Pubkey, 184 | } 185 | 186 | impl TokenInfo { 187 | pub const LEN: usize = 32 + 8 + 4 + 32 + 1 + 8 + 32; 188 | } 189 | 190 | #[account] 191 | pub struct TransactionRecord { 192 | pub researcher: Pubkey, 193 | pub data_provider: Pubkey, 194 | pub amount: u64, 195 | pub provider_share: u64, 196 | pub platform_share: u64, 197 | pub timestamp: i64, 198 | } 199 | 200 | impl TransactionRecord { 201 | pub const LEN: usize = 32 + 32 + 8 + 8 + 8 + 8; 202 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | frontend: 5 | build: 6 | context: ./frontend 7 | dockerfile: Dockerfile 8 | ports: 9 | - "3000:3000" 10 | volumes: 11 | - ./frontend:/app 12 | - /app/node_modules 13 | environment: 14 | - NODE_ENV=development 15 | - REACT_APP_API_URL=http://localhost:5000 16 | depends_on: 17 | - backend 18 | 19 | backend: 20 | build: 21 | context: ./backend 22 | dockerfile: Dockerfile 23 | ports: 24 | - "5000:5000" 25 | volumes: 26 | - ./backend:/app 27 | - /app/node_modules 28 | environment: 29 | - NODE_ENV=development 30 | - PORT=5000 31 | - MONGODB_URI=mongodb://mongo:27017/biocoin 32 | - JWT_SECRET=your_jwt_secret_key 33 | - JWT_EXPIRES_IN=7d 34 | - SOLANA_NETWORK=devnet 35 | - SOLANA_RPC_URL=https://api.devnet.solana.com 36 | depends_on: 37 | - mongo 38 | 39 | mongo: 40 | image: mongo:latest 41 | ports: 42 | - "27017:27017" 43 | volumes: 44 | - mongo-data:/data/db 45 | environment: 46 | - MONGO_INITDB_DATABASE=biocoin 47 | 48 | volumes: 49 | mongo-data: -------------------------------------------------------------------------------- /frontend/.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_API_URL=http://localhost:5000 2 | REACT_APP_SOLANA_NETWORK=devnet 3 | REACT_APP_SOLANA_RPC_URL=https://api.devnet.solana.com -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine 2 | 3 | WORKDIR /app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | EXPOSE 3000 12 | 13 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /frontend/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "biocoin-frontend", 3 | "version": "0.1.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "biocoin-frontend", 9 | "version": "0.1.0", 10 | "dependencies": { 11 | "@headlessui/react": "^1.7.17", 12 | "@heroicons/react": "^2.0.18", 13 | "axios": "^1.6.2", 14 | "ethers": "^6.9.0", 15 | "react": "^18.2.0", 16 | "react-dom": "^18.2.0", 17 | "react-router-dom": "^6.20.1", 18 | "react-scripts": "5.0.1", 19 | "web-vitals": "^2.1.4" 20 | }, 21 | "devDependencies": { 22 | "autoprefixer": "^10.4.16", 23 | "postcss": "^8.4.32", 24 | "tailwindcss": "^3.3.6" 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "biocoin-frontend", 3 | "version": "1.02.1", 4 | "private": true, 5 | "dependencies": { 6 | "@solana/web3.js": "^1.73.0", 7 | "@testing-library/jest-dom": "^5.16.5", 8 | "@testing-library/react": "^13.4.0", 9 | "@testing-library/user-event": "^13.5.0", 10 | "axios": "^1.3.2", 11 | "chart.js": "^4.2.1", 12 | "react": "^18.2.0", 13 | "react-chartjs-2": "^5.2.0", 14 | "react-dom": "^18.2.0", 15 | "react-query": "^3.39.3", 16 | "react-router-dom": "^6.8.1", 17 | "react-scripts": "5.0.1", 18 | "react-select": "^5.7.0", 19 | "tailwindcss": "^3.2.6", 20 | "web-vitals": "^2.1.4" 21 | }, 22 | "scripts": { 23 | "start": "react-scripts start", 24 | "build": "react-scripts build", 25 | "test": "react-scripts test", 26 | "eject": "react-scripts eject" 27 | }, 28 | "eslintConfig": { 29 | "extends": [ 30 | "react-app", 31 | "react-app/jest" 32 | ] 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | }, 46 | "devDependencies": { 47 | "autoprefixer": "^10.4.13", 48 | "postcss": "^8.4.21" 49 | } 50 | } -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | BioCoin - Monetize Your Biological Data 28 | 29 | 30 | 31 |
32 | 42 | 43 | -------------------------------------------------------------------------------- /frontend/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "BioCoin", 3 | "name": "BioCoin - Monetize Your Biological Data", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#00cc00", 24 | "background_color": "#ffffff" 25 | } -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; 3 | 4 | // Import pages 5 | import Home from './pages/Home'; 6 | import Dashboard from './pages/Dashboard'; 7 | import Research from './pages/Research'; 8 | import Profile from './pages/Profile'; 9 | import NotFound from './pages/NotFound'; 10 | 11 | // Import components 12 | import Navbar from './components/Navbar'; 13 | import Footer from './components/Footer'; 14 | import ToastContainer from './components/ToastContainer'; 15 | 16 | // Import context providers 17 | import { ToastProvider } from './contexts/ToastContext'; 18 | import { ThemeProvider } from './contexts/ThemeContext'; 19 | 20 | function App() { 21 | return ( 22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | } /> 30 | } /> 31 | } /> 32 | } /> 33 | } /> 34 | 35 |
36 |
37 | 38 |
39 |
40 |
41 |
42 | ); 43 | } 44 | 45 | export default App; -------------------------------------------------------------------------------- /frontend/src/components/Dashboard/AnalyticsDashboard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { exportAsCSV, exportAsJSON } from '../../utils/exportFormats'; 3 | 4 | /** 5 | * Analytics Dashboard component for visualizing blockchain and research data 6 | */ 7 | const AnalyticsDashboard = ({ userTransactions, researchContributions }) => { 8 | const [timeRange, setTimeRange] = useState('month'); 9 | 10 | // Mock data for demonstration 11 | const mockTransactions = userTransactions || [ 12 | { id: 1, date: '2025-02-15', amount: 45.25, researcher: 'Microbiome Research Institute', type: 'payment' }, 13 | { id: 2, date: '2025-02-10', amount: 32.50, researcher: 'BioGenetics Labs', type: 'payment' }, 14 | { id: 3, date: '2025-01-28', amount: 48.00, researcher: 'Global Health Initiative', type: 'payment' }, 15 | { id: 4, date: '2025-01-15', amount: 25.75, researcher: 'BioGenetics Labs', type: 'payment' }, 16 | { id: 5, date: '2024-12-22', amount: 60.25, researcher: 'Microbiome Research Institute', type: 'payment' }, 17 | ]; 18 | 19 | const mockResearch = researchContributions || [ 20 | { id: 1, date: '2025-02-16', type: 'dna', amount: 1, value: 45.25 }, 21 | { id: 2, date: '2025-02-11', type: 'bloodwork', amount: 1, value: 32.50 }, 22 | { id: 3, date: '2025-01-29', type: 'microbiome', amount: 1, value: 48.00 }, 23 | { id: 4, date: '2025-01-16', type: 'bloodwork', amount: 1, value: 25.75 }, 24 | { id: 5, date: '2024-12-23', type: 'microbiome', amount: 1, value: 60.25 }, 25 | ]; 26 | 27 | // Calculate analytics data 28 | const calculateAnalytics = () => { 29 | // Filter by time range 30 | const now = new Date(); 31 | let startDate; 32 | 33 | switch(timeRange) { 34 | case 'week': 35 | startDate = new Date(now.setDate(now.getDate() - 7)); 36 | break; 37 | case 'month': 38 | startDate = new Date(now.setMonth(now.getMonth() - 1)); 39 | break; 40 | case 'year': 41 | startDate = new Date(now.setFullYear(now.getFullYear() - 1)); 42 | break; 43 | default: 44 | startDate = new Date(0); // Beginning of time 45 | } 46 | 47 | // Fix for Safari: ensure dates are parsed correctly by standardizing format 48 | const parseSafariSafeDate = (dateString) => { 49 | // Convert any date format to YYYY-MM-DD format that works in Safari 50 | if (!dateString) return new Date(0); 51 | 52 | // If it's already a Date object 53 | if (dateString instanceof Date) return dateString; 54 | 55 | // Handle ISO strings and other formats 56 | // Replace dashes with slashes for Safari compatibility 57 | const normalizedDate = dateString.replace(/-/g, '/'); 58 | return new Date(normalizedDate); 59 | }; 60 | 61 | const filteredTransactions = mockTransactions.filter( 62 | tx => parseSafariSafeDate(tx.date) >= startDate 63 | ); 64 | 65 | const filteredResearch = mockResearch.filter( 66 | r => parseSafariSafeDate(r.date) >= startDate 67 | ); 68 | 69 | // Calculate totals 70 | const totalEarnings = filteredTransactions.reduce( 71 | (sum, tx) => sum + tx.amount, 72 | 0 73 | ).toFixed(2); 74 | 75 | const totalContributions = filteredResearch.length; 76 | 77 | // Group by researcher for pie chart data 78 | const researcherData = filteredTransactions.reduce((acc, tx) => { 79 | acc[tx.researcher] = (acc[tx.researcher] || 0) + tx.amount; 80 | return acc; 81 | }, {}); 82 | 83 | // Group by contribution type 84 | const contributionTypes = filteredResearch.reduce((acc, c) => { 85 | acc[c.type] = (acc[c.type] || 0) + 1; 86 | return acc; 87 | }, {}); 88 | 89 | return { 90 | totalEarnings, 91 | totalContributions, 92 | researcherData, 93 | contributionTypes 94 | }; 95 | }; 96 | 97 | const analytics = calculateAnalytics(); 98 | 99 | // Handle export 100 | const handleExport = (format) => { 101 | const data = { 102 | analytics: analytics, 103 | transactions: mockTransactions, 104 | researchContributions: mockResearch 105 | }; 106 | 107 | if (format === 'csv') { 108 | exportAsCSV(mockTransactions, 'biocoin_transactions.csv'); 109 | } else { 110 | exportAsJSON(data, 'biocoin_analytics.json'); 111 | } 112 | }; 113 | 114 | return ( 115 |
116 |
117 |

Analytics Dashboard

118 |
119 | 129 | 135 | 141 |
142 |
143 | 144 |
145 | {/* Summary Cards */} 146 |
147 |

Earnings

148 |

${analytics.totalEarnings}

149 |

From {mockTransactions.length} transactions

150 |
151 | 152 |
153 |

Contributions

154 |

{analytics.totalContributions}

155 |

Across {Object.keys(analytics.contributionTypes).length} research types

156 |
157 | 158 | {/* Researcher Distribution */} 159 |
160 |

Earnings by Researcher

161 |
162 | {Object.entries(analytics.researcherData).map(([researcher, amount]) => ( 163 |
164 |
165 |
171 |
172 | 173 | {researcher}: ${amount.toFixed(2)} 174 | 175 |
176 | ))} 177 |
178 |
179 | 180 | {/* Contribution Types */} 181 |
182 |

Contribution Types

183 |
184 | {Object.entries(analytics.contributionTypes).map(([type, count]) => ( 185 |
186 |

{type}

187 |

{count}

188 |
189 | ))} 190 |
191 |
192 |
193 |
194 | ); 195 | }; 196 | 197 | export default AnalyticsDashboard; -------------------------------------------------------------------------------- /frontend/src/components/Export/ExportOptions.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { exportAsCSV, exportAsJSON, exportAsText } from '../../utils/exportFormats'; 3 | 4 | /** 5 | * Component that provides export functionality with multiple format options 6 | */ 7 | const ExportOptions = ({ data, filename, title = 'Export Data' }) => { 8 | // Handle different export formats 9 | const handleExport = (format) => { 10 | if (!data) { 11 | console.error('No data provided for export'); 12 | return; 13 | } 14 | 15 | switch (format) { 16 | case 'csv': 17 | exportAsCSV(data, `${filename || 'export'}.csv`); 18 | break; 19 | case 'json': 20 | exportAsJSON(data, `${filename || 'export'}.json`); 21 | break; 22 | case 'text': 23 | // For text export, convert data to a string representation 24 | const textContent = typeof data === 'string' 25 | ? data 26 | : JSON.stringify(data, null, 2); 27 | exportAsText(textContent, `${filename || 'export'}.txt`); 28 | break; 29 | default: 30 | console.error('Unsupported export format:', format); 31 | } 32 | }; 33 | 34 | return ( 35 |
36 |

{title}

37 |
38 | 45 | 52 | 59 |
60 |

61 | Export your data in multiple formats for use in other applications 62 |

63 |
64 | ); 65 | }; 66 | 67 | export default ExportOptions; -------------------------------------------------------------------------------- /frontend/src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { useThemeContext } from '../contexts/ThemeContext'; 4 | 5 | const Footer = () => { 6 | const { theme } = useThemeContext(); 7 | 8 | return ( 9 |
10 |
11 |
12 | {/* Company Info */} 13 |
14 |

BioCoin

15 |

16 | The world's first decentralized marketplace for biological data, starting with the microbiome. 17 |

18 | 54 |
55 | 56 | {/* Quick Links */} 57 |
58 |

Quick Links

59 |
    60 |
  • 61 | 62 | Home 63 | 64 |
  • 65 |
  • 66 | 67 | Dashboard 68 | 69 |
  • 70 |
  • 71 | 72 | Research 73 | 74 |
  • 75 |
  • 76 | 77 | My Profile 78 | 79 |
  • 80 |
81 |
82 | 83 | {/* Resources */} 84 |
85 |

Resources

86 | 120 |
121 | 122 | {/* Contact */} 123 |
124 |

Contact Us

125 |

Email: info@biocoin.vip

126 |

Address: 123 Blockchain Ave, Bio City, 94101

127 |
128 |
129 | 130 |
131 |

© {new Date().getFullYear()} BioCoin. All rights reserved.

132 |
133 |
134 |
135 | ); 136 | }; 137 | 138 | export default Footer; -------------------------------------------------------------------------------- /frontend/src/components/Navbar.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import DarkModeToggle from './UI/DarkModeToggle'; 4 | import { useThemeContext } from '../contexts/ThemeContext'; 5 | 6 | const Navbar = () => { 7 | const [isMenuOpen, setIsMenuOpen] = useState(false); 8 | const { theme } = useThemeContext(); 9 | 10 | const toggleMenu = () => { 11 | setIsMenuOpen(!isMenuOpen); 12 | }; 13 | 14 | return ( 15 | 111 | ); 112 | }; 113 | 114 | export default Navbar; -------------------------------------------------------------------------------- /frontend/src/components/Toast.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { TOAST_TYPES } from '../contexts/ToastContext'; 3 | 4 | // Individual Toast component 5 | const Toast = ({ id, type, message, onClose }) => { 6 | // Define styles based on toast type 7 | const getToastStyles = () => { 8 | switch (type) { 9 | case TOAST_TYPES.SUCCESS: 10 | return 'bg-green-100 border-green-500 text-green-700'; 11 | case TOAST_TYPES.ERROR: 12 | return 'bg-red-100 border-red-500 text-red-700'; 13 | case TOAST_TYPES.WARNING: 14 | return 'bg-yellow-100 border-yellow-500 text-yellow-700'; 15 | case TOAST_TYPES.INFO: 16 | default: 17 | return 'bg-blue-100 border-blue-500 text-blue-700'; 18 | } 19 | }; 20 | 21 | // Define icon based on toast type 22 | const getIcon = () => { 23 | switch (type) { 24 | case TOAST_TYPES.SUCCESS: 25 | return ( 26 | 27 | 28 | 29 | ); 30 | case TOAST_TYPES.ERROR: 31 | return ( 32 | 33 | 34 | 35 | ); 36 | case TOAST_TYPES.WARNING: 37 | return ( 38 | 39 | 40 | 41 | ); 42 | case TOAST_TYPES.INFO: 43 | default: 44 | return ( 45 | 46 | 47 | 48 | ); 49 | } 50 | }; 51 | 52 | return ( 53 |
57 |
58 |
59 |
60 | {getIcon()} 61 |
62 |
63 |

{message}

64 |
65 |
66 | 75 |
76 |
77 | ); 78 | }; 79 | 80 | export default Toast; -------------------------------------------------------------------------------- /frontend/src/components/ToastContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useToast } from '../contexts/ToastContext'; 3 | import Toast from './Toast'; 4 | 5 | const ToastContainer = () => { 6 | const { toasts, removeToast } = useToast(); 7 | 8 | if (toasts.length === 0) { 9 | return null; 10 | } 11 | 12 | return ( 13 |
18 | {toasts.map((toast) => ( 19 | 26 | ))} 27 |
28 | ); 29 | }; 30 | 31 | export default ToastContainer; -------------------------------------------------------------------------------- /frontend/src/components/UI/DarkModeToggle.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useTheme } from '../../hooks/useTheme'; 3 | 4 | /** 5 | * Toggle button for switching between dark and light modes 6 | */ 7 | const DarkModeToggle = () => { 8 | const { theme, toggleTheme } = useTheme(); 9 | 10 | return ( 11 | 42 | ); 43 | }; 44 | 45 | export default DarkModeToggle; -------------------------------------------------------------------------------- /frontend/src/contexts/ThemeContext.jsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext } from 'react'; 2 | import { useTheme } from '../hooks/useTheme'; 3 | 4 | // Create context 5 | const ThemeContext = createContext({ 6 | theme: 'light', 7 | toggleTheme: () => {}, 8 | }); 9 | 10 | /** 11 | * Theme provider component that manages dark/light mode 12 | */ 13 | export const ThemeProvider = ({ children }) => { 14 | const themeValue = useTheme(); 15 | 16 | return ( 17 | 18 | {children} 19 | 20 | ); 21 | }; 22 | 23 | /** 24 | * Hook to use the theme context 25 | */ 26 | export const useThemeContext = () => { 27 | const context = useContext(ThemeContext); 28 | if (context === undefined) { 29 | throw new Error('useThemeContext must be used within a ThemeProvider'); 30 | } 31 | return context; 32 | }; -------------------------------------------------------------------------------- /frontend/src/contexts/ToastContext.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useState, useCallback } from 'react'; 2 | 3 | // Toast types 4 | export const TOAST_TYPES = { 5 | SUCCESS: 'success', 6 | ERROR: 'error', 7 | INFO: 'info', 8 | WARNING: 'warning', 9 | }; 10 | 11 | // Create context 12 | const ToastContext = createContext(null); 13 | 14 | // Default duration for toasts in milliseconds 15 | const DEFAULT_DURATION = 5000; 16 | 17 | export const ToastProvider = ({ children }) => { 18 | const [toasts, setToasts] = useState([]); 19 | 20 | // Add a new toast 21 | const addToast = useCallback( 22 | ({ type = TOAST_TYPES.INFO, message, duration = DEFAULT_DURATION }) => { 23 | const id = Date.now().toString(); 24 | setToasts((prevToasts) => [...prevToasts, { id, type, message }]); 25 | 26 | // Auto-remove toast after duration 27 | if (duration !== 0) { 28 | setTimeout(() => { 29 | removeToast(id); 30 | }, duration); 31 | } 32 | 33 | return id; 34 | }, 35 | [] 36 | ); 37 | 38 | // Remove a toast by ID 39 | const removeToast = useCallback((id) => { 40 | setToasts((prevToasts) => prevToasts.filter((toast) => toast.id !== id)); 41 | }, []); 42 | 43 | // Convenience methods for different toast types 44 | const success = useCallback( 45 | (message, duration) => addToast({ type: TOAST_TYPES.SUCCESS, message, duration }), 46 | [addToast] 47 | ); 48 | 49 | const error = useCallback( 50 | (message, duration) => addToast({ type: TOAST_TYPES.ERROR, message, duration }), 51 | [addToast] 52 | ); 53 | 54 | const info = useCallback( 55 | (message, duration) => addToast({ type: TOAST_TYPES.INFO, message, duration }), 56 | [addToast] 57 | ); 58 | 59 | const warning = useCallback( 60 | (message, duration) => addToast({ type: TOAST_TYPES.WARNING, message, duration }), 61 | [addToast] 62 | ); 63 | 64 | return ( 65 | 68 | {children} 69 | 70 | ); 71 | }; 72 | 73 | // Custom hook to use the toast context 74 | export const useToast = () => { 75 | const context = useContext(ToastContext); 76 | if (!context) { 77 | throw new Error('useToast must be used within a ToastProvider'); 78 | } 79 | return context; 80 | }; 81 | 82 | export default ToastContext; -------------------------------------------------------------------------------- /frontend/src/hooks/useAnalytics.js: -------------------------------------------------------------------------------- 1 | import { useState, useMemo } from 'react'; 2 | 3 | /** 4 | * Custom hook for processing and analyzing data for dashboards 5 | * @param {Array} data - Raw data array to analyze 6 | * @param {Object} options - Configuration options for the analysis 7 | * @returns {Object} Processed analytics and methods to filter/sort 8 | */ 9 | export const useAnalytics = (data, options = {}) => { 10 | const [timeRange, setTimeRange] = useState(options.defaultTimeRange || 'month'); 11 | const [sortBy, setSortBy] = useState(options.defaultSortBy || 'date'); 12 | const [sortDirection, setSortDirection] = useState(options.defaultSortDirection || 'desc'); 13 | 14 | // Filter data based on time range 15 | const filteredData = useMemo(() => { 16 | if (!data || !Array.isArray(data) || data.length === 0) return []; 17 | 18 | const now = new Date(); 19 | let startDate; 20 | 21 | switch(timeRange) { 22 | case 'week': 23 | startDate = new Date(now.setDate(now.getDate() - 7)); 24 | break; 25 | case 'month': 26 | startDate = new Date(now.setMonth(now.getMonth() - 1)); 27 | break; 28 | case 'year': 29 | startDate = new Date(now.setFullYear(now.getFullYear() - 1)); 30 | break; 31 | case 'all': 32 | default: 33 | return [...data]; // Return all data 34 | } 35 | 36 | // Filter data where date is after startDate 37 | return data.filter(item => { 38 | const itemDate = new Date(item.date); 39 | return itemDate >= startDate; 40 | }); 41 | }, [data, timeRange]); 42 | 43 | // Sort filtered data 44 | const sortedData = useMemo(() => { 45 | if (!filteredData.length) return []; 46 | 47 | return [...filteredData].sort((a, b) => { 48 | // Handle different types of values 49 | const valueA = typeof a[sortBy] === 'string' ? a[sortBy].toLowerCase() : a[sortBy]; 50 | const valueB = typeof b[sortBy] === 'string' ? b[sortBy].toLowerCase() : b[sortBy]; 51 | 52 | // Handle dates specially 53 | if (sortBy === 'date') { 54 | const dateA = new Date(a.date); 55 | const dateB = new Date(b.date); 56 | return sortDirection === 'asc' ? dateA - dateB : dateB - dateA; 57 | } 58 | 59 | // For other fields 60 | if (sortDirection === 'asc') { 61 | return valueA > valueB ? 1 : -1; 62 | } else { 63 | return valueA < valueB ? 1 : -1; 64 | } 65 | }); 66 | }, [filteredData, sortBy, sortDirection]); 67 | 68 | // Calculate summary statistics 69 | const summaryStats = useMemo(() => { 70 | if (!sortedData.length) return {}; 71 | 72 | const stats = { 73 | count: sortedData.length, 74 | }; 75 | 76 | // Check if data has numeric amounts to calculate totals 77 | if (sortedData.some(item => typeof item.amount === 'number')) { 78 | stats.totalAmount = sortedData.reduce((sum, item) => sum + (item.amount || 0), 0); 79 | stats.averageAmount = stats.totalAmount / stats.count; 80 | } 81 | 82 | // Get most recent item 83 | stats.mostRecent = sortedData[0]; 84 | 85 | // Group by category if data has categories 86 | if (sortedData.some(item => item.category)) { 87 | stats.byCategory = sortedData.reduce((acc, item) => { 88 | const category = item.category || 'Uncategorized'; 89 | 90 | if (!acc[category]) { 91 | acc[category] = { 92 | count: 0, 93 | totalAmount: 0, 94 | }; 95 | } 96 | 97 | acc[category].count++; 98 | acc[category].totalAmount += (item.amount || 0); 99 | 100 | return acc; 101 | }, {}); 102 | } 103 | 104 | return stats; 105 | }, [sortedData]); 106 | 107 | // Change the time range 108 | const changeTimeRange = (newRange) => { 109 | setTimeRange(newRange); 110 | }; 111 | 112 | // Change sorting 113 | const changeSorting = (newSortBy, newDirection) => { 114 | setSortBy(newSortBy); 115 | if (newDirection) { 116 | setSortDirection(newDirection); 117 | } else { 118 | // Toggle direction if same field clicked again 119 | setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc'); 120 | } 121 | }; 122 | 123 | return { 124 | data: sortedData, 125 | stats: summaryStats, 126 | timeRange, 127 | sortBy, 128 | sortDirection, 129 | changeTimeRange, 130 | changeSorting, 131 | }; 132 | }; -------------------------------------------------------------------------------- /frontend/src/hooks/useTheme.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | 3 | /** 4 | * Hook to manage theme (dark/light mode) 5 | * @returns {Object} Theme state and functions to manage it 6 | */ 7 | export const useTheme = () => { 8 | // Check for system preference or stored preference 9 | const getInitialTheme = () => { 10 | const savedTheme = localStorage.getItem('theme'); 11 | if (savedTheme) { 12 | return savedTheme; 13 | } 14 | 15 | // Check system preference 16 | return window.matchMedia && 17 | window.matchMedia('(prefers-color-scheme: dark)').matches 18 | ? 'dark' 19 | : 'light'; 20 | }; 21 | 22 | const [theme, setTheme] = useState(getInitialTheme); 23 | 24 | // Toggle between light and dark mode 25 | const toggleTheme = () => { 26 | setTheme(prevTheme => { 27 | const newTheme = prevTheme === 'light' ? 'dark' : 'light'; 28 | localStorage.setItem('theme', newTheme); 29 | return newTheme; 30 | }); 31 | }; 32 | 33 | // Apply theme class to document 34 | useEffect(() => { 35 | const root = window.document.documentElement; 36 | root.classList.remove('light', 'dark'); 37 | root.classList.add(theme); 38 | }, [theme]); 39 | 40 | return { theme, toggleTheme }; 41 | }; -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* Base styles */ 6 | body { 7 | margin: 0; 8 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 9 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 10 | sans-serif; 11 | -webkit-font-smoothing: antialiased; 12 | -moz-osx-font-smoothing: grayscale; 13 | } 14 | 15 | code { 16 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 17 | monospace; 18 | } 19 | 20 | /* Dark mode styles */ 21 | .dark body { 22 | @apply bg-gray-900 text-white; 23 | } 24 | 25 | .dark .shadow { 26 | box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2), 0 2px 4px -1px rgba(0, 0, 0, 0.1); 27 | } 28 | 29 | /* Transitions for smooth theme switching */ 30 | .transition-colors { 31 | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; 32 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); 33 | transition-duration: 150ms; 34 | } 35 | 36 | /* Accessibility - focus styles */ 37 | :focus { 38 | @apply outline-none ring-2 ring-blue-500; 39 | } 40 | 41 | /* Make sure form elements are readable in dark mode */ 42 | .dark input, 43 | .dark select, 44 | .dark textarea { 45 | @apply bg-gray-800 border-gray-700 text-white; 46 | } 47 | 48 | /* Custom scrollbar for dark mode */ 49 | .dark ::-webkit-scrollbar { 50 | width: 10px; 51 | height: 10px; 52 | } 53 | 54 | .dark ::-webkit-scrollbar-track { 55 | @apply bg-gray-800; 56 | } 57 | 58 | .dark ::-webkit-scrollbar-thumb { 59 | @apply bg-gray-600 rounded-full; 60 | } 61 | 62 | .dark ::-webkit-scrollbar-thumb:hover { 63 | @apply bg-gray-500; 64 | } -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | const root = ReactDOM.createRoot(document.getElementById('root')); 8 | root.render( 9 | 10 | 11 | 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); -------------------------------------------------------------------------------- /frontend/src/pages/Dashboard.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useToast } from '../contexts/ToastContext'; 3 | import AnalyticsDashboard from '../components/Dashboard/AnalyticsDashboard'; 4 | import { useThemeContext } from '../contexts/ThemeContext'; 5 | 6 | const Dashboard = () => { 7 | const [isConnected, setIsConnected] = useState(false); 8 | const toast = useToast(); 9 | const { theme } = useThemeContext(); 10 | 11 | // Mock data for demonstration 12 | const userData = { 13 | totalEarnings: 125.75, 14 | dataPoints: 3, 15 | pendingRequests: 2, 16 | recentTransactions: [ 17 | { id: 1, date: '2023-02-15', amount: 45.25, researcher: 'Microbiome Research Institute' }, 18 | { id: 2, date: '2023-02-10', amount: 32.50, researcher: 'BioGenetics Labs' }, 19 | { id: 3, date: '2023-01-28', amount: 48.00, researcher: 'Global Health Initiative' }, 20 | ], 21 | }; 22 | 23 | const connectWallet = () => { 24 | // In a real implementation, this would connect to a Solana wallet 25 | setIsConnected(true); 26 | toast.success('Wallet connected successfully!'); 27 | }; 28 | 29 | const connectData = () => { 30 | // In a real implementation, this would open a modal to connect data sources 31 | toast.info('Initiating connection to your microbiome data sources...'); 32 | 33 | // Simulate an async operation 34 | setTimeout(() => { 35 | toast.success('Successfully connected to 1 data source!'); 36 | }, 2000); 37 | }; 38 | 39 | const approveRequest = () => { 40 | toast.info('Processing your approval...'); 41 | 42 | // Simulate an async operation 43 | setTimeout(() => { 44 | toast.success('Request approved! You will receive BioCoin tokens once the transaction is processed.'); 45 | }, 1500); 46 | }; 47 | 48 | const rejectRequest = () => { 49 | toast.info('Processing your rejection...'); 50 | 51 | // Simulate an async operation 52 | setTimeout(() => { 53 | toast.warning('Request rejected. The researcher will not have access to your data.'); 54 | }, 1500); 55 | }; 56 | 57 | return ( 58 |
59 |
60 |
61 |

Dashboard

62 |

63 | Manage your BioCoin contributions and earnings 64 |

65 |
66 | 67 | {!isConnected ? ( 68 | 74 | ) : ( 75 |
76 | Wallet Connected 77 |
78 | )} 79 |
80 | 81 | {isConnected ? ( 82 |
83 |
84 |

Total Earnings

85 |

${userData.totalEarnings}

86 |

From {userData.dataPoints} contributions

87 |
88 | 89 |
90 |

Data Contributions

91 |

{userData.dataPoints}

92 |

Helping advance research

93 |
94 | 95 |
96 |

Pending Requests

97 |

{userData.pendingRequests}

98 |

From research institutions

99 |
100 |
101 | ) : ( 102 |
103 |
104 |
105 | 106 | 107 | 108 |
109 |
110 |

111 | Connect your wallet to view your dashboard and start earning BioCoin. 112 |

113 |
114 |
115 |
116 | )} 117 | 118 | {isConnected && ( 119 | <> 120 |

Recent Transactions

121 |
122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | {userData.recentTransactions.map((tx) => ( 132 | 133 | 134 | 135 | 136 | 137 | ))} 138 | 139 |
DateAmountResearcher
{tx.date}${tx.amount}{tx.researcher}
140 |
141 | 142 | {/* New Analytics Dashboard Component */} 143 | 144 | 145 | )} 146 |
147 | ); 148 | }; 149 | 150 | export default Dashboard; -------------------------------------------------------------------------------- /frontend/src/pages/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { useToast } from '../contexts/ToastContext'; 4 | 5 | const Home = () => { 6 | const toast = useToast(); 7 | 8 | // Demo function to show various toast notifications 9 | const showToastDemo = (type) => { 10 | switch (type) { 11 | case 'success': 12 | toast.success('Successfully connected to your wallet!'); 13 | break; 14 | case 'error': 15 | toast.error('Unable to process transaction. Please try again.'); 16 | break; 17 | case 'info': 18 | toast.info('New research opportunity available for your data profile.'); 19 | break; 20 | case 'warning': 21 | toast.warning('Your data sharing permissions will expire in 7 days.'); 22 | break; 23 | default: 24 | toast.info('Welcome to BioCoin platform!'); 25 | } 26 | }; 27 | 28 | return ( 29 |
30 | {/* Hero Section */} 31 |
32 |

33 | Monetize Your Biological Data with BioCoin 34 |

35 |

36 | The world's first decentralized marketplace for biological data, starting with the microbiome. 37 |

38 |
39 | 43 | Get Started 44 | 45 | 49 | For Researchers 50 | 51 |
52 |
53 | 54 | {/* New Demo Section */} 55 |
56 |

Experience Our New Notification System

57 |

BioCoin v1.01 now features a robust notification system to keep you informed about important events.

58 |
59 | 65 | 71 | 77 | 83 |
84 |
85 | 86 | {/* Features Section */} 87 |
88 |

Key Features

89 |
90 |
91 |

Data Ownership

92 |

Maintain complete control of your biological data while earning tokens when it's accessed for research.

93 |
94 |
95 |

Fair Value Distribution

96 |

Unlike traditional models, BioCoin ensures data providers receive the majority (70%) of access fees.

97 |
98 |
99 |

AI-Driven Valuation

100 |

Advanced AI algorithms provide objective pricing for your biological data assets.

101 |
102 |
103 |
104 | 105 | {/* How It Works Section */} 106 |
107 |

How It Works

108 |
109 |
110 |
111 |

1. Connect Your Data

112 |

Link your microbiome test results from supported providers or upload your raw data directly.

113 |
114 |
115 | {/* Placeholder for illustration */} 116 |
117 | Data Connection Illustration 118 |
119 |
120 |
121 | 122 |
123 |
124 |

2. Receive AI Valuation

125 |

Our advanced AI analyzes your microbiome data and assigns a fair market value based on uniqueness and research relevance.

126 |
127 |
128 | {/* Placeholder for illustration */} 129 |
130 | AI Valuation Illustration 131 |
132 |
133 |
134 | 135 |
136 |
137 |

3. Earn BioCoin Tokens

138 |

When researchers access your data, you automatically receive BioCoin tokens as compensation, with complete transparency.

139 |
140 |
141 | {/* Placeholder for illustration */} 142 |
143 | Token Rewards Illustration 144 |
145 |
146 |
147 |
148 |
149 | 150 | {/* CTA Section */} 151 |
152 |

Ready to Monetize Your Biological Data?

153 |

154 | Join thousands of users who are already earning BioCoin tokens by contributing to scientific research. 155 |

156 | 160 | Get Started Now 161 | 162 |
163 |
164 | ); 165 | }; 166 | 167 | export default Home; -------------------------------------------------------------------------------- /frontend/src/pages/NotFound.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | const NotFound = () => { 5 | return ( 6 |
7 |

404 - Page Not Found

8 |

9 | The page you are looking for does not exist or has been moved. 10 |

11 | 15 | Return to Home 16 | 17 |
18 | ); 19 | }; 20 | 21 | export default NotFound; -------------------------------------------------------------------------------- /frontend/src/pages/Profile.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | const Profile = () => { 4 | const [activeTab, setActiveTab] = useState('account'); 5 | 6 | // Mock user data for demonstration 7 | const userData = { 8 | name: 'Alex Johnson', 9 | email: 'alex.johnson@example.com', 10 | walletAddress: '8ZJ6BLiXSxQVXzpGLnwdeM3UqwPK3MbJGX9iMaYCTUTU', 11 | joinDate: 'January 15, 2023', 12 | dataContributions: 3, 13 | totalEarnings: 125.75, 14 | dataSettings: { 15 | allowAnonymousResearch: true, 16 | allowCommercialUse: false, 17 | requireApproval: true, 18 | shareHealthInsights: true, 19 | }, 20 | }; 21 | 22 | return ( 23 |
24 |
25 | {/* Sidebar */} 26 |
27 |
28 |
29 |
30 | 31 | {userData.name.split(' ').map(n => n[0]).join('')} 32 | 33 |
34 |

{userData.name}

35 |

Member since {userData.joinDate}

36 |
37 |
38 |
39 | Data Contributions: 40 | {userData.dataContributions} 41 |
42 |
43 | Total Earnings: 44 | {userData.totalEarnings} BIO 45 |
46 |
47 |
48 | 49 | {/* Navigation */} 50 |
51 | 59 | 67 | 75 | 83 |
84 |
85 | 86 | {/* Main Content */} 87 |
88 |
89 | {/* Account Settings Tab */} 90 | {activeTab === 'account' && ( 91 |
92 |

Account Settings

93 |
94 |
95 |
96 | 97 | 102 |
103 |
104 | 105 | 110 |
111 |
112 | 113 |
114 | 115 |
116 | 122 | 129 |
130 |
131 | 132 |
133 | 134 | 140 |
141 | 142 |
143 | 144 |
145 |
146 | 147 | 148 |
149 |
150 | 151 | 152 |
153 |
154 | 155 | 156 |
157 |
158 |
159 | 160 |
161 | 167 |
168 |
169 |
170 | )} 171 | 172 | {/* Data Management Tab */} 173 | {activeTab === 'data' && ( 174 |
175 |

Data Management

176 |
177 |

Your Microbiome Data

178 |
179 |
180 |
181 |

Gut Microbiome Profile

182 |

Uploaded on January 20, 2023

183 |
184 |
185 | 186 | 187 |
188 |
189 |
190 |
191 |
192 |
193 |

Oral Microbiome Profile

194 |

Uploaded on February 5, 2023

195 |
196 |
197 | 198 | 199 |
200 |
201 |
202 |
203 |
204 |
205 |

Skin Microbiome Profile

206 |

Uploaded on March 12, 2023

207 |
208 |
209 | 210 | 211 |
212 |
213 |
214 |
215 | 216 |
217 |

Connect New Data

218 |
219 | 224 | 229 | 234 |
235 |
236 |
237 | )} 238 | 239 | {/* Wallet & Payments Tab */} 240 | {activeTab === 'wallet' && ( 241 |
242 |

Wallet & Payments

243 |
244 |
245 |
246 |

Your BioCoin Balance

247 |

Available for withdrawal or research access

248 |
249 |
{userData.totalEarnings} BIO
250 |
251 |
252 | 255 | 258 |
259 |
260 | 261 |

Transaction History

262 |
263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 |
DateDescriptionAmount
Mar 15, 2023Data access payment from Microbiome Research Institute+45.25 BIO
Feb 28, 2023Data access payment from BioGenetics Labs+32.50 BIO
Feb 10, 2023Withdrawal to external wallet-50.00 BIO
Jan 22, 2023Data access payment from Global Health Initiative+48.00 BIO
294 |
295 |
296 | )} 297 | 298 | {/* Privacy & Consent Tab */} 299 | {activeTab === 'privacy' && ( 300 |
301 |

Privacy & Consent Settings

302 |

303 | Control how your biological data is used and who can access it. Your privacy is our priority. 304 |

305 | 306 |
307 |
308 |
309 |
310 | 315 |
316 |
317 | 320 |

321 | Your data can be used for scientific research in an anonymized form without requiring 322 | explicit approval for each use. 323 |

324 |
325 |
326 |
327 | 328 |
329 |
330 |
331 | 336 |
337 |
338 | 341 |

342 | Your data can be used by commercial entities such as pharmaceutical companies for 343 | product development. 344 |

345 |
346 |
347 |
348 | 349 |
350 |
351 |
352 | 357 |
358 |
359 | 362 |

363 | You will be notified and must approve each request to access your data before it is granted. 364 |

365 |
366 |
367 |
368 | 369 |
370 |
371 |
372 | 377 |
378 |
379 | 382 |

383 | Receive personalized health insights and recommendations based on your microbiome data 384 | and research findings. 385 |

386 |
387 |
388 |
389 |
390 | 391 |
392 | 398 |
399 |
400 | )} 401 |
402 |
403 |
404 |
405 | ); 406 | }; 407 | 408 | export default Profile; -------------------------------------------------------------------------------- /frontend/src/pages/Research.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | const Research = () => { 4 | const [activeTab, setActiveTab] = useState('browse'); 5 | 6 | // Mock data for demonstration 7 | const availableData = [ 8 | { 9 | id: 1, 10 | title: 'Gut Microbiome Profile - 35M European', 11 | description: 'Complete gut microbiome profile from a 35-year-old male of European descent with no known health conditions.', 12 | price: 25, 13 | uniqueness: 'Medium', 14 | dataPoints: 1250, 15 | }, 16 | { 17 | id: 2, 18 | title: 'Microbiome Time Series - 28F Asian', 19 | description: 'Six-month time series of gut microbiome samples from a 28-year-old female of Asian descent following a plant-based diet.', 20 | price: 45, 21 | uniqueness: 'High', 22 | dataPoints: 7500, 23 | }, 24 | { 25 | id: 3, 26 | title: 'Oral Microbiome Profile - 42M African', 27 | description: 'Detailed oral microbiome profile from a 42-year-old male of African descent with periodontal disease history.', 28 | price: 30, 29 | uniqueness: 'High', 30 | dataPoints: 980, 31 | }, 32 | { 33 | id: 4, 34 | title: 'Skin Microbiome - 31F Hispanic', 35 | description: 'Skin microbiome samples from five body sites of a 31-year-old female of Hispanic descent with eczema.', 36 | price: 35, 37 | uniqueness: 'Medium', 38 | dataPoints: 2100, 39 | }, 40 | ]; 41 | 42 | const requestAccess = (dataId) => { 43 | // In a real implementation, this would open a modal to request access 44 | alert(`Requesting access to data ID: ${dataId}`); 45 | }; 46 | 47 | return ( 48 |
49 |

Research Data Marketplace

50 | 51 | {/* Tabs */} 52 |
53 | 63 | 73 | 83 |
84 | 85 | {/* Browse Data Tab */} 86 | {activeTab === 'browse' && ( 87 |
88 |
89 |

Available Biological Data

90 |

91 | Browse and access anonymized microbiome data for your research. All data is ethically sourced 92 | with explicit consent from providers. 93 |

94 |
95 | 96 | {/* Filters */} 97 |
98 |
99 |
100 | 101 | 107 |
108 |
109 | 110 | 116 |
117 |
118 | 119 | 127 |
128 |
129 | 130 | 136 |
137 |
138 |
139 | 140 | {/* Data Cards */} 141 |
142 | {availableData.map((data) => ( 143 |
144 |

{data.title}

145 |

{data.description}

146 |
147 | 148 | {data.dataPoints} data points 149 | 150 | 151 | Uniqueness: {data.uniqueness} 152 | 153 |
154 |
155 | {data.price} BIO 156 | 162 |
163 |
164 | ))} 165 |
166 |
167 | )} 168 | 169 | {/* My Requests Tab */} 170 | {activeTab === 'requests' && ( 171 |
172 |

My Data Access Requests

173 |

174 | Track the status of your data access requests and manage your research projects. 175 |

176 |
177 |

You haven't made any data access requests yet.

178 | 184 |
185 |
186 | )} 187 | 188 | {/* Publish Research Tab */} 189 | {activeTab === 'publish' && ( 190 |
191 |

Publish Your Research

192 |

193 | Share your research findings with the BioCoin community and gain recognition for your work. 194 |

195 |
196 |
197 |
198 | 199 | 204 |
205 |
206 | 207 | 212 |
213 |
214 | 215 |
216 |

Drag and drop your research paper here

217 |

Supported formats: PDF, DOCX (Max 10MB)

218 | 224 |
225 |
226 |
227 | 228 | 233 |
234 |
235 | 241 |
242 |
243 |
244 |
245 | )} 246 |
247 | ); 248 | }; 249 | 250 | export default Research; -------------------------------------------------------------------------------- /frontend/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = (onPerfEntry) => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; -------------------------------------------------------------------------------- /frontend/src/utils/accessibilityHelpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Accessibility helper functions for better WCAG compliance 3 | */ 4 | 5 | /** 6 | * Add proper keyboard navigation to a custom component 7 | * @param {Event} e - Keyboard event 8 | * @param {Function} callback - Function to call on Enter or Space key 9 | */ 10 | export const handleKeyboardEvent = (e, callback) => { 11 | // Trigger on Enter or Space key 12 | if (e.key === 'Enter' || e.key === ' ') { 13 | e.preventDefault(); 14 | callback(); 15 | } 16 | }; 17 | 18 | /** 19 | * Format numbers for screen readers 20 | * @param {Number} value - Number to format 21 | * @param {String} unit - Optional unit (e.g., 'dollars', 'percent') 22 | * @returns {String} Screen reader friendly number 23 | */ 24 | export const formatNumberForScreenReaders = (value, unit = '') => { 25 | if (typeof value !== 'number') return ''; 26 | 27 | const formattedNumber = value.toLocaleString(); 28 | return unit ? `${formattedNumber} ${unit}` : formattedNumber; 29 | }; 30 | 31 | /** 32 | * Create a unique ID for aria-labelledby or other accessibility attributes 33 | * @param {String} prefix - Prefix for the ID 34 | * @returns {String} Unique ID 35 | */ 36 | export const generateAccessibleId = (prefix = 'a11y') => { 37 | return `${prefix}-${Math.random().toString(36).substring(2, 9)}`; 38 | }; 39 | 40 | /** 41 | * Announce a message to screen readers using aria-live region 42 | * @param {String} message - Message to announce 43 | * @param {String} priority - 'polite' or 'assertive' 44 | */ 45 | export const announceToScreenReader = (message, priority = 'polite') => { 46 | const announcer = document.getElementById('sr-announcer'); 47 | 48 | if (!announcer) { 49 | // Create announcer if it doesn't exist 50 | const newAnnouncer = document.createElement('div'); 51 | newAnnouncer.id = 'sr-announcer'; 52 | newAnnouncer.className = 'sr-only'; 53 | newAnnouncer.setAttribute('aria-live', priority); 54 | newAnnouncer.setAttribute('aria-atomic', 'true'); 55 | document.body.appendChild(newAnnouncer); 56 | 57 | // Give browser time to register the element before using it 58 | setTimeout(() => { 59 | newAnnouncer.textContent = message; 60 | }, 50); 61 | } else { 62 | // Use existing announcer 63 | announcer.textContent = ''; 64 | // Timeout required to ensure screen readers register the change 65 | setTimeout(() => { 66 | announcer.textContent = message; 67 | }, 50); 68 | } 69 | }; 70 | 71 | /** 72 | * Trap focus within a modal or other container for keyboard navigation 73 | * @param {HTMLElement} container - The container to trap focus within 74 | * @returns {Function} Function to remove the trap 75 | */ 76 | export const trapFocus = (container) => { 77 | if (!container) return () => {}; 78 | 79 | // Find all focusable elements 80 | const focusableElements = container.querySelectorAll( 81 | 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' 82 | ); 83 | 84 | if (focusableElements.length === 0) return () => {}; 85 | 86 | const firstElement = focusableElements[0]; 87 | const lastElement = focusableElements[focusableElements.length - 1]; 88 | 89 | // Focus the first element initially 90 | firstElement.focus(); 91 | 92 | // Handle keydown events 93 | const handleKeyDown = (e) => { 94 | if (e.key !== 'Tab') return; 95 | 96 | // Shift + Tab 97 | if (e.shiftKey) { 98 | if (document.activeElement === firstElement) { 99 | e.preventDefault(); 100 | lastElement.focus(); 101 | } 102 | } 103 | // Tab 104 | else { 105 | if (document.activeElement === lastElement) { 106 | e.preventDefault(); 107 | firstElement.focus(); 108 | } 109 | } 110 | }; 111 | 112 | // Add event listener 113 | document.addEventListener('keydown', handleKeyDown); 114 | 115 | // Return function to remove the trap 116 | return () => { 117 | document.removeEventListener('keydown', handleKeyDown); 118 | }; 119 | }; -------------------------------------------------------------------------------- /frontend/src/utils/exportFormats.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility functions for exporting data in different formats 3 | */ 4 | 5 | /** 6 | * Export data as CSV 7 | * @param {Array} data - Array of objects to export 8 | * @param {String} filename - Name for the downloaded file 9 | */ 10 | export const exportAsCSV = (data, filename = 'export.csv') => { 11 | if (!data || !data.length) return; 12 | 13 | // Get headers from the first object's keys 14 | const headers = Object.keys(data[0]); 15 | 16 | // Create CSV rows 17 | const csvRows = [ 18 | headers.join(','), // Header row 19 | ...data.map(row => 20 | headers.map(header => { 21 | // Handle values with commas, quotes, or special characters 22 | const cell = row[header] !== undefined ? String(row[header]) : ''; 23 | if (cell.includes(',') || cell.includes('"') || cell.includes('\n') || cell.includes('\r')) { 24 | // Escape quotes by doubling them and wrap in quotes 25 | return `"${cell.replace(/"/g, '""')}"`; 26 | } 27 | return cell; 28 | }).join(',') 29 | ) 30 | ]; 31 | 32 | // Create blob with UTF-8 BOM for Excel compatibility 33 | const BOM = '\uFEFF'; // UTF-8 BOM 34 | const csvContent = BOM + csvRows.join('\r\n'); // Use Windows line endings for better compatibility 35 | const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); 36 | 37 | downloadFile(blob, filename); 38 | }; 39 | 40 | /** 41 | * Export data as JSON 42 | * @param {Array|Object} data - Data to export 43 | * @param {String} filename - Name for the downloaded file 44 | */ 45 | export const exportAsJSON = (data, filename = 'export.json') => { 46 | if (!data) return; 47 | 48 | const jsonContent = JSON.stringify(data, null, 2); 49 | const blob = new Blob([jsonContent], { type: 'application/json' }); 50 | 51 | downloadFile(blob, filename); 52 | }; 53 | 54 | /** 55 | * Export data as plain text 56 | * @param {String} text - Text content to export 57 | * @param {String} filename - Name for the downloaded file 58 | */ 59 | export const exportAsText = (text, filename = 'export.txt') => { 60 | if (!text) return; 61 | 62 | const blob = new Blob([text], { type: 'text/plain' }); 63 | downloadFile(blob, filename); 64 | }; 65 | 66 | /** 67 | * Helper function to download a file 68 | * @param {Blob} blob - Blob to download 69 | * @param {String} filename - Name for the downloaded file 70 | */ 71 | const downloadFile = (blob, filename) => { 72 | const url = URL.createObjectURL(blob); 73 | const link = document.createElement('a'); 74 | 75 | link.href = url; 76 | link.download = filename; 77 | link.style.display = 'none'; 78 | 79 | document.body.appendChild(link); 80 | link.click(); 81 | 82 | // Clean up 83 | setTimeout(() => { 84 | document.body.removeChild(link); 85 | URL.revokeObjectURL(url); 86 | }, 100); 87 | }; -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./src/**/*.{js,jsx,ts,tsx}", 5 | ], 6 | darkMode: 'class', 7 | theme: { 8 | extend: { 9 | colors: { 10 | 'biocoin-green': '#00cc00', 11 | }, 12 | }, 13 | }, 14 | plugins: [], 15 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "biocoin", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "biocoin", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "concurrently": "^8.2.2" 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "biocoin", 3 | "version": "1.02.1", 4 | "description": "BioCoin platform for biomedical research incentivization and data sharing", 5 | "scripts": { 6 | "start": "concurrently \"npm run start:frontend\" \"npm run start:backend\"", 7 | "start:frontend": "cd frontend && npm start", 8 | "start:backend": "cd backend && npm start", 9 | "install:all": "npm install && cd frontend && npm install && cd ../backend && npm install", 10 | "test": "concurrently \"cd frontend && npm test\" \"cd backend && npm test\"" 11 | }, 12 | "author": "Charles Ramoose ", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "concurrently": "^8.2.2" 16 | } 17 | } --------------------------------------------------------------------------------