diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..e0ac51f --- /dev/null +++ b/docs/API.md @@ -0,0 +1,567 @@ +# API Reference + +## Overview + +The StellarInsure backend provides a REST API for querying indexed policy data, managing user accounts, and receiving webhook notifications. The API is built with FastAPI and includes automatic OpenAPI documentation. + +**Base URL:** `http://localhost:8000` (development) + +**API Documentation:** `http://localhost:8000/docs` (Swagger UI) + +## Authentication + +### JWT Authentication + +Most endpoints require JWT authentication. Include the token in the Authorization header: + +``` +Authorization: Bearer +``` + +### Authentication Flow + +``` +1. User connects Freighter Wallet + ↓ +2. Frontend requests challenge message from backend + ↓ +3. User signs challenge with wallet + ↓ +4. Frontend sends signed message to /auth/login + ↓ +5. Backend verifies signature and returns JWT + ↓ +6. Frontend includes JWT in subsequent requests +``` + +## Endpoints + +### Authentication + +#### POST /auth/challenge + +Generate a challenge message for wallet signature. + +**Request Body:** +```json +{ + "public_key": "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +} +``` + +**Response:** +```json +{ + "challenge": "Sign this message to authenticate: 1234567890", + "expires_at": "2026-04-27T12:00:00Z" +} +``` + +#### POST /auth/login + +Authenticate with signed challenge. + +**Request Body:** +```json +{ + "public_key": "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "signature": "base64_encoded_signature", + "challenge": "Sign this message to authenticate: 1234567890" +} +``` + +**Response:** +```json +{ + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "token_type": "bearer", + "expires_in": 3600 +} +``` + +**Error Codes:** +- `401` - Invalid signature or expired challenge +- `400` - Missing required fields + +### Policies + +#### GET /policies + +List all policies for the authenticated user. + +**Headers:** +``` +Authorization: Bearer +``` + +**Query Parameters:** +- `status` (optional): Filter by status (active, expired, cancelled, claim_pending) +- `policy_type` (optional): Filter by type (weather, flight, smart_contract, health, asset) +- `limit` (optional): Number of results (default: 50, max: 100) +- `offset` (optional): Pagination offset (default: 0) + +**Response:** +```json +{ + "policies": [ + { + "id": 1, + "policyholder": "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "beneficiary": "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "policy_type": "weather", + "coverage_amount": "10000000000", + "premium": "500000000", + "start_time": 1714204800, + "end_time": 1716796800, + "trigger_condition": "rainfall < 50mm", + "status": "active", + "claim_amount": "0", + "created_at": "2026-04-27T10:00:00Z", + "updated_at": "2026-04-27T10:00:00Z" + } + ], + "total": 1, + "limit": 50, + "offset": 0 +} +``` + +#### GET /policies/{policy_id} + +Get details for a specific policy. + +**Headers:** +``` +Authorization: Bearer +``` + +**Response:** +```json +{ + "id": 1, + "policyholder": "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "beneficiary": "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "policy_type": "weather", + "coverage_amount": "10000000000", + "premium": "500000000", + "start_time": 1714204800, + "end_time": 1716796800, + "trigger_condition": "rainfall < 50mm", + "status": "active", + "claim_amount": "0", + "created_at": "2026-04-27T10:00:00Z", + "updated_at": "2026-04-27T10:00:00Z" +} +``` + +**Error Codes:** +- `404` - Policy not found +- `403` - Not authorized to view this policy + +#### POST /policies/calculate-premium + +Calculate premium for a prospective policy. + +**Request Body:** +```json +{ + "policy_type": "weather", + "coverage_amount": "10000000000", + "duration_seconds": 2592000 +} +``` + +**Response:** +```json +{ + "premium": "500000000", + "policy_type": "weather", + "coverage_amount": "10000000000", + "duration_seconds": 2592000, + "annual_rate_bps": 350 +} +``` + +### Claims + +#### GET /claims + +List all claims for the authenticated user. + +**Headers:** +``` +Authorization: Bearer +``` + +**Query Parameters:** +- `status` (optional): Filter by approval status (pending, approved, rejected) +- `limit` (optional): Number of results (default: 50) +- `offset` (optional): Pagination offset (default: 0) + +**Response:** +```json +{ + "claims": [ + { + "policy_id": 1, + "claim_amount": "5000000000", + "proof": "Weather station data showing rainfall < 50mm", + "timestamp": 1714291200, + "approved": false, + "status": "pending", + "created_at": "2026-04-28T10:00:00Z" + } + ], + "total": 1, + "limit": 50, + "offset": 0 +} +``` + +#### GET /claims/{policy_id} + +Get claim details for a specific policy. + +**Headers:** +``` +Authorization: Bearer +``` + +**Response:** +```json +{ + "policy_id": 1, + "claim_amount": "5000000000", + "proof": "Weather station data showing rainfall < 50mm", + "timestamp": 1714291200, + "approved": false, + "status": "pending", + "votes": { + "approvals": 2, + "rejections": 0, + "threshold": 3 + }, + "created_at": "2026-04-28T10:00:00Z" +} +``` + +### Risk Pool + +#### GET /pool/stats + +Get risk pool statistics. + +**Response:** +```json +{ + "total_liquidity": "100000000000000", + "total_yield_distributed": "5000000000000", + "provider_count": 42, + "reserve_ratio": 2000, + "available_liquidity": "80000000000000" +} +``` + +#### GET /pool/providers/{address} + +Get provider position details. + +**Response:** +```json +{ + "provider": "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "contribution": "10000000000000", + "accrued_yield": "500000000000", + "share_percentage": "10.00" +} +``` + +### Treasury + +#### GET /treasury/stats + +Get overall treasury statistics. + +**Response:** +```json +{ + "total_premium_collected": "50000000000000", + "total_payouts_distributed": "30000000000000", + "current_balance": "20000000000000", + "active_policies": 156, + "total_policies": 200 +} +``` + +### Webhooks + +#### POST /webhooks + +Register a webhook endpoint for event notifications. + +**Headers:** +``` +Authorization: Bearer +``` + +**Request Body:** +```json +{ + "url": "https://your-domain.com/webhook", + "events": ["policy.created", "claim.submitted", "claim.processed"], + "secret": "your_webhook_secret" +} +``` + +**Response:** +```json +{ + "id": "webhook_123", + "url": "https://your-domain.com/webhook", + "events": ["policy.created", "claim.submitted", "claim.processed"], + "created_at": "2026-04-27T10:00:00Z", + "active": true +} +``` + +#### GET /webhooks + +List registered webhooks for the authenticated user. + +**Headers:** +``` +Authorization: Bearer +``` + +**Response:** +```json +{ + "webhooks": [ + { + "id": "webhook_123", + "url": "https://your-domain.com/webhook", + "events": ["policy.created", "claim.submitted"], + "active": true, + "created_at": "2026-04-27T10:00:00Z" + } + ] +} +``` + +#### DELETE /webhooks/{webhook_id} + +Delete a webhook. + +**Headers:** +``` +Authorization: Bearer +``` + +**Response:** +```json +{ + "message": "Webhook deleted successfully" +} +``` + +### Webhook Event Payloads + +When events occur, the backend sends POST requests to registered webhook URLs. + +**Headers:** +``` +X-Webhook-Signature: sha256= +Content-Type: application/json +``` + +**Policy Created Event:** +```json +{ + "event": "policy.created", + "timestamp": "2026-04-27T10:00:00Z", + "data": { + "policy_id": 1, + "policyholder": "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "policy_type": "weather", + "coverage_amount": "10000000000", + "premium": "500000000" + } +} +``` + +**Claim Submitted Event:** +```json +{ + "event": "claim.submitted", + "timestamp": "2026-04-28T10:00:00Z", + "data": { + "policy_id": 1, + "claim_amount": "5000000000", + "proof": "Weather station data" + } +} +``` + +**Claim Processed Event:** +```json +{ + "event": "claim.processed", + "timestamp": "2026-04-28T12:00:00Z", + "data": { + "policy_id": 1, + "approved": true, + "payout_amount": "5000000000" + } +} +``` + +## Error Codes + +The API uses standard HTTP status codes: + +| Code | Meaning | +|------|---------| +| 200 | Success | +| 201 | Created | +| 400 | Bad Request - Invalid parameters | +| 401 | Unauthorized - Missing or invalid token | +| 403 | Forbidden - Insufficient permissions | +| 404 | Not Found - Resource doesn't exist | +| 429 | Too Many Requests - Rate limit exceeded | +| 500 | Internal Server Error | +| 503 | Service Unavailable | + +### Error Response Format + +```json +{ + "error": { + "code": "INVALID_PREMIUM", + "message": "Premium does not match calculated amount", + "details": { + "provided": "400000000", + "required": "500000000" + } + } +} +``` + +## Rate Limiting + +The API implements rate limiting to prevent abuse: + +- **Anonymous requests:** 100 requests per hour +- **Authenticated requests:** 1000 requests per hour +- **Webhook deliveries:** 10 retries with exponential backoff + +Rate limit headers are included in responses: + +``` +X-RateLimit-Limit: 1000 +X-RateLimit-Remaining: 999 +X-RateLimit-Reset: 1714208400 +``` + +## Pagination + +List endpoints support pagination using `limit` and `offset` parameters: + +``` +GET /policies?limit=20&offset=40 +``` + +Response includes pagination metadata: + +```json +{ + "data": [...], + "total": 156, + "limit": 20, + "offset": 40, + "has_more": true +} +``` + +## CORS + +The API supports CORS for browser-based applications. Allowed origins are configurable via environment variables. + +**Development:** All origins allowed +**Production:** Whitelist specific domains + +## WebSocket Support + +Real-time updates are available via WebSocket connection: + +```javascript +const ws = new WebSocket('ws://localhost:8000/ws'); + +ws.onmessage = (event) => { + const data = JSON.parse(event.data); + console.log('Event:', data.event, data.data); +}; +``` + +**Supported Events:** +- `policy.created` +- `policy.updated` +- `claim.submitted` +- `claim.processed` +- `pool.deposit` +- `pool.withdraw` + +## SDK Examples + +### JavaScript/TypeScript + +```typescript +import { StellarInsureAPI } from '@stellarinsure/sdk'; + +const api = new StellarInsureAPI({ + baseURL: 'http://localhost:8000', + token: 'your_jwt_token' +}); + +// List policies +const policies = await api.policies.list({ status: 'active' }); + +// Calculate premium +const premium = await api.policies.calculatePremium({ + policy_type: 'weather', + coverage_amount: '10000000000', + duration_seconds: 2592000 +}); + +// Get pool stats +const stats = await api.pool.getStats(); +``` + +### Python + +```python +from stellarinsure import StellarInsureClient + +client = StellarInsureClient( + base_url='http://localhost:8000', + token='your_jwt_token' +) + +# List policies +policies = client.policies.list(status='active') + +# Calculate premium +premium = client.policies.calculate_premium( + policy_type='weather', + coverage_amount=10000000000, + duration_seconds=2592000 +) + +# Get pool stats +stats = client.pool.get_stats() +``` + +## Testing + +Use the interactive API documentation at `/docs` to test endpoints directly in your browser. + +For automated testing, use the provided Postman collection or OpenAPI spec to generate client code. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..0f771b8 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,334 @@ +# StellarInsure Architecture + +## System Overview + +StellarInsure is a parametric insurance protocol built on Stellar Soroban that enables automated, trustless insurance policies with instant payouts based on verifiable events. The system consists of smart contracts, a backend API, a frontend application, and oracle integrations. + +## Component Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ FRONTEND LAYER │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ Next.js 14 Application (TypeScript + Tailwind CSS) │ │ +│ │ - Policy Management UI │ │ +│ │ - Claim Submission Interface │ │ +│ │ - Risk Pool Dashboard │ │ +│ │ - Freighter Wallet Integration │ │ +│ └──────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ BACKEND LAYER │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ FastAPI REST API (Python) │ │ +│ │ - Policy indexing and caching │ │ +│ │ - Event processing │ │ +│ │ - Oracle data relay │ │ +│ │ - Webhook notifications │ │ +│ └──────────────────────────────────────────────────────────┘ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ PostgreSQL Database │ │ +│ │ - Policy records │ │ +│ │ - Claim history │ │ +│ │ - User accounts │ │ +│ └──────────────────────────────────────────────────────────┘ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ Redis Cache │ │ +│ │ - Rate limiting │ │ +│ │ - Session management │ │ +│ │ - Query result caching │ │ +│ └──────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ BLOCKCHAIN LAYER │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ Stellar Soroban Smart Contracts (Rust) │ │ +│ │ │ │ +│ │ ┌────────────────────────────────────────────────────┐ │ │ +│ │ │ StellarInsure Contract │ │ │ +│ │ │ - create_policy() │ │ │ +│ │ │ - pay_premium() │ │ │ +│ │ │ - submit_claim() │ │ │ +│ │ │ - process_claim() / vote_claim() │ │ │ +│ │ │ - cancel_policy() │ │ │ +│ │ │ - renew_policy() │ │ │ +│ │ │ - increase_coverage() │ │ │ +│ │ │ - extend_duration() │ │ │ +│ │ └────────────────────────────────────────────────────┘ │ │ +│ │ │ │ +│ │ ┌────────────────────────────────────────────────────┐ │ │ +│ │ │ RiskPool Contract │ │ │ +│ │ │ - add_liquidity() │ │ │ +│ │ │ - withdraw_liquidity() │ │ │ +│ │ │ - distribute_yield() │ │ │ +│ │ │ - claim_yield() │ │ │ +│ │ │ - fund_payout() │ │ │ +│ │ └────────────────────────────────────────────────────┘ │ │ +│ └──────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ ORACLE LAYER │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ Oracle Network (Future Integration) │ │ +│ │ - Weather data feeds │ │ +│ │ - Flight status APIs │ │ +│ │ - Smart contract monitoring │ │ +│ │ - Asset price feeds │ │ +│ └──────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## Data Flow + +### Policy Creation Flow + +``` +1. User connects Freighter Wallet + │ + ▼ +2. Frontend calls calculate_premium() (read-only) + │ + ▼ +3. User reviews premium and confirms + │ + ▼ +4. Frontend calls create_policy() with calculated premium + │ + ▼ +5. Smart contract verifies premium matches calculation + │ + ▼ +6. Policy created, event emitted + │ + ▼ +7. Backend indexes event, stores in PostgreSQL + │ + ▼ +8. Frontend displays policy details +``` + +### Premium Payment Flow + +``` +1. User initiates premium payment + │ + ▼ +2. Frontend calls pay_premium() with policy_id + │ + ▼ +3. Smart contract transfers tokens from user to risk pool + │ + ▼ +4. Risk pool distributes yield to liquidity providers + │ + ▼ +5. Premium paid event emitted + │ + ▼ +6. Backend updates policy status +``` + +### Claim Submission and Processing Flow + +``` +1. User submits claim with proof + │ + ▼ +2. Frontend calls submit_claim() + │ + ▼ +3. Smart contract validates claim amount and policy status + │ + ▼ +4. Claim status set to pending + │ + ▼ +5. Multi-sig admins vote on claim via vote_claim() + │ + ▼ +6. When threshold reached, automatic payout triggered + │ + ▼ +7. Risk pool transfers funds to policyholder + │ + ▼ +8. Payout event emitted + │ + ▼ +9. Backend updates claim status and sends webhook notification +``` + +## Smart Contract Design + +### StellarInsure Contract + +The main insurance protocol contract manages policy lifecycle and claim processing. + +**Key Features:** +- Policy creation with premium verification +- Multi-signature admin voting for claim approval +- Policy modification (coverage increase, duration extension) +- Policy renewal with grace period +- Emergency pause mechanism +- Configurable policy count limits + +**Storage:** +- Policies indexed by policy_id +- Claims indexed by policy_id +- Admin list and voting threshold +- Policy counter with configurable maximum +- Premium token address +- Risk pool address + +### RiskPool Contract + +Manages liquidity provider funds and yield distribution. + +**Key Features:** +- Liquidity deposit and withdrawal +- Yield distribution to providers +- Payout funding with reserve protection +- Configurable reserve ratio for pending claims + +**Storage:** +- Provider positions (contribution + accrued yield) +- Total liquidity +- Total yield distributed +- Reserve ratio (default 20%) + +## Security Model + +### Access Control + +1. **Admin Functions:** + - Multi-signature voting for claim approval + - Configurable approval threshold + - Admin addition/removal requires existing admin + +2. **User Functions:** + - Policy operations require policyholder authentication + - Premium payments verified against calculated amounts + - Claim submissions validated for policy status + +3. **Emergency Controls:** + - Pause/unpause mechanism for critical issues + - Only admin can pause contract + - Paused state blocks policy creation, premium payment, claims + +### Economic Security + +1. **Premium Verification:** + - All premiums calculated using actuarial formula + - Create policy enforces premium matches calculation + - Prevents underpayment attacks + +2. **Risk Pool Protection:** + - Reserve ratio prevents liquidity drain + - Withdrawals blocked if they would impair pending claims + - Configurable reserve percentage (default 20%) + +3. **Policy Limits:** + - Maximum policy count prevents storage exhaustion + - Admin-configurable limit (default 1,000,000) + - Clear error when limit reached + +## Deployment Architecture + +### Development Environment + +``` +docker-compose.dev.yml +├── frontend (Next.js dev server) +├── backend (FastAPI with hot reload) +├── postgres (development database) +└── redis (cache and rate limiting) +``` + +### Staging Environment + +Kubernetes deployment with: +- Frontend and backend pods with HPA +- PostgreSQL StatefulSet +- Redis StatefulSet +- Ingress with TLS +- ConfigMaps for environment variables + +### Production Environment + +Same as staging with: +- Increased replica counts +- Production-grade resource limits +- Enhanced monitoring and logging +- Backup and disaster recovery + +## Technology Stack + +| Component | Technology | Purpose | +|-----------|-----------|---------| +| Smart Contracts | Rust + Soroban SDK | On-chain logic | +| Backend API | Python + FastAPI | Event indexing, caching | +| Database | PostgreSQL | Persistent storage | +| Cache | Redis | Rate limiting, sessions | +| Frontend | Next.js 14 + TypeScript | User interface | +| Styling | Tailwind CSS | UI components | +| Wallet | Freighter | Stellar wallet integration | +| Infrastructure | Docker + Kubernetes | Deployment | + +## Event System + +All state-changing operations emit Soroban events for off-chain indexing: + +- `("policy", "created")` - New policy created +- `("policy", "premium")` - Premium paid +- `("claim", "submit")` - Claim submitted +- `("claim", "process")` - Claim approved/rejected +- `("claim", "vote")` - Admin vote cast +- `("policy", "cancel")` - Policy cancelled +- `("policy", "renewed")` - Policy renewed +- `("pool", "deposit")` - Liquidity added +- `("pool", "withdraw")` - Liquidity withdrawn +- `("pool", "yield")` - Yield distributed + +The backend listens for these events and updates the PostgreSQL database accordingly. + +## Scalability Considerations + +1. **Smart Contract:** + - Policy counter with configurable maximum + - Efficient storage using persistent and instance storage + - Event-driven architecture for off-chain indexing + +2. **Backend:** + - Redis caching for frequently accessed data + - Connection pooling for database efficiency + - Rate limiting to prevent abuse + +3. **Frontend:** + - Server-side rendering for performance + - Static generation for public pages + - Client-side caching of blockchain data + +## Future Enhancements + +1. **Oracle Integration:** + - Chainlink price feeds + - Weather data APIs + - Flight status verification + - Smart contract exploit detection + +2. **Governance:** + - DAO-based protocol governance + - Community voting on parameters + - Treasury management + +3. **Advanced Features:** + - Cross-chain insurance + - Reinsurance pools + - Automated market makers for premiums + - NFT-based policy certificates diff --git a/docs/FRONTEND.md b/docs/FRONTEND.md new file mode 100644 index 0000000..d92e8b6 --- /dev/null +++ b/docs/FRONTEND.md @@ -0,0 +1,664 @@ +# Frontend Architecture Guide + +## Overview + +The StellarInsure frontend is built with Next.js 14, TypeScript, and Tailwind CSS. It provides a modern, responsive interface for creating policies, submitting claims, and managing risk pool participation. + +## Technology Stack + +| Technology | Version | Purpose | +|------------|---------|---------| +| Next.js | 14.x | React framework with SSR/SSG | +| TypeScript | 5.x | Type-safe development | +| Tailwind CSS | 3.x | Utility-first styling | +| Freighter | Latest | Stellar wallet integration | +| Stellar SDK | Latest | Blockchain interactions | +| React Query | 5.x | Data fetching and caching | +| Zustand | 4.x | State management | +| Playwright | Latest | E2E testing | +| Storybook | 7.x | Component development | + +## Project Structure + +``` +frontend/ +├── src/ +│ ├── app/ # Next.js 14 app directory +│ │ ├── layout.tsx # Root layout +│ │ ├── page.tsx # Home page +│ │ ├── globals.css # Global styles +│ │ ├── tokens.css # Design tokens +│ │ ├── create/ # Policy creation +│ │ │ ├── page.tsx +│ │ │ └── create-page-client.tsx +│ │ ├── policies/ # Policy management +│ │ │ ├── page.tsx +│ │ │ ├── policies-list-page-client.tsx +│ │ │ └── [policyId]/ +│ │ │ ├── page.tsx +│ │ │ └── policy-detail-page-client.tsx +│ │ ├── claims/ # Claim submission +│ │ │ └── [claimId]/ +│ │ │ ├── page.tsx +│ │ │ └── claim-detail-page-client.tsx +│ │ ├── history/ # Transaction history +│ │ │ ├── page.tsx +│ │ │ └── history-page-client.tsx +│ │ ├── treasury/ # Treasury dashboard +│ │ │ ├── page.tsx +│ │ │ └── treasury-page-client.tsx +│ │ ├── settings/ # User settings +│ │ │ ├── page.tsx +│ │ │ └── settings-page-client.tsx +│ │ └── legal/ # Legal pages +│ │ ├── layout.tsx +│ │ ├── privacy/ +│ │ └── terms/ +│ ├── components/ # Reusable components +│ │ ├── ui/ # Base UI components +│ │ ├── policy/ # Policy-specific components +│ │ ├── claim/ # Claim-specific components +│ │ ├── pool/ # Risk pool components +│ │ └── wallet/ # Wallet integration +│ ├── lib/ # Utilities and helpers +│ │ ├── stellar.ts # Stellar SDK wrapper +│ │ ├── contract.ts # Smart contract interactions +│ │ ├── api.ts # Backend API client +│ │ └── utils.ts # Helper functions +│ ├── hooks/ # Custom React hooks +│ │ ├── useWallet.ts # Wallet connection +│ │ ├── usePolicies.ts # Policy data fetching +│ │ ├── useClaims.ts # Claim data fetching +│ │ └── usePool.ts # Pool data fetching +│ ├── stores/ # Zustand state stores +│ │ ├── walletStore.ts # Wallet state +│ │ ├── policyStore.ts # Policy state +│ │ └── uiStore.ts # UI state +│ └── types/ # TypeScript types +│ ├── policy.ts +│ ├── claim.ts +│ └── pool.ts +├── e2e/ # Playwright E2E tests +│ ├── home.spec.ts +│ ├── navigation.spec.ts +│ ├── policies.spec.ts +│ └── visual-regression.spec.ts +├── .storybook/ # Storybook configuration +│ ├── main.ts +│ └── preview.ts +├── public/ # Static assets +├── next.config.js # Next.js configuration +├── tailwind.config.js # Tailwind configuration +├── playwright.config.ts # Playwright configuration +└── package.json +``` + +## Component Architecture + +### Page Components + +Pages are split into server and client components following Next.js 14 patterns: + +**Server Component (page.tsx):** +```typescript +// app/policies/page.tsx +import { Metadata } from 'next'; +import PoliciesListPageClient from './policies-list-page-client'; + +export const metadata: Metadata = { + title: 'My Policies | StellarInsure', + description: 'View and manage your insurance policies' +}; + +export default function PoliciesPage() { + return ; +} +``` + +**Client Component (policies-list-page-client.tsx):** +```typescript +'use client'; + +import { usePolicies } from '@/hooks/usePolicies'; +import { PolicyCard } from '@/components/policy/PolicyCard'; +import { LoadingSpinner } from '@/components/ui/LoadingSpinner'; + +export default function PoliciesListPageClient() { + const { policies, isLoading, error } = usePolicies(); + + if (isLoading) return ; + if (error) return ; + + return ( +
+

