├── .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 |

5 |
6 | [](https://opensource.org/licenses/MIT)
7 | [](https://biocoin.vip)
8 | [](https://x.com/BioCoindotvip)
9 | [](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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
29 | );
30 | case TOAST_TYPES.ERROR:
31 | return (
32 |
35 | );
36 | case TOAST_TYPES.WARNING:
37 | return (
38 |
41 | );
42 | case TOAST_TYPES.INFO:
43 | default:
44 | return (
45 |
48 | );
49 | }
50 | };
51 |
52 | return (
53 |
57 |
58 |
59 |
60 | {getIcon()}
61 |
62 |
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 |
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 | Date |
126 | Amount |
127 | Researcher |
128 |
129 |
130 |
131 | {userData.recentTransactions.map((tx) => (
132 |
133 | {tx.date} |
134 | ${tx.amount} |
135 | {tx.researcher} |
136 |
137 | ))}
138 |
139 |
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 |
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 | Date |
267 | Description |
268 | Amount |
269 |
270 |
271 |
272 |
273 | Mar 15, 2023 |
274 | Data access payment from Microbiome Research Institute |
275 | +45.25 BIO |
276 |
277 |
278 | Feb 28, 2023 |
279 | Data access payment from BioGenetics Labs |
280 | +32.50 BIO |
281 |
282 |
283 | Feb 10, 2023 |
284 | Withdrawal to external wallet |
285 | -50.00 BIO |
286 |
287 |
288 | Jan 22, 2023 |
289 | Data access payment from Global Health Initiative |
290 | +48.00 BIO |
291 |
292 |
293 |
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 |
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 | }
--------------------------------------------------------------------------------