My Policies

+
+ {policies.map(policy => ( + + ))} +
+
+ ); +} +``` + +### Reusable Components + +**PolicyCard Component:** +```typescript +// components/policy/PolicyCard.tsx +import { Policy } from '@/types/policy'; +import { formatXLM, formatDate } from '@/lib/utils'; +import { Badge } from '@/components/ui/Badge'; +import { Button } from '@/components/ui/Button'; + +interface PolicyCardProps { + policy: Policy; +} + +export function PolicyCard({ policy }: PolicyCardProps) { + const statusColor = { + active: 'green', + expired: 'gray', + cancelled: 'red', + claim_pending: 'yellow' + }[policy.status]; + + return ( +
+
+

Policy #{policy.id}

+ {policy.status} +
+ +
+
+ Type: + {policy.policy_type} +
+
+ Coverage: + {formatXLM(policy.coverage_amount)} +
+
+ Premium: + {formatXLM(policy.premium)} +
+
+ Expires: + {formatDate(policy.end_time)} +
+
+ + +
+ ); +} +``` + +## State Management + +### Wallet State (Zustand) + +```typescript +// stores/walletStore.ts +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +interface WalletState { + address: string | null; + isConnected: boolean; + network: 'testnet' | 'mainnet'; + connect: () => Promise; + disconnect: () => void; + switchNetwork: (network: 'testnet' | 'mainnet') => void; +} + +export const useWalletStore = create()( + persist( + (set) => ({ + address: null, + isConnected: false, + network: 'testnet', + + connect: async () => { + try { + const freighter = await import('@stellar/freighter-api'); + const { address } = await freighter.requestAccess(); + set({ address, isConnected: true }); + } catch (error) { + console.error('Failed to connect wallet:', error); + throw error; + } + }, + + disconnect: () => { + set({ address: null, isConnected: false }); + }, + + switchNetwork: (network) => { + set({ network }); + } + }), + { + name: 'wallet-storage' + } + ) +); +``` + +### Data Fetching (React Query) + +```typescript +// hooks/usePolicies.ts +import { useQuery } from '@tanstack/react-query'; +import { useWalletStore } from '@/stores/walletStore'; +import { api } from '@/lib/api'; + +export function usePolicies() { + const address = useWalletStore(state => state.address); + + return useQuery({ + queryKey: ['policies', address], + queryFn: () => api.policies.list(), + enabled: !!address, + staleTime: 30000, // 30 seconds + refetchInterval: 60000 // 1 minute + }); +} + +export function usePolicy(policyId: number) { + return useQuery({ + queryKey: ['policy', policyId], + queryFn: () => api.policies.get(policyId), + enabled: !!policyId + }); +} +``` + +## Routing + +Next.js 14 App Router with file-based routing: + +| Route | Component | Description | +|-------|-----------|-------------| +| `/` | `app/page.tsx` | Home page | +| `/create` | `app/create/page.tsx` | Create new policy | +| `/policies` | `app/policies/page.tsx` | List all policies | +| `/policies/[id]` | `app/policies/[id]/page.tsx` | Policy details | +| `/claims/[id]` | `app/claims/[id]/page.tsx` | Claim details | +| `/history` | `app/history/page.tsx` | Transaction history | +| `/treasury` | `app/treasury/page.tsx` | Treasury dashboard | +| `/settings` | `app/settings/page.tsx` | User settings | +| `/legal/privacy` | `app/legal/privacy/page.tsx` | Privacy policy | +| `/legal/terms` | `app/legal/terms/page.tsx` | Terms of service | + +### Dynamic Routes + +```typescript +// app/policies/[policyId]/page.tsx +interface PageProps { + params: { policyId: string }; +} + +export default function PolicyDetailPage({ params }: PageProps) { + return ; +} + +// Generate static params for SSG +export async function generateStaticParams() { + const policies = await fetchAllPolicies(); + return policies.map(policy => ({ + policyId: policy.id.toString() + })); +} +``` + +## Wallet Integration + +### Freighter Wallet Connection + +```typescript +// lib/stellar.ts +import * as StellarSdk from '@stellar/stellar-sdk'; +import { isConnected, requestAccess, signTransaction } from '@stellar/freighter-api'; + +export class StellarClient { + private server: StellarSdk.Server; + private network: string; + + constructor(network: 'testnet' | 'mainnet' = 'testnet') { + this.network = network; + const rpcUrl = network === 'testnet' + ? 'https://soroban-testnet.stellar.org' + : 'https://soroban-mainnet.stellar.org'; + this.server = new StellarSdk.Server(rpcUrl); + } + + async connectWallet(): Promise { + const connected = await isConnected(); + if (!connected) { + throw new Error('Freighter wallet not installed'); + } + + const { address } = await requestAccess(); + return address; + } + + async signAndSubmit(transaction: StellarSdk.Transaction): Promise { + const xdr = transaction.toXDR(); + const signedXDR = await signTransaction(xdr, { + network: this.network, + networkPassphrase: this.network === 'testnet' + ? StellarSdk.Networks.TESTNET + : StellarSdk.Networks.PUBLIC + }); + + const signedTx = StellarSdk.TransactionBuilder.fromXDR( + signedXDR, + this.network === 'testnet' + ? StellarSdk.Networks.TESTNET + : StellarSdk.Networks.PUBLIC + ); + + const result = await this.server.submitTransaction(signedTx); + return result.hash; + } +} +``` + +### Contract Interactions + +```typescript +// lib/contract.ts +import * as StellarSdk from '@stellar/stellar-sdk'; +import { StellarClient } from './stellar'; + +export class StellarInsureContract { + private client: StellarClient; + private contractId: string; + + constructor(contractId: string, network: 'testnet' | 'mainnet' = 'testnet') { + this.client = new StellarClient(network); + this.contractId = contractId; + } + + async createPolicy(params: { + policyholder: string; + policyType: string; + coverageAmount: string; + premium: string; + duration: number; + triggerCondition: string; + }): Promise { + const contract = new StellarSdk.Contract(this.contractId); + + const tx = new StellarSdk.TransactionBuilder( + await this.client.server.getAccount(params.policyholder), + { + fee: StellarSdk.BASE_FEE, + networkPassphrase: StellarSdk.Networks.TESTNET + } + ) + .addOperation( + contract.call( + 'create_policy', + StellarSdk.Address.fromString(params.policyholder).toScVal(), + StellarSdk.nativeToScVal(params.policyType, { type: 'symbol' }), + StellarSdk.nativeToScVal(params.coverageAmount, { type: 'i128' }), + StellarSdk.nativeToScVal(params.premium, { type: 'i128' }), + StellarSdk.nativeToScVal(params.duration, { type: 'u64' }), + StellarSdk.nativeToScVal(params.triggerCondition, { type: 'string' }) + ) + ) + .setTimeout(30) + .build(); + + const hash = await this.client.signAndSubmit(tx); + + // Parse policy ID from transaction result + const result = await this.client.server.getTransaction(hash); + return this.parsePolicyId(result); + } + + async calculatePremium(params: { + policyType: string; + coverageAmount: string; + duration: number; + }): Promise { + const contract = new StellarSdk.Contract(this.contractId); + + const result = await contract.call( + 'calculate_premium', + StellarSdk.nativeToScVal(params.policyType, { type: 'symbol' }), + StellarSdk.nativeToScVal(params.coverageAmount, { type: 'i128' }), + StellarSdk.nativeToScVal(params.duration, { type: 'u64' }) + ); + + return StellarSdk.scValToNative(result); + } +} +``` + +## Styling + +### Tailwind Configuration + +```javascript +// tailwind.config.js +module.exports = { + content: [ + './src/app/**/*.{js,ts,jsx,tsx}', + './src/components/**/*.{js,ts,jsx,tsx}' + ], + theme: { + extend: { + colors: { + primary: { + 50: '#f0f9ff', + 100: '#e0f2fe', + 500: '#0ea5e9', + 600: '#0284c7', + 700: '#0369a1' + }, + secondary: { + 50: '#faf5ff', + 500: '#a855f7', + 600: '#9333ea' + } + }, + fontFamily: { + sans: ['Inter', 'system-ui', 'sans-serif'] + } + } + }, + plugins: [ + require('@tailwindcss/forms'), + require('@tailwindcss/typography') + ] +}; +``` + +### Design Tokens + +```css +/* app/tokens.css */ +:root { + --color-primary: #0ea5e9; + --color-secondary: #a855f7; + --color-success: #10b981; + --color-warning: #f59e0b; + --color-error: #ef4444; + + --spacing-xs: 0.25rem; + --spacing-sm: 0.5rem; + --spacing-md: 1rem; + --spacing-lg: 1.5rem; + --spacing-xl: 2rem; + + --font-size-xs: 0.75rem; + --font-size-sm: 0.875rem; + --font-size-base: 1rem; + --font-size-lg: 1.125rem; + --font-size-xl: 1.25rem; + --font-size-2xl: 1.5rem; + --font-size-3xl: 1.875rem; + + --border-radius-sm: 0.25rem; + --border-radius-md: 0.375rem; + --border-radius-lg: 0.5rem; + --border-radius-xl: 0.75rem; +} +``` + +## Testing + +### E2E Tests (Playwright) + +```typescript +// e2e/policies.spec.ts +import { test, expect } from '@playwright/test'; + +test.describe('Policy Management', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/'); + await page.click('text=Connect Wallet'); + // Mock wallet connection + }); + + test('should create a new policy', async ({ page }) => { + await page.goto('/create'); + + await page.selectOption('[name="policyType"]', 'weather'); + await page.fill('[name="coverageAmount"]', '10000'); + await page.fill('[name="duration"]', '30'); + await page.fill('[name="triggerCondition"]', 'rainfall < 50mm'); + + await page.click('button:has-text("Calculate Premium")'); + await expect(page.locator('.premium-amount')).toBeVisible(); + + await page.click('button:has-text("Create Policy")'); + await expect(page).toHaveURL(/\/policies\/\d+/); + }); + + test('should display policy list', async ({ page }) => { + await page.goto('/policies'); + + await expect(page.locator('.policy-card')).toHaveCount(3); + await expect(page.locator('text=Policy #1')).toBeVisible(); + }); +}); +``` + +### Component Tests (Storybook) + +```typescript +// components/policy/PolicyCard.stories.tsx +import type { Meta, StoryObj } from '@storybook/react'; +import { PolicyCard } from './PolicyCard'; + +const meta: Meta = { + title: 'Policy/PolicyCard', + component: PolicyCard, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryObj; + +export const Active: Story = { + args: { + policy: { + id: 1, + policy_type: 'weather', + coverage_amount: '10000000000', + premium: '500000000', + status: 'active', + end_time: Date.now() / 1000 + 2592000 + } + } +}; + +export const Expired: Story = { + args: { + policy: { + ...Active.args.policy, + status: 'expired', + end_time: Date.now() / 1000 - 86400 + } + } +}; +``` + +## Performance Optimization + +1. **Code Splitting:** + - Dynamic imports for heavy components + - Route-based code splitting + +2. **Image Optimization:** + - Next.js Image component + - WebP format with fallbacks + +3. **Caching:** + - React Query for data caching + - Service worker for offline support + +4. **Bundle Size:** + - Tree shaking + - Minimize dependencies + - Lazy loading + +## Deployment + +### Build Process + +```bash +# Install dependencies +npm install + +# Run type checking +npm run type-check + +# Run linting +npm run lint + +# Run tests +npm run test + +# Build for production +npm run build + +# Start production server +npm start +``` + +### Environment Variables + +```env +# .env.production +NEXT_PUBLIC_STELLAR_NETWORK=mainnet +NEXT_PUBLIC_CONTRACT_ID=CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +NEXT_PUBLIC_API_URL=https://api.stellarinsure.io +NEXT_PUBLIC_HORIZON_URL=https://horizon.stellar.org +``` + +## Accessibility + +All components follow WCAG 2.1 Level AA guidelines: + +- Semantic HTML +- ARIA labels and roles +- Keyboard navigation +- Screen reader support +- Color contrast ratios +- Focus indicators + +## Browser Support + +- Chrome/Edge (last 2 versions) +- Firefox (last 2 versions) +- Safari (last 2 versions) +- Mobile browsers (iOS Safari, Chrome Android) diff --git a/docs/ORACLE.md b/docs/ORACLE.md new file mode 100644 index 0000000..268ee27 --- /dev/null +++ b/docs/ORACLE.md @@ -0,0 +1,572 @@ +# Oracle Integration Guide + +## Overview + +StellarInsure uses oracles to verify real-world events and trigger automated claim payouts. The oracle system provides trustless verification of parametric insurance conditions without requiring manual claim assessment. + +## Oracle Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ External Data Sources │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ Weather │ │ Flight │ │ Price │ │ Contract │ │ +│ │ APIs │ │ APIs │ │ Feeds │ │ Monitors │ │ +│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ +└───────┼─────────────┼─────────────┼─────────────┼──────────┘ + │ │ │ │ + ▼ ▼ ▼ ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Oracle Network │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ Oracle Aggregator │ │ +│ │ - Collects data from multiple sources │ │ +│ │ - Validates and aggregates responses │ │ +│ │ - Calculates consensus value │ │ +│ └──────────────────────────────────────────────────────┘ │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ Oracle Nodes │ │ +│ │ - Independent data providers │ │ +│ │ - Cryptographic signing of data │ │ +│ │ - Reputation and staking system │ │ +│ └──────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ StellarInsure Backend │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ Oracle Relay Service │ │ +│ │ - Monitors oracle updates │ │ +│ │ - Validates signatures │ │ +│ │ - Submits verified data to smart contracts │ │ +│ └──────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Soroban Smart Contracts │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ Oracle Verification Functions │ │ +│ │ - verify_oracle_condition() │ │ +│ │ - Checks trigger conditions against oracle data │ │ +│ │ - Automatically processes claims when met │ │ +│ └──────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Oracle Types + +### Weather Oracle + +Provides meteorological data for weather-based insurance policies. + +**Data Sources:** +- NOAA (National Oceanic and Atmospheric Administration) +- OpenWeatherMap API +- Weather Underground +- Local weather stations + +**Supported Parameters:** +- Temperature (Celsius/Fahrenheit) +- Rainfall (mm) +- Snowfall (cm) +- Wind speed (km/h, mph) +- Humidity (%) +- Atmospheric pressure (hPa) + +**Example Trigger Conditions:** +```rust +// Temperature below freezing +"temperature < 0°C" + +// Heavy rainfall +"rainfall > 100mm in 24h" + +// Hurricane-force winds +"wind_speed > 120km/h" + +// Drought conditions +"rainfall < 50mm in 30 days" +``` + +**Data Format:** +```json +{ + "oracle_type": "weather", + "location": { + "latitude": 40.7128, + "longitude": -74.0060, + "city": "New York" + }, + "timestamp": 1714204800, + "data": { + "temperature": -2.5, + "rainfall_24h": 0.0, + "wind_speed": 15.3, + "humidity": 65 + }, + "sources": [ + { + "provider": "NOAA", + "confidence": 0.95 + }, + { + "provider": "OpenWeatherMap", + "confidence": 0.92 + } + ], + "signature": "0x..." +} +``` + +### Flight Oracle + +Tracks flight status for travel insurance policies. + +**Data Sources:** +- FlightAware API +- FlightRadar24 +- Airline APIs +- Airport status feeds + +**Supported Parameters:** +- Departure delay (minutes) +- Arrival delay (minutes) +- Cancellation status +- Diversion status +- Gate changes + +**Example Trigger Conditions:** +```rust +// Flight delayed over 2 hours +"delay > 120 minutes" + +// Flight cancelled +"status == cancelled" + +// Missed connection +"arrival_delay > 60 AND connection_time < 90" +``` + +**Data Format:** +```json +{ + "oracle_type": "flight", + "flight": { + "number": "AA123", + "airline": "American Airlines", + "origin": "JFK", + "destination": "LAX", + "scheduled_departure": 1714204800, + "scheduled_arrival": 1714226400 + }, + "timestamp": 1714204800, + "data": { + "status": "delayed", + "actual_departure": 1714212000, + "delay_minutes": 120, + "reason": "weather" + }, + "sources": [ + { + "provider": "FlightAware", + "confidence": 0.98 + } + ], + "signature": "0x..." +} +``` + +### Smart Contract Oracle + +Monitors blockchain events for DeFi insurance. + +**Data Sources:** +- Blockchain explorers +- Smart contract event logs +- Security monitoring services +- Exploit detection systems + +**Supported Parameters:** +- Contract exploit detected +- Unusual transaction volume +- Price manipulation +- Reentrancy attacks +- Access control violations + +**Example Trigger Conditions:** +```rust +// Exploit detected +"exploit_detected == true" + +// Large unauthorized withdrawal +"unauthorized_withdrawal > 1000000 XLM" + +// Oracle manipulation +"price_deviation > 10%" +``` + +**Data Format:** +```json +{ + "oracle_type": "smart_contract", + "contract": { + "address": "CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "network": "stellar-mainnet" + }, + "timestamp": 1714204800, + "data": { + "event_type": "exploit_detected", + "severity": "critical", + "affected_value": "5000000000000", + "transaction_hash": "abc123..." + }, + "sources": [ + { + "provider": "CertiK", + "confidence": 0.99 + } + ], + "signature": "0x..." +} +``` + +### Asset Price Oracle + +Provides cryptocurrency and asset price data. + +**Data Sources:** +- Stellar DEX +- Centralized exchanges (Binance, Coinbase) +- Price aggregators (CoinGecko, CoinMarketCap) +- DeFi protocols + +**Supported Parameters:** +- Current price +- 24h price change +- Volume +- Market cap +- Price volatility + +**Example Trigger Conditions:** +```rust +// Price drop protection +"price_drop > 20%" + +// Stablecoin de-peg +"usdc_price < 0.95" + +// Volatility spike +"volatility > 50%" +``` + +**Data Format:** +```json +{ + "oracle_type": "asset_price", + "asset": { + "symbol": "XLM", + "name": "Stellar Lumens" + }, + "timestamp": 1714204800, + "data": { + "price_usd": 0.12, + "change_24h": -5.2, + "volume_24h": 150000000, + "volatility": 12.5 + }, + "sources": [ + { + "provider": "Stellar DEX", + "price": 0.1205, + "confidence": 0.95 + }, + { + "provider": "Binance", + "price": 0.1198, + "confidence": 0.97 + } + ], + "signature": "0x..." +} +``` + +## Oracle Integration + +### Smart Contract Integration + +The StellarInsure contract includes oracle verification functions: + +```rust +pub fn verify_oracle_condition( + env: Env, + oracle_type: Symbol, + parameter: Symbol, +) -> Result +``` + +**Usage Example:** +```rust +// Verify weather condition +let result = contract.verify_oracle_condition( + &env, + symbol_short!("Weather"), + symbol_short!("temp") +); + +if result.condition_met { + // Automatically process claim + contract.process_claim(&env, policy_id, true)?; +} +``` + +### Backend Oracle Relay + +The backend service monitors oracle updates and relays verified data to smart contracts. + +**Configuration:** +```python +# backend/src/config.py +ORACLE_CONFIG = { + "weather": { + "providers": ["NOAA", "OpenWeatherMap"], + "update_interval": 3600, # 1 hour + "min_confidence": 0.90 + }, + "flight": { + "providers": ["FlightAware"], + "update_interval": 300, # 5 minutes + "min_confidence": 0.95 + }, + "smart_contract": { + "providers": ["CertiK", "PeckShield"], + "update_interval": 60, # 1 minute + "min_confidence": 0.99 + } +} +``` + +**Oracle Relay Service:** +```python +class OracleRelayService: + async def monitor_policies(self): + """Monitor active policies and check oracle conditions""" + active_policies = await self.get_active_policies() + + for policy in active_policies: + oracle_data = await self.fetch_oracle_data( + policy.policy_type, + policy.trigger_condition + ) + + if self.condition_met(oracle_data, policy.trigger_condition): + await self.submit_claim(policy.id, oracle_data) + + async def fetch_oracle_data(self, oracle_type, condition): + """Fetch and aggregate data from multiple oracle sources""" + providers = ORACLE_CONFIG[oracle_type]["providers"] + responses = [] + + for provider in providers: + data = await self.query_provider(provider, condition) + if data.confidence >= ORACLE_CONFIG[oracle_type]["min_confidence"]: + responses.append(data) + + return self.aggregate_responses(responses) +``` + +## Data Feed Specifications + +### Update Frequency + +| Oracle Type | Update Interval | Latency | +|-------------|----------------|---------| +| Weather | 1 hour | < 5 minutes | +| Flight | 5 minutes | < 1 minute | +| Smart Contract | 1 minute | < 30 seconds | +| Asset Price | 1 minute | < 10 seconds | + +### Data Validation + +All oracle data must pass validation checks: + +1. **Signature Verification:** + - Cryptographic signature from trusted oracle node + - Public key registered on-chain + +2. **Timestamp Validation:** + - Data must be recent (within acceptable staleness window) + - Prevents replay attacks + +3. **Consensus Mechanism:** + - Aggregate responses from multiple providers + - Require minimum number of confirmations + - Outlier detection and removal + +4. **Confidence Scoring:** + - Each data point includes confidence score + - Minimum confidence threshold enforced + - Higher confidence for critical decisions + +### Oracle Node Requirements + +To become an oracle node provider: + +1. **Staking:** + - Stake minimum amount of XLM as collateral + - Slashing for incorrect or malicious data + +2. **Reputation:** + - Track record of accurate data provision + - Uptime and response time metrics + +3. **Infrastructure:** + - Reliable data sources + - Low-latency API endpoints + - Cryptographic key management + +4. **Registration:** + - Register public key on-chain + - Specify supported oracle types + - Set pricing for data provision + +## Security Considerations + +### Oracle Manipulation Prevention + +1. **Multiple Data Sources:** + - Aggregate data from 3+ independent providers + - Require consensus among majority + +2. **Economic Incentives:** + - Oracle nodes stake collateral + - Rewards for accurate data + - Penalties for incorrect data + +3. **Time-Weighted Average:** + - Use TWAP for price oracles + - Prevents flash loan attacks + +4. **Circuit Breakers:** + - Pause system if oracle data anomalies detected + - Admin intervention for suspicious activity + +### Data Privacy + +For sensitive data (health records, personal information): + +1. **Zero-Knowledge Proofs:** + - Prove condition met without revealing data + - Preserve user privacy + +2. **Encrypted Data Feeds:** + - End-to-end encryption + - Only authorized parties can decrypt + +3. **Selective Disclosure:** + - Reveal only necessary information + - Minimize data exposure + +## Future Enhancements + +### Chainlink Integration + +Integration with Chainlink oracle network for: +- Decentralized price feeds +- Weather data +- Sports results +- Random number generation + +**Example Integration:** +```rust +use chainlink_soroban::PriceFeed; + +pub fn get_xlm_price(env: &Env) -> i128 { + let price_feed = PriceFeed::new(env, &CHAINLINK_XLM_USD_FEED); + price_feed.latest_answer() +} +``` + +### Custom Oracle Network + +Build dedicated oracle network for StellarInsure: +- Specialized data providers +- Insurance-specific data feeds +- Lower latency and costs +- Tighter integration with protocol + +### Machine Learning Integration + +Use ML models for: +- Fraud detection +- Risk assessment +- Premium optimization +- Claim prediction + +## Testing + +### Oracle Simulation + +For development and testing, use simulated oracle responses: + +```rust +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_weather_oracle() { + let env = Env::default(); + let contract = StellarInsureClient::new(&env, &contract_id); + + // Simulate weather data + let oracle_result = OracleResult { + condition_met: true, + value: -5, // Temperature -5°C + timestamp: env.ledger().timestamp(), + confidence: 95 + }; + + // Verify condition triggers claim + assert!(oracle_result.condition_met); + } +} +``` + +### Integration Testing + +Test oracle integration with real data sources in staging environment: + +```bash +# Run oracle relay service in test mode +python -m backend.src.services.oracle_relay --test-mode + +# Monitor oracle updates +curl http://localhost:8000/oracle/status +``` + +## Monitoring and Alerts + +Set up monitoring for oracle health: + +- Data freshness alerts +- Consensus failure notifications +- Provider downtime tracking +- Anomaly detection + +**Example Alert Configuration:** +```yaml +alerts: + - name: stale_oracle_data + condition: data_age > 3600 + severity: warning + + - name: oracle_consensus_failure + condition: consensus_providers < 2 + severity: critical + + - name: price_anomaly + condition: price_deviation > 20% + severity: high +``` diff --git a/smartcontract/src/error.rs b/smartcontract/src/error.rs index 36c848f..a759b14 100644 --- a/smartcontract/src/error.rs +++ b/smartcontract/src/error.rs @@ -35,4 +35,10 @@ pub enum Error { // Issue #21 — policy modification CoverageDecrease = 26, PolicyAlreadyExpired = 27, + // Issue #203 — premium verification + PremiumMismatch = 28, + // Issue #199 — policy count limit + MaxPoliciesReached = 29, + // Issue #202 — risk pool withdrawal protection + InsufficientPoolReserve = 30, } diff --git a/smartcontract/src/lib.rs b/smartcontract/src/lib.rs index fd51244..8907822 100644 --- a/smartcontract/src/lib.rs +++ b/smartcontract/src/lib.rs @@ -45,6 +45,8 @@ pub use types::*; #[contract] pub struct StellarInsure; +const MAX_POLICIES: u64 = 1_000_000; + #[contractimpl] impl StellarInsure { /// Initialize the insurance protocol @@ -56,6 +58,26 @@ impl StellarInsure { storage::set_admins(&env, &admins); storage::set_threshold(&env, 1); storage::set_policy_counter(&env, 0); + storage::set_max_policies(&env, MAX_POLICIES); + } + + /// Set the maximum number of policies allowed (admin only) + pub fn set_max_policies(env: Env, admin: Address, max_policies: u64) -> Result<(), Error> { + admin.require_auth(); + let current_admin = storage::get_admin(&env); + if admin != current_admin { + return Err(Error::Unauthorized); + } + if max_policies == 0 { + return Err(Error::InvalidAmount); + } + storage::set_max_policies(&env, max_policies); + Ok(()) + } + + /// Get the maximum number of policies allowed + pub fn get_max_policies(env: Env) -> u64 { + storage::get_max_policies(&env) } /// Set the token used for premiums and payouts (admin only) @@ -118,6 +140,16 @@ impl StellarInsure { } let policy_id = storage::get_policy_counter(&env); + let max_policies = storage::get_max_policies(&env); + if policy_id >= max_policies { + return Err(Error::MaxPoliciesReached); + } + + let calculated_premium = premium::calculate_premium(&policy_type, coverage_amount, duration)?; + if premium != calculated_premium { + return Err(Error::PremiumMismatch); + } + let next_id = policy_id + 1; let policy = Policy { diff --git a/smartcontract/src/risk_pool.rs b/smartcontract/src/risk_pool.rs index fdabb58..ee85467 100644 --- a/smartcontract/src/risk_pool.rs +++ b/smartcontract/src/risk_pool.rs @@ -18,10 +18,34 @@ impl RiskPool { storage::set_risk_pool_admin(&env, &admin); storage::set_total_liquidity(&env, 0); storage::set_total_yield_distributed(&env, 0); + storage::set_reserve_ratio(&env, 2000); Ok(()) } + pub fn set_reserve_ratio(env: Env, admin: Address, ratio: u32) -> Result<(), Error> { + admin.require_auth(); + let current_admin = storage::get_risk_pool_admin(&env); + if admin != current_admin { + return Err(Error::Unauthorized); + } + if ratio > 10000 { + return Err(Error::InvalidAmount); + } + storage::set_reserve_ratio(&env, ratio); + Ok(()) + } + + pub fn get_reserve_ratio(env: Env) -> u32 { + storage::get_reserve_ratio(&env) + } + + fn calculate_pending_claims_reserve(env: &Env) -> i128 { + let total_liquidity = storage::get_total_liquidity(env); + let reserve_ratio = storage::get_reserve_ratio(env); + total_liquidity * (reserve_ratio as i128) / 10000 + } + pub fn add_liquidity(env: Env, provider: Address, amount: i128) -> Result<(), Error> { provider.require_auth(); @@ -68,8 +92,16 @@ impl RiskPool { return Err(Error::InsufficientLiquidity); } + let total_liquidity = storage::get_total_liquidity(&env); + let reserve_required = Self::calculate_pending_claims_reserve(&env); + let available_balance = total_liquidity - reserve_required; + + if amount > available_balance { + return Err(Error::InsufficientPoolReserve); + } + position.contribution -= amount; - let new_total = storage::get_total_liquidity(&env) - amount; + let new_total = total_liquidity - amount; storage::set_total_liquidity(&env, new_total); if position.contribution == 0 && position.accrued_yield == 0 { diff --git a/smartcontract/src/storage.rs b/smartcontract/src/storage.rs index 4833aa0..2042eeb 100644 --- a/smartcontract/src/storage.rs +++ b/smartcontract/src/storage.rs @@ -23,6 +23,10 @@ enum DataKey { TotalPremium, TotalPayouts, RiskPool, + // Issue #199 — max policies + MaxPolicies, + // Issue #202 — reserve ratio + ReserveRatio, } pub fn get_version(env: &Env) -> u32 { @@ -332,3 +336,29 @@ pub fn get_pool_stats(env: &Env) -> PoolStats { provider_count: providers.len(), } } + +pub fn set_max_policies(env: &Env, max_policies: u64) { + env.storage() + .instance() + .set(&DataKey::MaxPolicies, &max_policies); +} + +pub fn get_max_policies(env: &Env) -> u64 { + env.storage() + .instance() + .get(&DataKey::MaxPolicies) + .unwrap_or(1_000_000) +} + +pub fn set_reserve_ratio(env: &Env, ratio: u32) { + env.storage() + .instance() + .set(&DataKey::ReserveRatio, &ratio); +} + +pub fn get_reserve_ratio(env: &Env) -> u32 { + env.storage() + .instance() + .get(&DataKey::ReserveRatio) + .unwrap_or(2000) +} diff --git a/smartcontract/src/test.rs b/smartcontract/src/test.rs index 4f64882..07d3fcb 100644 --- a/smartcontract/src/test.rs +++ b/smartcontract/src/test.rs @@ -1230,3 +1230,180 @@ fn test_verify_oracle_stubs() { ); assert!(res_contract.is_verified); } + +// ── Tests for Issue #203: Premium verification ─────────────────────────────── + +#[test] +#[should_panic(expected = "PremiumMismatch")] +fn test_create_policy_rejects_incorrect_premium() { + let (env, contract_id, _admin, policyholder, _token) = setup_insurance_contract(); + let client = StellarInsureClient::new(&env, &contract_id); + + // Calculate correct premium + let correct_premium = client.calculate_premium( + &PolicyType::Weather, + &1_000_000, + &2_592_000, + ); + + // Try to create policy with incorrect premium + client.create_policy( + &policyholder, + &PolicyType::Weather, + &1_000_000, + &(correct_premium - 1000), // Wrong premium + &2_592_000, + &String::from_str(&env, "temperature < 0"), + ).unwrap(); +} + +#[test] +fn test_create_policy_accepts_correct_premium() { + let (env, contract_id, _admin, policyholder, _token) = setup_insurance_contract(); + let client = StellarInsureClient::new(&env, &contract_id); + + let correct_premium = client.calculate_premium( + &PolicyType::Weather, + &1_000_000, + &2_592_000, + ); + + let policy_id = client.create_policy( + &policyholder, + &PolicyType::Weather, + &1_000_000, + &correct_premium, + &2_592_000, + &String::from_str(&env, "temperature < 0"), + ).unwrap(); + + let policy = client.get_policy(&policy_id); + assert_eq!(policy.premium, correct_premium); +} + +// ── Tests for Issue #199: Max policy count limit ───────────────────────────── + +#[test] +fn test_set_max_policies() { + let (env, contract_id, admin, _policyholder, _token) = setup_insurance_contract(); + let client = StellarInsureClient::new(&env, &contract_id); + + client.set_max_policies(&admin, &100).unwrap(); + let max_policies = client.get_max_policies(); + assert_eq!(max_policies, 100); +} + +#[test] +#[should_panic(expected = "Unauthorized")] +fn test_set_max_policies_requires_admin() { + let (env, contract_id, _admin, policyholder, _token) = setup_insurance_contract(); + let client = StellarInsureClient::new(&env, &contract_id); + + client.set_max_policies(&policyholder, &100).unwrap(); +} + +#[test] +#[should_panic(expected = "MaxPoliciesReached")] +fn test_create_policy_respects_max_limit() { + let (env, contract_id, admin, policyholder, _token) = setup_insurance_contract(); + let client = StellarInsureClient::new(&env, &contract_id); + + // Set limit to 2 policies + client.set_max_policies(&admin, &2).unwrap(); + + // Create 2 policies successfully + let premium = client.calculate_premium(&PolicyType::Weather, &1_000_000, &2_592_000); + + client.create_policy( + &policyholder, + &PolicyType::Weather, + &1_000_000, + &premium, + &2_592_000, + &String::from_str(&env, "condition1"), + ).unwrap(); + + client.create_policy( + &policyholder, + &PolicyType::Weather, + &1_000_000, + &premium, + &2_592_000, + &String::from_str(&env, "condition2"), + ).unwrap(); + + // Third policy should fail + client.create_policy( + &policyholder, + &PolicyType::Weather, + &1_000_000, + &premium, + &2_592_000, + &String::from_str(&env, "condition3"), + ).unwrap(); +} + +// ── Tests for Issue #202: Risk pool withdrawal protection ──────────────────── + +#[test] +fn test_set_reserve_ratio() { + let (env, contract_id, admin, _provider_one, _provider_two) = setup_risk_pool(); + let client = RiskPoolClient::new(&env, &contract_id); + + client.set_reserve_ratio(&admin, &3000).unwrap(); // 30% + let ratio = client.get_reserve_ratio(); + assert_eq!(ratio, 3000); +} + +#[test] +#[should_panic(expected = "Unauthorized")] +fn test_set_reserve_ratio_requires_admin() { + let (env, contract_id, _admin, provider_one, _provider_two) = setup_risk_pool(); + let client = RiskPoolClient::new(&env, &contract_id); + + client.set_reserve_ratio(&provider_one, &3000).unwrap(); +} + +#[test] +#[should_panic(expected = "InsufficientPoolReserve")] +fn test_withdraw_respects_reserve_ratio() { + let (env, contract_id, admin, provider_one, _provider_two) = setup_risk_pool(); + let client = RiskPoolClient::new(&env, &contract_id); + + // Set reserve ratio to 50% + client.set_reserve_ratio(&admin, &5000).unwrap(); + + // Add liquidity + client.add_liquidity(&provider_one, &1_000_000).unwrap(); + + // Try to withdraw more than available (should leave 50% reserve) + // Available = 1_000_000 - (1_000_000 * 50%) = 500_000 + client.withdraw_liquidity(&provider_one, &600_000).unwrap(); +} + +#[test] +fn test_withdraw_within_reserve_succeeds() { + let (env, contract_id, admin, provider_one, _provider_two) = setup_risk_pool(); + let client = RiskPoolClient::new(&env, &contract_id); + + // Set reserve ratio to 20% + client.set_reserve_ratio(&admin, &2000).unwrap(); + + // Add liquidity + client.add_liquidity(&provider_one, &1_000_000).unwrap(); + + // Withdraw within available amount (80% of total) + client.withdraw_liquidity(&provider_one, &700_000).unwrap(); + + let position = client.get_provider_position(&provider_one); + assert_eq!(position.contribution, 300_000); +} + +#[test] +fn test_default_reserve_ratio_is_20_percent() { + let (env, contract_id, _admin, _provider_one, _provider_two) = setup_risk_pool(); + let client = RiskPoolClient::new(&env, &contract_id); + + let ratio = client.get_reserve_ratio(); + assert_eq!(ratio, 2000); // 20% +}