├── .gitignore ├── backend ├── src │ ├── plaid │ │ ├── entities │ │ │ └── plaid.entity.ts │ │ ├── dto │ │ │ ├── create-plaid.dto.ts │ │ │ └── update-plaid.dto.ts │ │ ├── plaid.service.spec.ts │ │ ├── plaid.controller.spec.ts │ │ ├── plaid.module.ts │ │ └── plaid.controller.ts │ ├── email │ │ ├── dto │ │ │ ├── create-email.dto.ts │ │ │ └── update-email.dto.ts │ │ ├── entities │ │ │ └── email.entity.ts │ │ ├── email.module.ts │ │ ├── email.service.spec.ts │ │ ├── email.controller.spec.ts │ │ ├── email.controller.ts │ │ └── email.service.ts │ ├── redis │ │ ├── dto │ │ │ ├── create-redi.dto.ts │ │ │ └── update-redi.dto.ts │ │ ├── entities │ │ │ └── redi.entity.ts │ │ ├── redis.module.ts │ │ ├── redis.service.spec.ts │ │ ├── redis.controller.spec.ts │ │ ├── redis.controller.ts │ │ └── redis.service.ts │ ├── banking │ │ ├── dto │ │ │ ├── create-banking.dto.ts │ │ │ └── update-banking.dto.ts │ │ ├── banking.service.spec.ts │ │ ├── banking.module.ts │ │ ├── banking.controller.spec.ts │ │ ├── banking.service.ts │ │ ├── entities │ │ │ ├── bank-token.entity.ts │ │ │ └── bank-account.entity.ts │ │ └── banking.controller.ts │ ├── transaction │ │ ├── dto │ │ │ ├── create-transaction.dto.ts │ │ │ └── update-transaction.dto.ts │ │ ├── transaction.module.ts │ │ ├── transaction.service.spec.ts │ │ ├── transaction.controller.spec.ts │ │ ├── transaction.controller.ts │ │ └── entities │ │ │ └── transaction.entity.ts │ ├── auth │ │ ├── entities │ │ │ └── auth.entity.ts │ │ ├── dto │ │ │ ├── forgot-password.dto.ts │ │ │ ├── login.dto.ts │ │ │ ├── wallet-auth.dto.ts │ │ │ ├── reset-password.dto.ts │ │ │ └── register.dto.ts │ │ ├── decorators │ │ │ ├── public.decorator.ts │ │ │ ├── roles.decorator.ts │ │ │ └── current-user.decorator.ts │ │ ├── auth.service.spec.ts │ │ ├── auth.controller.spec.ts │ │ ├── strategies │ │ │ ├── local.strategy.ts │ │ │ └── jwt.strategy.ts │ │ ├── guards │ │ │ ├── jwt-auth.guard.ts │ │ │ └── roles.guard.ts │ │ └── auth.module.ts │ ├── privacy │ │ ├── dto │ │ │ ├── request-data-deletion.dto.ts │ │ │ ├── request-data-export.dto.ts │ │ │ ├── create-consent.dto.ts │ │ │ └── create-privacy-assessment.dto.ts │ │ ├── entities │ │ │ ├── privacy-assessment.entity.ts │ │ │ ├── consent.entity.ts │ │ │ ├── data-classification.entity.ts │ │ │ └── audit-log.entity.ts │ │ ├── privacy.module.ts │ │ └── privacy.controller.ts │ ├── users │ │ ├── dto │ │ │ ├── export-profile.dto.ts │ │ │ ├── preferences.dto.ts │ │ │ ├── deactivate-profile.dto.ts │ │ │ ├── create-profile.dto.ts │ │ │ ├── create-user-dto.ts │ │ │ └── update-profile.dto.ts │ │ ├── entities │ │ │ ├── index.ts │ │ │ ├── refresh-token.entity.ts │ │ │ └── user.entity.ts │ │ ├── users.module.ts │ │ ├── users.service.spec.ts │ │ ├── users.controller.spec.ts │ │ ├── utils │ │ │ └── encryption.util.ts │ │ └── users.controller.ts │ ├── app.service.ts │ ├── documents │ │ ├── dto │ │ │ └── document-upload.dto.ts │ │ ├── enum │ │ │ └── documentProcessingStatus.enum.ts │ │ ├── multer.config.ts │ │ ├── documents.module.ts │ │ ├── config.ts │ │ ├── documents.service.spec.ts │ │ └── entities │ │ │ └── document-processing.entity.ts │ ├── risk │ │ ├── risk.module.ts │ │ └── risk.controller.ts │ ├── app.controller.ts │ ├── ipfs │ │ ├── entities │ │ │ └── ipfs-document.entity.ts │ │ ├── ipfs.module.ts │ │ ├── ipfs-document.service.ts │ │ ├── ipfs.controller.ts │ │ └── ipfs.service.spec.ts │ ├── credit-bureaus │ │ ├── credit-bureau.module.ts │ │ ├── test │ │ │ └── credit-bureau.factory.ts │ │ ├── credit-bureau.adapter.ts │ │ └── README.md │ ├── screening │ │ ├── screening.module.ts │ │ ├── entities │ │ │ ├── watchlist.entity.ts │ │ │ ├── screening-match.entity.ts │ │ │ └── screening-result.entity.ts │ │ ├── processors │ │ │ └── screening.processor.ts │ │ ├── scripts │ │ │ └── populate-watchlist.ts │ │ ├── controllers │ │ │ └── screening.controller.ts │ │ └── services │ │ │ ├── watchlist.service.ts │ │ │ ├── risk-scoring.service.ts │ │ │ └── fuzzy-matching.service.ts │ ├── anonymization │ │ ├── dto │ │ │ └── anonymize-data.dto.ts │ │ ├── anonymization.module.ts │ │ ├── services │ │ │ ├── pseudonymization.service.ts │ │ │ └── k-anonymity.service.ts │ │ └── controllers │ │ │ └── anonymization.controller.ts │ ├── audit │ │ ├── audit.module.ts │ │ ├── audit.controller.ts │ │ └── audit.interceptor.ts │ ├── analytics │ │ ├── analytics.controller.ts │ │ └── analytics.module.ts │ ├── cache │ │ ├── cache-warning.service.ts │ │ └── cache.service.ts │ ├── main.ts │ ├── migration │ │ ├── 1754578035383-AddEmailIndexToUsers.ts │ │ └── 1754574370959-CreateUserTable.ts │ ├── verification │ │ ├── dto │ │ │ ├── verification-request.dto.ts │ │ │ └── verification-response.dto.ts │ │ ├── verification.controller.ts │ │ └── verification.service.ts │ ├── app.controller.spec.ts │ ├── api-gateway │ │ ├── dto │ │ │ ├── service-instance.dto.ts │ │ │ ├── api-key.dto.ts │ │ │ └── endpoint.dto.ts │ │ ├── entities │ │ │ ├── service-health.entity.ts │ │ │ ├── api-usage.entity.ts │ │ │ ├── api-key.entity.ts │ │ │ └── api-endpoint.entity.ts │ │ ├── scripts │ │ │ ├── generate-docs.ts │ │ │ └── initialize-endpoints.ts │ │ ├── interceptors │ │ │ ├── transformation.interceptor.ts │ │ │ └── circuit-breaker.interceptor.ts │ │ ├── api-gateway.module.ts │ │ ├── api-gateway.spec.ts │ │ ├── services │ │ │ ├── health-monitor.service.ts │ │ │ └── rate-limit.service.ts │ │ └── guards │ │ │ └── api-key.guard.ts │ ├── data-source.ts │ ├── utils │ │ └── crypto.utils.ts │ ├── transaction-analysis │ │ ├── entities │ │ │ ├── analysis-rule.entity.ts │ │ │ └── transaction-analysis.entity.ts │ │ ├── transaction-analysis.module.ts │ │ ├── services │ │ │ └── __tests__ │ │ │ │ ├── fraud-detection.service.spec.ts │ │ │ │ └── categorization.service.spec.ts │ │ └── controllers │ │ │ └── __tests__ │ │ │ └── transaction-analysis.controller.spec.ts │ └── app.module.ts ├── .prettierrc ├── tsconfig.build.json ├── nest-cli.json ├── test │ ├── jest-e2e.json │ └── app.e2e-spec.ts ├── jest.config.js ├── __mocks__ │ └── ipfs-http-client.js ├── bankingAnalysis │ ├── dto │ │ └── link.dto.ts │ ├── banking-module.ts │ ├── encrytion-utility.ts │ ├── banking.controller.ts │ ├── PlaidService.ts │ └── banking-service.ts ├── .gitignore ├── tsconfig.json ├── eslint.config.mjs └── .env.example ├── frontend ├── src │ └── app │ │ ├── favicon.ico │ │ ├── globals.css │ │ └── layout.tsx ├── postcss.config.mjs ├── public │ ├── vercel.svg │ ├── window.svg │ ├── file.svg │ ├── globe.svg │ └── next.svg ├── next.config.ts ├── eslint.config.mjs ├── .gitignore ├── tsconfig.json ├── package.json ├── README.md └── CONTRIBUTING.md ├── contracts ├── interfaces │ ├── IFlareContractRegistry.sol │ ├── IFtsoRegistry.sol │ ├── IStateConnector.sol │ ├── ICreditScoring.sol │ └── IKYCRegistry.sol └── tests │ └── mocks │ └── MockContracts.sol ├── .env ├── tsconfig.json ├── README.md ├── hardhat.config.js ├── scripts └── get-flare-addresses.js ├── hardhat.config.ts └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /backend/src/plaid/entities/plaid.entity.ts: -------------------------------------------------------------------------------- 1 | export class Plaid {} 2 | -------------------------------------------------------------------------------- /backend/src/email/dto/create-email.dto.ts: -------------------------------------------------------------------------------- 1 | export class CreateEmailDto {} 2 | -------------------------------------------------------------------------------- /backend/src/plaid/dto/create-plaid.dto.ts: -------------------------------------------------------------------------------- 1 | export class CreatePlaidDto {} 2 | -------------------------------------------------------------------------------- /backend/src/redis/dto/create-redi.dto.ts: -------------------------------------------------------------------------------- 1 | export class CreateRediDto {} 2 | -------------------------------------------------------------------------------- /backend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /backend/src/banking/dto/create-banking.dto.ts: -------------------------------------------------------------------------------- 1 | export class CreateBankingDto {} 2 | -------------------------------------------------------------------------------- /backend/src/transaction/dto/create-transaction.dto.ts: -------------------------------------------------------------------------------- 1 | export class CreateTransactionDto {} 2 | -------------------------------------------------------------------------------- /frontend/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rub-a-Dab-Dub/credora/HEAD/frontend/src/app/favicon.ico -------------------------------------------------------------------------------- /backend/src/auth/entities/auth.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity } from 'typeorm'; 2 | 3 | @Entity('auth') 4 | export class Auth {} 5 | -------------------------------------------------------------------------------- /frontend/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /backend/src/email/entities/email.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity } from 'typeorm'; 2 | 3 | @Entity('email') 4 | export class Email {} 5 | -------------------------------------------------------------------------------- /backend/src/redis/entities/redi.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity } from 'typeorm'; 2 | 3 | @Entity('redis') 4 | export class Redi {} 5 | -------------------------------------------------------------------------------- /backend/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /backend/src/privacy/dto/request-data-deletion.dto.ts: -------------------------------------------------------------------------------- 1 | export class RequestDataDeletionDto { 2 | userId: string; 3 | reason?: string; 4 | } 5 | -------------------------------------------------------------------------------- /backend/src/privacy/dto/request-data-export.dto.ts: -------------------------------------------------------------------------------- 1 | export class RequestDataExportDto { 2 | userId: string; 3 | format: 'json' | 'csv'; 4 | } 5 | -------------------------------------------------------------------------------- /backend/src/users/dto/export-profile.dto.ts: -------------------------------------------------------------------------------- 1 | export class ExportProfileDto { 2 | // This DTO can be extended for GDPR export options 3 | } 4 | -------------------------------------------------------------------------------- /backend/src/users/entities/index.ts: -------------------------------------------------------------------------------- 1 | export { User, UserRole } from './user.entity'; 2 | export { RefreshToken } from './refresh-token.entity'; 3 | -------------------------------------------------------------------------------- /backend/src/privacy/dto/create-consent.dto.ts: -------------------------------------------------------------------------------- 1 | export class CreateConsentDto { 2 | userId: string; 3 | purpose: string; 4 | granted: boolean; 5 | } 6 | -------------------------------------------------------------------------------- /backend/src/privacy/dto/create-privacy-assessment.dto.ts: -------------------------------------------------------------------------------- 1 | export class CreatePrivacyAssessmentDto { 2 | userId: string; 3 | assessment: string; 4 | } 5 | -------------------------------------------------------------------------------- /frontend/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/src/auth/dto/forgot-password.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsEmail } from 'class-validator'; 2 | 3 | export class ForgotPasswordDto { 4 | @IsEmail() 5 | email: string; 6 | } 7 | -------------------------------------------------------------------------------- /contracts/interfaces/IFlareContractRegistry.sol: -------------------------------------------------------------------------------- 1 | interface IFlareContractRegistry { 2 | function getContractAddressByName(string calldata _name) external view returns (address); 3 | } -------------------------------------------------------------------------------- /contracts/interfaces/IFtsoRegistry.sol: -------------------------------------------------------------------------------- 1 | interface IFtsoRegistry { 2 | function getCurrentPrice(string calldata _symbol) external view returns (uint256 _price, uint256 _timestamp); 3 | } -------------------------------------------------------------------------------- /frontend/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /backend/src/users/dto/preferences.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsObject } from 'class-validator'; 2 | 3 | export class PreferencesDto { 4 | @IsObject() 5 | preferences: Record; 6 | } 7 | -------------------------------------------------------------------------------- /backend/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/users/dto/deactivate-profile.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsBoolean } from 'class-validator'; 2 | 3 | export class DeactivateProfileDto { 4 | @IsBoolean() 5 | deactivate: boolean; 6 | } 7 | -------------------------------------------------------------------------------- /backend/src/auth/decorators/public.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | 3 | export const IS_PUBLIC_KEY = 'isPublic'; 4 | export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); 5 | -------------------------------------------------------------------------------- /backend/src/documents/dto/document-upload.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsString, IsOptional } from 'class-validator'; 2 | 3 | export class DocumentUploadDto { 4 | @IsString() 5 | @IsOptional() 6 | description?: string; 7 | } 8 | -------------------------------------------------------------------------------- /backend/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/redis/dto/update-redi.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { CreateRediDto } from './create-redi.dto'; 3 | 4 | export class UpdateRediDto extends PartialType(CreateRediDto) {} 5 | -------------------------------------------------------------------------------- /backend/src/email/dto/update-email.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { CreateEmailDto } from './create-email.dto'; 3 | 4 | export class UpdateEmailDto extends PartialType(CreateEmailDto) {} 5 | -------------------------------------------------------------------------------- /backend/src/plaid/dto/update-plaid.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { CreatePlaidDto } from './create-plaid.dto'; 3 | 4 | export class UpdatePlaidDto extends PartialType(CreatePlaidDto) {} 5 | -------------------------------------------------------------------------------- /backend/src/banking/dto/update-banking.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { CreateBankingDto } from './create-banking.dto'; 3 | 4 | export class UpdateBankingDto extends PartialType(CreateBankingDto) {} 5 | -------------------------------------------------------------------------------- /backend/src/documents/enum/documentProcessingStatus.enum.ts: -------------------------------------------------------------------------------- 1 | export enum DocumentProcessingStatus { 2 | QUEUED = 'queued', 3 | PROCESSING = 'processing', 4 | COMPLETED = 'completed', 5 | FAILED = 'failed', 6 | RETRY = 'retry', 7 | } -------------------------------------------------------------------------------- /backend/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /backend/src/transaction/dto/update-transaction.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { CreateTransactionDto } from './create-transaction.dto'; 3 | 4 | export class UpdateTransactionDto extends PartialType(CreateTransactionDto) {} 5 | -------------------------------------------------------------------------------- /backend/src/auth/decorators/roles.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | import { UserRole } from 'src/users/entities'; 3 | 4 | export const ROLES_KEY = 'roles'; 5 | export const Roles = (...roles: UserRole[]) => SetMetadata(ROLES_KEY, roles); 6 | -------------------------------------------------------------------------------- /contracts/interfaces/IStateConnector.sol: -------------------------------------------------------------------------------- 1 | interface IStateConnector { 2 | function requestAttestation(bytes calldata _attestationRequest) external returns (bytes32); 3 | function getAttestation(bytes32 _attestationId) external view returns (bool _proved, bytes calldata _data); 4 | } -------------------------------------------------------------------------------- /backend/src/auth/dto/login.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsEmail, IsString, IsOptional } from 'class-validator'; 2 | 3 | export class LoginDto { 4 | @IsEmail() 5 | email: string; 6 | 7 | @IsString() 8 | password: string; 9 | 10 | @IsOptional() 11 | @IsString() 12 | twoFactorCode?: string; 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/auth/dto/wallet-auth.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsString, IsEthereumAddress } from 'class-validator'; 2 | 3 | export class WalletAuthDto { 4 | @IsEthereumAddress() 5 | walletAddress: string; 6 | 7 | @IsString() 8 | signature: string; 9 | 10 | @IsString() 11 | message: string; 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/risk/risk.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { RiskService } from './risk.service'; 3 | import { RiskController } from './risk.controller'; 4 | 5 | @Module({ 6 | controllers: [RiskController], 7 | providers: [RiskService], 8 | }) 9 | export class RiskModule {} 10 | -------------------------------------------------------------------------------- /backend/src/email/email.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { EmailService } from './email.service'; 3 | import { EmailController } from './email.controller'; 4 | 5 | @Module({ 6 | controllers: [EmailController], 7 | providers: [EmailService], 8 | exports: [EmailService], 9 | }) 10 | export class EmailModule {} 11 | -------------------------------------------------------------------------------- /backend/src/redis/redis.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { RedisService } from './redis.service'; 3 | import { RedisController } from './redis.controller'; 4 | 5 | @Module({ 6 | controllers: [RedisController], 7 | providers: [RedisService], 8 | exports: [RedisService], 9 | }) 10 | export class RedisModule {} 11 | -------------------------------------------------------------------------------- /backend/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(): string { 10 | return this.appService.getHello(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /backend/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | moduleFileExtensions: ['ts', 'js', 'json'], 5 | transform: { 6 | '^.+\\.(ts|tsx)$': 'ts-jest', 7 | }, 8 | testMatch: ['**/?(*.)+(spec|test).ts'], 9 | globals: { 10 | 'ts-jest': { 11 | tsconfig: 'tsconfig.json', 12 | }, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /backend/src/auth/decorators/current-user.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 2 | import { Request } from 'express'; 3 | 4 | export const CurrentUser = createParamDecorator( 5 | (data: unknown, ctx: ExecutionContext) => { 6 | const request = ctx.switchToHttp().getRequest(); 7 | return request.user; 8 | }, 9 | ); 10 | -------------------------------------------------------------------------------- /contracts/interfaces/ICreditScoring.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | interface ICreditScoring { 5 | function getCreditScore(uint256 userId) external view returns ( 6 | uint16 creditScore, 7 | uint256 lastUpdated, 8 | uint8 riskLevel, 9 | bool hasTraditionalCredit, 10 | bool hasDeFiActivity 11 | ); 12 | } -------------------------------------------------------------------------------- /backend/__mocks__/ipfs-http-client.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | create: () => ({ 3 | id: async () => ({ id: 'test-node' }), 4 | add: async (content) => ({ cid: { toString: () => 'testcid' } }), 5 | pin: { 6 | add: async () => {}, 7 | ls: async function* () { yield { cid: { toString: () => 'testcid' } }; }, 8 | rm: async () => {}, 9 | }, 10 | }), 11 | }; 12 | -------------------------------------------------------------------------------- /frontend/public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/src/privacy/entities/privacy-assessment.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm'; 2 | 3 | @Entity() 4 | export class PrivacyAssessment { 5 | @PrimaryGeneratedColumn('uuid') 6 | id: string; 7 | 8 | @Column() 9 | userId: string; 10 | 11 | @Column('text') 12 | assessment: string; 13 | 14 | @CreateDateColumn() 15 | createdAt: Date; 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/users/dto/create-profile.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsString, IsOptional, IsObject } from 'class-validator'; 2 | 3 | export class CreateProfileDto { 4 | @IsString() 5 | fullName: string; 6 | 7 | @IsString() 8 | email: string; 9 | 10 | @IsOptional() 11 | @IsString() 12 | walletAddress?: string; 13 | 14 | @IsOptional() 15 | @IsObject() 16 | preferences?: Record; 17 | } 18 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # Wallet private key (NEVER commit this) 2 | PRIVATE_KEY=your_private_key_here 3 | 4 | # Flare network endpoints 5 | FLARE_RPC=https://flare-api.flare.network/ext/C/rpc 6 | SONGBIRD_RPC=https://songbird-api.flare.network/ext/C/rpc 7 | COSTON2_RPC=https://coston2-api.flare.network/ext/C/rpc 8 | 9 | # Contract addresses (will be filled after deployment) 10 | FLARE_CONTRACT_REGISTRY= 11 | FTSO_REGISTRY= 12 | STATE_CONNECTOR= -------------------------------------------------------------------------------- /backend/bankingAnalysis/dto/link.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString } from 'class-validator'; 2 | 3 | export class LinkAccountDto { 4 | @IsString() 5 | @IsNotEmpty() 6 | public_token: string; 7 | 8 | // You would typically get the userId from an authenticated session (e.g., a JWT guard) 9 | // For this example, we'll pass it in the body for simplicity. 10 | @IsString() 11 | @IsNotEmpty() 12 | userId: string; 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/ipfs/entities/ipfs-document.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm'; 2 | 3 | @Entity('ipfs_documents') 4 | export class IpfsDocument { 5 | @PrimaryGeneratedColumn() 6 | id: number; 7 | 8 | @Column() 9 | filename: string; 10 | 11 | @Column() 12 | owner: string; 13 | 14 | @Column() 15 | ipfsHash: string; 16 | 17 | @CreateDateColumn() 18 | createdAt: Date; 19 | } 20 | -------------------------------------------------------------------------------- /backend/src/privacy/entities/consent.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm'; 2 | 3 | @Entity() 4 | export class Consent { 5 | @PrimaryGeneratedColumn('uuid') 6 | id: string; 7 | 8 | @Column() 9 | userId: string; 10 | 11 | @Column() 12 | purpose: string; 13 | 14 | @Column({ default: true }) 15 | granted: boolean; 16 | 17 | @CreateDateColumn() 18 | createdAt: Date; 19 | } 20 | -------------------------------------------------------------------------------- /backend/src/users/dto/create-user-dto.ts: -------------------------------------------------------------------------------- 1 | import { IsString, IsEmail, IsNotEmpty } from 'class-validator'; 2 | 3 | export class CreateUserDto { 4 | // eslint-disable-next-line @typescript-eslint/no-unsafe-call 5 | @IsNotEmpty() 6 | // eslint-disable-next-line @typescript-eslint/no-unsafe-call 7 | @IsString() 8 | fullName: string; 9 | 10 | // eslint-disable-next-line @typescript-eslint/no-unsafe-call 11 | @IsEmail() 12 | email: string; 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/privacy/entities/data-classification.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm'; 2 | 3 | @Entity() 4 | export class DataClassification { 5 | @PrimaryGeneratedColumn('uuid') 6 | id: string; 7 | 8 | @Column() 9 | entityType: string; 10 | 11 | @Column() 12 | entityId: string; 13 | 14 | @Column() 15 | classification: string; 16 | 17 | @CreateDateColumn() 18 | classifiedAt: Date; 19 | } 20 | -------------------------------------------------------------------------------- /backend/src/users/dto/update-profile.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsString, IsOptional, IsObject } from 'class-validator'; 2 | 3 | export class UpdateProfileDto { 4 | @IsOptional() 5 | @IsString() 6 | fullName?: string; 7 | 8 | @IsOptional() 9 | @IsString() 10 | email?: string; 11 | 12 | @IsOptional() 13 | @IsString() 14 | walletAddress?: string; 15 | 16 | @IsOptional() 17 | @IsObject() 18 | preferences?: Record; 19 | } 20 | -------------------------------------------------------------------------------- /backend/src/auth/dto/reset-password.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsString, MinLength, MaxLength, Matches } from 'class-validator'; 2 | 3 | export class ResetPasswordDto { 4 | @IsString() 5 | token: string; 6 | 7 | @IsString() 8 | @MinLength(8) 9 | @MaxLength(100) 10 | @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, { 11 | message: 12 | 'Password must contain uppercase, lowercase, and number/special character', 13 | }) 14 | newPassword: string; 15 | } 16 | -------------------------------------------------------------------------------- /frontend/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /backend/src/users/users.module.ts: -------------------------------------------------------------------------------- 1 | // src/users/users.module.ts 2 | import { Module } from '@nestjs/common'; 3 | import { UsersService } from './users.service'; 4 | import { UsersController } from './users.controller'; 5 | import { TypeOrmModule } from '@nestjs/typeorm'; 6 | import { User } from './entities/user.entity'; 7 | @Module({ 8 | imports: [TypeOrmModule.forFeature([User])], 9 | controllers: [UsersController], 10 | providers: [UsersService], 11 | }) 12 | export class UsersModule {} 13 | -------------------------------------------------------------------------------- /backend/src/credit-bureaus/credit-bureau.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ConfigModule } from '@nestjs/config'; 3 | import { CreditBureauController } from './credit-bureau.controller'; 4 | import { CreditBureauService } from './credit-bureau.service'; 5 | 6 | @Module({ 7 | imports: [ConfigModule], 8 | controllers: [CreditBureauController], 9 | providers: [CreditBureauService], 10 | exports: [CreditBureauService], 11 | }) 12 | export class CreditBureauModule {} 13 | -------------------------------------------------------------------------------- /backend/src/auth/dto/register.dto.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IsEmail, 3 | IsString, 4 | MinLength, 5 | MaxLength, 6 | Matches, 7 | } from 'class-validator'; 8 | 9 | export class RegisterDto { 10 | @IsEmail() 11 | email: string; 12 | 13 | @IsString() 14 | @MinLength(8) 15 | @MaxLength(100) 16 | @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, { 17 | message: 18 | 'Password must contain uppercase, lowercase, and number/special character', 19 | }) 20 | password: string; 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/ipfs/ipfs.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { IpfsService } from './ipfs.service'; 4 | import { IpfsDocumentService } from './ipfs-document.service'; 5 | import { IpfsDocument } from './entities/ipfs-document.entity'; 6 | 7 | @Module({ 8 | imports: [TypeOrmModule.forFeature([IpfsDocument])], 9 | providers: [IpfsService, IpfsDocumentService], 10 | exports: [IpfsService, IpfsDocumentService], 11 | }) 12 | export class IpfsModule {} 13 | -------------------------------------------------------------------------------- /backend/bankingAnalysis/banking-module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ConfigModule } from '@nestjs/config'; 3 | import { BankingController } from './banking.controller'; 4 | import { BankingService } from './banking.service'; 5 | import { PlaidService } from './plaid.service'; 6 | 7 | @Module({ 8 | imports: [ConfigModule], // Make ConfigService available within this module 9 | controllers: [BankingController], 10 | providers: [BankingService, PlaidService], 11 | }) 12 | export class BankingModule {} 13 | -------------------------------------------------------------------------------- /backend/src/auth/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AuthService } from './auth.service'; 3 | 4 | describe('AuthService', () => { 5 | let service: AuthService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [AuthService], 10 | }).compile(); 11 | 12 | service = module.get(AuthService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /backend/src/transaction/transaction.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { TransactionsService } from './transaction.service'; 4 | import { Transaction } from './entities/transaction.entity'; 5 | import { BankAccount } from 'src/banking/entities/bank-account.entity'; 6 | 7 | @Module({ 8 | imports: [TypeOrmModule.forFeature([Transaction, BankAccount])], 9 | providers: [TransactionsService], 10 | exports: [TransactionsService], 11 | }) 12 | export class TransactionsModule {} 13 | -------------------------------------------------------------------------------- /backend/src/email/email.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { EmailService } from './email.service'; 3 | 4 | describe('EmailService', () => { 5 | let service: EmailService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [EmailService], 10 | }).compile(); 11 | 12 | service = module.get(EmailService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /backend/src/plaid/plaid.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { PlaidService } from './plaid.service'; 3 | 4 | describe('PlaidService', () => { 5 | let service: PlaidService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [PlaidService], 10 | }).compile(); 11 | 12 | service = module.get(PlaidService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /backend/src/redis/redis.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { RedisService } from './redis.service'; 3 | 4 | describe('RedisService', () => { 5 | let service: RedisService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [RedisService], 10 | }).compile(); 11 | 12 | service = module.get(RedisService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /backend/src/users/users.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UsersService } from './users.service'; 3 | 4 | describe('UsersService', () => { 5 | let service: UsersService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [UsersService], 10 | }).compile(); 11 | 12 | service = module.get(UsersService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Compiled TypeScript files 5 | /dist 6 | 7 | # Environment variables 8 | # IMPORTANT: Never commit your .env file! 9 | .env 10 | 11 | # Log files 12 | *.log 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/settings.json 20 | !.vscode/tasks.json 21 | !.vscode/launch.json 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | 30 | # Operating System files 31 | .DS_Store 32 | -------------------------------------------------------------------------------- /backend/src/screening/screening.module.ts: -------------------------------------------------------------------------------- 1 | // src/screening/screening.module.ts 2 | @Module({ 3 | imports: [ 4 | TypeOrmModule.forFeature([Watchlist, ScreeningResult, ScreeningMatch]), 5 | BullModule.registerQueue({ 6 | name: 'screening-queue', 7 | }), 8 | ], 9 | providers: [ 10 | ScreeningService, 11 | WatchlistService, 12 | FuzzyMatchingService, 13 | RiskScoringService, 14 | ScreeningProcessor, 15 | ], 16 | controllers: [ScreeningController], 17 | exports: [ScreeningService], 18 | }) 19 | export class ScreeningModule {} 20 | -------------------------------------------------------------------------------- /backend/src/banking/banking.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { BankingService } from './banking.service'; 3 | 4 | describe('BankingService', () => { 5 | let service: BankingService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [BankingService], 10 | }).compile(); 11 | 12 | service = module.get(BankingService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /backend/src/anonymization/dto/anonymize-data.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsString, IsNotEmpty, IsArray, IsNumber } from 'class-validator'; 2 | 3 | export class AnonymizeDataDto { 4 | @IsString() 5 | @IsNotEmpty() 6 | readonly data: string; 7 | } 8 | 9 | export class ReversibilityDto { 10 | @IsString() 11 | @IsNotEmpty() 12 | readonly pseudonym: string; 13 | } 14 | 15 | export class KAnonymityDto { 16 | @IsArray() 17 | @IsNotEmpty() 18 | readonly data: any[]; 19 | } 20 | 21 | export class DifferentialPrivacyDto { 22 | @IsNumber() 23 | @IsNotEmpty() 24 | readonly value: number; 25 | } -------------------------------------------------------------------------------- /backend/src/audit/audit.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { AuditLog } from '../privacy/entities/audit-log.entity'; 4 | import { AuditService } from './audit.service'; 5 | import { AuditController } from './audit.controller'; 6 | import { IpfsModule } from '../ipfs/ipfs.module'; 7 | 8 | @Module({ 9 | imports: [TypeOrmModule.forFeature([AuditLog]), IpfsModule], 10 | controllers: [AuditController], 11 | providers: [AuditService], 12 | exports: [AuditService], 13 | }) 14 | export class AuditModule {} 15 | 16 | 17 | -------------------------------------------------------------------------------- /backend/src/analytics/analytics.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AnalyticsService } from './analytics.service'; 3 | 4 | @Controller('analytics') 5 | export class AnalyticsController { 6 | constructor(private readonly analyticsService: AnalyticsService) {} 7 | 8 | @Get('kyc-funnel') 9 | async getKycFunnel() { 10 | return this.analyticsService.getKycFunnelAnalysis(); 11 | } 12 | 13 | @Get('credit-score-distribution') 14 | async getCreditScoreDistribution() { 15 | return this.analyticsService.getCreditScoreDistribution(); 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /backend/src/banking/banking.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { BankingService } from './banking.service'; 3 | import { BankingController } from './banking.controller'; 4 | import { TypeOrmModule } from '@nestjs/typeorm'; 5 | import { BankToken } from './entities/bank-token.entity'; 6 | import { BankAccount } from './entities/bank-account.entity'; 7 | 8 | @Module({ 9 | imports: [TypeOrmModule.forFeature([BankToken, BankAccount])], 10 | controllers: [BankingController], 11 | providers: [BankingService], 12 | exports: [BankingService], 13 | }) 14 | export class BankingModule {} 15 | -------------------------------------------------------------------------------- /backend/src/cache/cache-warning.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, OnModuleInit } from '@nestjs/common'; 2 | import { UsersService } from '../users/users.service'; // Example service 3 | 4 | @Injectable() 5 | export class CacheWarmingService implements OnModuleInit { 6 | constructor(private readonly usersService: UsersService) {} 7 | 8 | async onModuleInit() { 9 | // Pre-populate the cache with frequently accessed users 10 | const frequentUserIds = ['user-1', 'user-2', 'user-3']; 11 | for (const id of frequentUserIds) { 12 | await this.usersService.findOne(id); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /backend/src/main.ts: -------------------------------------------------------------------------------- 1 | // src/main.ts 2 | import { NestFactory } from '@nestjs/core'; 3 | import { AppModule } from './app.module'; 4 | import * as bodyParser from 'body-parser'; 5 | 6 | async function bootstrap() { 7 | const app = await NestFactory.create(AppModule); 8 | 9 | // capture raw body for webhook verification 10 | app.use( 11 | bodyParser.json({ 12 | verify: (req: any, res, buf) => { 13 | // store raw body on request 14 | req.rawBody = buf.toString(); 15 | }, 16 | }), 17 | ); 18 | 19 | await app.listen(process.env.PORT || 3000); 20 | } 21 | bootstrap(); 22 | -------------------------------------------------------------------------------- /backend/src/transaction/transaction.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { TransactionService } from './transaction.service'; 3 | 4 | describe('TransactionService', () => { 5 | let service: TransactionService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [TransactionService], 10 | }).compile(); 11 | 12 | service = module.get(TransactionService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /frontend/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | :root { 4 | --background: #ffffff; 5 | --foreground: #171717; 6 | } 7 | 8 | @theme inline { 9 | --color-background: var(--background); 10 | --color-foreground: var(--foreground); 11 | --font-sans: var(--font-geist-sans); 12 | --font-mono: var(--font-geist-mono); 13 | } 14 | 15 | @media (prefers-color-scheme: dark) { 16 | :root { 17 | --background: #0a0a0a; 18 | --foreground: #ededed; 19 | } 20 | } 21 | 22 | body { 23 | background: var(--background); 24 | color: var(--foreground); 25 | font-family: Arial, Helvetica, sans-serif; 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/users/entities/refresh-token.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | PrimaryGeneratedColumn, 4 | Column, 5 | ManyToOne, 6 | CreateDateColumn, 7 | } from 'typeorm'; 8 | import { User } from './user.entity'; 9 | 10 | @Entity('refresh_tokens') 11 | export class RefreshToken { 12 | @PrimaryGeneratedColumn('uuid') 13 | id: string; 14 | 15 | @Column() 16 | token: string; 17 | 18 | @Column() 19 | expiresAt: Date; 20 | 21 | @Column() 22 | userId: string; 23 | 24 | @ManyToOne(() => User, (user) => user.refreshTokens) 25 | user: User; 26 | 27 | @CreateDateColumn() 28 | createdAt: Date; 29 | } 30 | -------------------------------------------------------------------------------- /backend/src/screening/entities/watchlist.entity.ts: -------------------------------------------------------------------------------- 1 | // src/screening/entities/watchlist.entity.ts 2 | @Entity('watchlists') 3 | export class Watchlist { 4 | @PrimaryGeneratedColumn('uuid') 5 | id: string; 6 | 7 | @Column() 8 | name: string; // OFAC, PEP, Adverse Media, Custom 9 | 10 | @Column() 11 | type: string; // sanctions, pep, adverse_media, custom 12 | 13 | @Column() 14 | source: string; // data source identifier 15 | 16 | @Column({ type: 'jsonb' }) 17 | data: any; // flexible data storage 18 | 19 | @CreateDateColumn() 20 | createdAt: Date; 21 | 22 | @UpdateDateColumn() 23 | updatedAt: Date; 24 | } 25 | -------------------------------------------------------------------------------- /backend/src/screening/entities/screening-match.entity.ts: -------------------------------------------------------------------------------- 1 | // src/screening/entities/ 2 | @Entity('screening_matches') 3 | export class ScreeningMatch { 4 | @PrimaryGeneratedColumn('uuid') 5 | id: string; 6 | 7 | @ManyToOne(() => ScreeningResult) 8 | screeningResult: ScreeningResult; 9 | 10 | @Column() 11 | watchlistId: string; 12 | 13 | @Column() 14 | matchedField: string; // 'name', 'passport', etc. 15 | 16 | @Column() 17 | matchScore: number; // 0-100 similarity score 18 | 19 | @Column({ type: 'jsonb' }) 20 | matchDetails: any; 21 | 22 | @Column() 23 | riskLevel: string; // 'low', 'medium', 'high' 24 | } 25 | -------------------------------------------------------------------------------- /backend/src/migration/1754578035383-AddEmailIndexToUsers.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class AddEmailIndexToUsers1754578035383 implements MigrationInterface { 4 | name = 'AddEmailIndexToUsers1754578035383'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `CREATE INDEX "IDX_97672ac88f789774dd47f7c8be" ON "users" ("email") `, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `DROP INDEX "public"."IDX_97672ac88f789774dd47f7c8be"`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /backend/src/analytics/analytics.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AnalyticsController } from './analytics.controller'; 3 | import { AnalyticsService } from './analytics.service'; 4 | import { InfluxdbModule } from '../influxdb/influxdb.module'; 5 | 6 | @Module({ 7 | imports: [ 8 | // Import the InfluxdbModule to make the database client available. 9 | InfluxdbModule, 10 | ], 11 | controllers: [AnalyticsController], 12 | providers: [AnalyticsService], 13 | // We export the service so other parts of the app can use it to track events. 14 | exports: [AnalyticsService], 15 | }) 16 | export class AnalyticsModule {} 17 | 18 | -------------------------------------------------------------------------------- /contracts/interfaces/IKYCRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "../utils/KYCTypes"; 5 | 6 | 7 | interface IKYCRegistry { 8 | function API_CONSUMER_ROLE() external view returns (bytes32); 9 | function grantRole(bytes32 role, address account) external; 10 | function isKYCValid(uint256 userId) external view returns (bool); 11 | function getUserKYCInfo(uint256 userId) external view returns ( 12 | KYCTypes.KYCStatus status, 13 | uint256 verificationTimestamp, 14 | uint256 expirationTimestamp, 15 | uint8 complianceScore, 16 | string memory jurisdiction 17 | ); 18 | } -------------------------------------------------------------------------------- /backend/src/auth/auth.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AuthController } from './auth.controller'; 3 | import { AuthService } from './auth.service'; 4 | 5 | describe('AuthController', () => { 6 | let controller: AuthController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [AuthController], 11 | providers: [AuthService], 12 | }).compile(); 13 | 14 | controller = module.get(AuthController); 15 | }); 16 | 17 | it('should be defined', () => { 18 | expect(controller).toBeDefined(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /backend/src/verification/dto/verification-request.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsString, IsOptional, IsNotEmpty } from 'class-validator'; 2 | 3 | export class VerificationRequestDto { 4 | @IsNotEmpty() 5 | @IsString() 6 | userId: string; // The user ID requesting verification 7 | 8 | @IsNotEmpty() 9 | @IsString() 10 | documentType: string; // Document type (e.g., passport, ID card) 11 | 12 | @IsNotEmpty() 13 | @IsString() 14 | documentImage: string; // Base64-encoded image of the document (if applicable) 15 | 16 | @IsOptional() 17 | @IsString() 18 | selfieImage?: string; // Optional: Base64-encoded image of the selfie (for biometric verification) 19 | } 20 | -------------------------------------------------------------------------------- /backend/src/documents/multer.config.ts: -------------------------------------------------------------------------------- 1 | import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface'; 2 | import { memoryStorage } from 'multer'; 3 | import { ALLOWED_MIME_TYPES, MAX_FILE_SIZE_BYTES } from './config'; 4 | 5 | export const multerConfig: MulterOptions = { 6 | limits: { 7 | fileSize: MAX_FILE_SIZE_BYTES, 8 | }, 9 | fileFilter: (req, file, cb) => { 10 | if (ALLOWED_MIME_TYPES.includes((file.mimetype || '').toLowerCase())) { 11 | cb(null, true); 12 | } else { 13 | cb(new Error('Invalid file type. Only PDF, JPG, and PNG are allowed.'), false); 14 | } 15 | }, 16 | storage: memoryStorage(), 17 | }; 18 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /backend/src/email/email.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { EmailController } from './email.controller'; 3 | import { EmailService } from './email.service'; 4 | 5 | describe('EmailController', () => { 6 | let controller: EmailController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [EmailController], 11 | providers: [EmailService], 12 | }).compile(); 13 | 14 | controller = module.get(EmailController); 15 | }); 16 | 17 | it('should be defined', () => { 18 | expect(controller).toBeDefined(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /backend/src/plaid/plaid.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { PlaidController } from './plaid.controller'; 3 | import { PlaidService } from './plaid.service'; 4 | 5 | describe('PlaidController', () => { 6 | let controller: PlaidController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [PlaidController], 11 | providers: [PlaidService], 12 | }).compile(); 13 | 14 | controller = module.get(PlaidController); 15 | }); 16 | 17 | it('should be defined', () => { 18 | expect(controller).toBeDefined(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /backend/src/plaid/plaid.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { PlaidService } from './plaid.service'; 4 | import { PlaidController } from './plaid.controller'; 5 | import { BankToken } from 'src/banking/entities/bank-token.entity'; 6 | import { BankAccount } from 'src/banking/entities/bank-account.entity'; 7 | import { BankingModule } from 'src/banking/banking.module'; 8 | 9 | @Module({ 10 | imports: [TypeOrmModule.forFeature([BankToken, BankAccount]), BankingModule], 11 | providers: [PlaidService], 12 | controllers: [PlaidController], 13 | exports: [PlaidService], 14 | }) 15 | export class PlaidModule {} 16 | -------------------------------------------------------------------------------- /backend/src/redis/redis.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { RedisController } from './redis.controller'; 3 | import { RedisService } from './redis.service'; 4 | 5 | describe('RedisController', () => { 6 | let controller: RedisController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [RedisController], 11 | providers: [RedisService], 12 | }).compile(); 13 | 14 | controller = module.get(RedisController); 15 | }); 16 | 17 | it('should be defined', () => { 18 | expect(controller).toBeDefined(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /backend/src/users/users.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UsersController } from './users.controller'; 3 | import { UsersService } from './users.service'; 4 | 5 | describe('UsersController', () => { 6 | let controller: UsersController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [UsersController], 11 | providers: [UsersService], 12 | }).compile(); 13 | 14 | controller = module.get(UsersController); 15 | }); 16 | 17 | it('should be defined', () => { 18 | expect(controller).toBeDefined(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /backend/src/banking/banking.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { BankingController } from './banking.controller'; 3 | import { BankingService } from './banking.service'; 4 | 5 | describe('BankingController', () => { 6 | let controller: BankingController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [BankingController], 11 | providers: [BankingService], 12 | }).compile(); 13 | 14 | controller = module.get(BankingController); 15 | }); 16 | 17 | it('should be defined', () => { 18 | expect(controller).toBeDefined(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /backend/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /backend/src/api-gateway/dto/service-instance.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsString, IsOptional, IsNumber, IsBoolean, IsUrl } from 'class-validator'; 2 | import { ApiProperty } from '@nestjs/swagger'; 3 | 4 | export class CreateServiceInstanceDto { 5 | @ApiProperty({ description: 'Service instance ID' }) 6 | @IsString() 7 | id: string; 8 | 9 | @ApiProperty({ description: 'Service instance URL' }) 10 | @IsUrl() 11 | url: string; 12 | 13 | @ApiProperty({ description: 'Load balancing weight', required: false, default: 1 }) 14 | @IsOptional() 15 | @IsNumber() 16 | weight?: number; 17 | 18 | @ApiProperty({ description: 'Health status', required: false, default: true }) 19 | @IsOptional() 20 | @IsBoolean() 21 | isHealthy?: boolean; 22 | } -------------------------------------------------------------------------------- /backend/src/transaction/transaction.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { TransactionController } from './transaction.controller'; 3 | import { TransactionService } from './transaction.service'; 4 | 5 | describe('TransactionController', () => { 6 | let controller: TransactionController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [TransactionController], 11 | providers: [TransactionService], 12 | }).compile(); 13 | 14 | controller = module.get(TransactionController); 15 | }); 16 | 17 | it('should be defined', () => { 18 | expect(controller).toBeDefined(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "declaration": true, 6 | "removeComments": true, 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "allowSyntheticDefaultImports": true, 10 | "target": "ES2023", 11 | "sourceMap": true, 12 | "outDir": "./dist", 13 | "baseUrl": "./", 14 | "incremental": true, 15 | "skipLibCheck": true, 16 | "strictNullChecks": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "noImplicitAny": false, 19 | "strictBindCallApply": false, 20 | "noFallthroughCasesInSwitch": false 21 | }, 22 | "include": ["src/**/*.ts"], 23 | "exclude": ["node_modules", "dist"] 24 | } 25 | -------------------------------------------------------------------------------- /backend/src/banking/banking.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { CreateBankingDto } from './dto/create-banking.dto'; 3 | import { UpdateBankingDto } from './dto/update-banking.dto'; 4 | 5 | @Injectable() 6 | export class BankingService { 7 | create(createBankingDto: CreateBankingDto) { 8 | return 'This action adds a new banking'; 9 | } 10 | 11 | findAll() { 12 | return `This action returns all banking`; 13 | } 14 | 15 | findOne(id: number) { 16 | return `This action returns a #${id} banking`; 17 | } 18 | 19 | update(id: number, updateBankingDto: UpdateBankingDto) { 20 | return `This action updates a #${id} banking`; 21 | } 22 | 23 | remove(id: number) { 24 | return `This action removes a #${id} banking`; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/privacy/privacy.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { PrivacyController } from './privacy.controller'; 3 | import { PrivacyService } from './privacy.service'; 4 | import { TypeOrmModule } from '@nestjs/typeorm'; 5 | import { Consent } from './entities/consent.entity'; 6 | import { AuditLog } from './entities/audit-log.entity'; 7 | import { DataClassification } from './entities/data-classification.entity'; 8 | import { PrivacyAssessment } from './entities/privacy-assessment.entity'; 9 | 10 | @Module({ 11 | imports: [TypeOrmModule.forFeature([Consent, AuditLog, DataClassification, PrivacyAssessment])], 12 | controllers: [PrivacyController], 13 | providers: [PrivacyService], 14 | exports: [PrivacyService], 15 | }) 16 | export class PrivacyModule {} 17 | -------------------------------------------------------------------------------- /backend/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { App } from 'supertest/types'; 5 | import { AppModule } from './../src/app.module'; 6 | 7 | describe('AppController (e2e)', () => { 8 | let app: INestApplication; 9 | 10 | beforeEach(async () => { 11 | const moduleFixture: TestingModule = await Test.createTestingModule({ 12 | imports: [AppModule], 13 | }).compile(); 14 | 15 | app = moduleFixture.createNestApplication(); 16 | await app.init(); 17 | }); 18 | 19 | it('/ (GET)', () => { 20 | return request(app.getHttpServer()) 21 | .get('/') 22 | .expect(200) 23 | .expect('Hello World!'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /backend/src/api-gateway/entities/service-health.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | PrimaryGeneratedColumn, 4 | Column, 5 | CreateDateColumn, 6 | Index, 7 | } from 'typeorm'; 8 | 9 | @Entity('service_health') 10 | @Index(['serviceName', 'timestamp']) 11 | export class ServiceHealth { 12 | @PrimaryGeneratedColumn('uuid') 13 | id: string; 14 | 15 | @Column() 16 | serviceName: string; 17 | 18 | @Column() 19 | endpoint: string; 20 | 21 | @Column() 22 | status: 'healthy' | 'unhealthy' | 'degraded'; 23 | 24 | @Column({ type: 'int' }) 25 | responseTime: number; 26 | 27 | @Column({ nullable: true }) 28 | errorMessage: string; 29 | 30 | @Column({ type: 'json', nullable: true }) 31 | metadata: Record; 32 | 33 | @CreateDateColumn() 34 | timestamp: Date; 35 | } -------------------------------------------------------------------------------- /backend/src/auth/strategies/local.strategy.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unsafe-assignment */ 2 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 3 | import { PassportStrategy } from '@nestjs/passport'; 4 | import { Strategy } from 'passport-local'; 5 | import { AuthService } from '../auth.service'; 6 | 7 | @Injectable() 8 | export class LocalStrategy extends PassportStrategy(Strategy) { 9 | constructor(private authService: AuthService) { 10 | super({ usernameField: 'email' }); 11 | } 12 | 13 | async validate(email: string, password: string): Promise { 14 | const user = await this.authService.validateUser(email, password); 15 | if (!user) { 16 | throw new UnauthorizedException('Invalid credentials'); 17 | } 18 | return user; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/migration/1754574370959-CreateUserTable.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class CreateUserTable1754574370959 implements MigrationInterface { 4 | name = 'CreateUserTable1754574370959'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `CREATE TABLE "users" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "fullName" character varying(100) NOT NULL, "email" character varying NOT NULL, "isActive" boolean NOT NULL DEFAULT true, CONSTRAINT "UQ_97672ac88f789774dd47f7c8be3" UNIQUE ("email"), CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`DROP TABLE "users"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /backend/src/verification/dto/verification-response.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsString, IsNotEmpty, IsOptional } from 'class-validator'; 2 | 3 | export class VerificationResponseDto { 4 | @IsNotEmpty() 5 | @IsString() 6 | verificationStatus: string; // Status of the verification (e.g., 'verified', 'failed') 7 | 8 | @IsOptional() 9 | @IsString() 10 | failureReason?: string; // Reason for failure, if any 11 | 12 | @IsOptional() 13 | @IsString() 14 | verificationProvider?: string; // Provider used for the verification (e.g., 'Jumio', 'Onfido') 15 | 16 | @IsOptional() 17 | @IsString() 18 | verificationId?: string; // Unique ID assigned to this verification (by provider) 19 | 20 | @IsOptional() 21 | @IsString() 22 | additionalInfo?: string; // Any additional info provided by the provider (e.g., match score, warnings) 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "commonjs", 5 | "lib": ["es2022"], 6 | "outDir": "./dist", 7 | "rootDir": "./", 8 | "removeComments": true, 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "resolveJsonModule": true, 14 | "declaration": true, 15 | "declarationMap": true, 16 | "sourceMap": true, 17 | "typeRoots": ["./node_modules/@types", "./typechain-types"], 18 | "types": ["node", "mocha", "chai"] 19 | }, 20 | "include": [ 21 | "./contracts/**/*", 22 | "./test/**/*", 23 | "./scripts/**/*", 24 | "./typechain-types/**/*" 25 | ], 26 | "exclude": ["node_modules", "dist", "cache", "artifacts"], 27 | "files": ["./hardhat.config.ts"] 28 | } 29 | -------------------------------------------------------------------------------- /backend/src/screening/processors/screening.processor.ts: -------------------------------------------------------------------------------- 1 | // src/screening/processors/screening.processor.ts 2 | @Processor('screening-queue') 3 | export class ScreeningProcessor { 4 | constructor(private screeningService: ScreeningService) {} 5 | 6 | @Process('screen-entity') 7 | async handleScreening(job: Job) { 8 | const { entityId, entityType, screeningData } = job.data; 9 | 10 | try { 11 | const result = await this.screeningService.performScreening( 12 | entityId, 13 | entityType, 14 | screeningData, 15 | ); 16 | 17 | console.log( 18 | `Screening completed for entity ${entityId}: ${result.status}`, 19 | ); 20 | return result; 21 | } catch (error) { 22 | console.error(`Screening failed for entity ${entityId}:`, error); 23 | throw error; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /frontend/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Geist, Geist_Mono } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const geistSans = Geist({ 6 | variable: "--font-geist-sans", 7 | subsets: ["latin"], 8 | }); 9 | 10 | const geistMono = Geist_Mono({ 11 | variable: "--font-geist-mono", 12 | subsets: ["latin"], 13 | }); 14 | 15 | export const metadata: Metadata = { 16 | title: "Create Next App", 17 | description: "Generated by create next app", 18 | }; 19 | 20 | export default function RootLayout({ 21 | children, 22 | }: Readonly<{ 23 | children: React.ReactNode; 24 | }>) { 25 | return ( 26 | 27 | 30 | {children} 31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /backend/src/banking/entities/bank-token.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | PrimaryGeneratedColumn, 4 | Column, 5 | CreateDateColumn, 6 | UpdateDateColumn, 7 | } from 'typeorm'; 8 | 9 | @Entity({ name: 'bank_tokens' }) 10 | export class BankToken { 11 | @PrimaryGeneratedColumn('uuid') 12 | id: string; 13 | 14 | @Column() 15 | provider: string; // 'plaid', 'yodlee', 'openbanking-uk', ... 16 | 17 | @Column() 18 | itemId: string; // provider item id (e.g. plaid item_id) 19 | 20 | @Column({ type: 'text' }) 21 | encryptedToken: string; 22 | 23 | @Column({ nullable: true }) 24 | refreshToken?: string; // encrypt if needed similarly 25 | 26 | @Column({ type: 'jsonb', default: {} }) 27 | meta: Record; 28 | 29 | @CreateDateColumn() 30 | createdAt: Date; 31 | 32 | @UpdateDateColumn() 33 | updatedAt: Date; 34 | } 35 | -------------------------------------------------------------------------------- /backend/src/ipfs/ipfs-document.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { Repository } from 'typeorm'; 4 | import { IpfsDocument } from './entities/ipfs-document.entity'; 5 | 6 | @Injectable() 7 | export class IpfsDocumentService { 8 | constructor( 9 | @InjectRepository(IpfsDocument) 10 | private readonly ipfsDocumentRepository: Repository, 11 | ) {} 12 | 13 | async saveMetadata(filename: string, owner: string, ipfsHash: string): Promise { 14 | const doc = this.ipfsDocumentRepository.create({ filename, owner, ipfsHash }); 15 | return this.ipfsDocumentRepository.save(doc); 16 | } 17 | 18 | async findByHash(ipfsHash: string): Promise { 19 | return this.ipfsDocumentRepository.findOne({ where: { ipfsHash } }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/screening/scripts/populate-watchlist.ts: -------------------------------------------------------------------------------- 1 | // Create a script to populate initial watchlist data 2 | 3 | import { WatchlistService } from "../services/watchlist.service"; 4 | 5 | // src/screening/scripts/populate-watchlists.ts 6 | export async function populateWatchlists(watchlistService: WatchlistService) { 7 | // Sample OFAC data 8 | const ofacData = [ 9 | { 10 | name: 'John Doe', 11 | type: 'individual', 12 | country: 'XX', 13 | reason: 'sanctions', 14 | }, 15 | // Add more sample data 16 | ]; 17 | 18 | await watchlistService.bulkImportWatchlistData('sanctions', 'ofac', ofacData); 19 | 20 | // Sample PEP data 21 | const pepData = [ 22 | { name: 'Jane Smith', position: 'Minister', country: 'YY', risk: 'high' }, 23 | // Add more sample data 24 | ]; 25 | 26 | await watchlistService.bulkImportWatchlistData('pep', 'custom', pepData); 27 | } 28 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --turbopack", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "format": "prettier --write .", 11 | "format:check": "prettier --check ." 12 | }, 13 | "dependencies": { 14 | "ethers": "^6.15.0", 15 | "framer-motion": "^12.23.12", 16 | "lucide-react": "^0.542.0", 17 | "next": "15.4.5", 18 | "react": "19.1.0", 19 | "react-dom": "19.1.0", 20 | "web3": "^4.16.0" 21 | }, 22 | "devDependencies": { 23 | "@eslint/eslintrc": "^3", 24 | "@tailwindcss/postcss": "^4", 25 | "@types/node": "^20.19.9", 26 | "@types/react": "^19", 27 | "@types/react-dom": "^19", 28 | "eslint": "^9", 29 | "eslint-config-next": "15.4.5", 30 | "tailwindcss": "^4", 31 | "typescript": "^5" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # credora 2 | # Credora — Decentralized KYC & Credit Scoring Protocol 3 | 4 | Credora is a decentralized identity and credit scoring protocol built on the Flare network. It enables users to verify their identity, build on-chain/off-chain reputation, and interact with DeFi applications in a privacy-preserving, composable way. 5 | 6 | 7 | 8 | ## Tech Stack 9 | 10 | - **Solidity** — Smart contract language 11 | - **Hardhat** — Development and testing framework 12 | - **TypeScript** — Scripts and testing 13 | - **Flare Network** — Oracle-native EVM-compatible chain 14 | - **IPFS** (optional) — Decentralized encrypted data storage 15 | - **Zero-Knowledge Proofs** (future) — Selective identity disclosure 16 | 17 | 18 | ## Getting Started 19 | 20 | ### 1. Install dependencies 21 | 22 | ```bash 23 | npm install 24 | 25 | ### 2. Compile Contracts 26 | npx hardhat compile 27 | 28 | 29 | ### 3. Run Tests 30 | npx hardhat test 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /backend/src/screening/entities/screening-result.entity.ts: -------------------------------------------------------------------------------- 1 | // src/screening/entities/screening-result.entity.ts 2 | @Entity('screening_results') 3 | export class ScreeningResult { 4 | @PrimaryGeneratedColumn('uuid') 5 | id: string; 6 | 7 | @Column() 8 | entityId: string; // user ID or entity being screened 9 | 10 | @Column() 11 | entityType: string; // 'user', 'company', etc. 12 | 13 | @Column({ type: 'jsonb' }) 14 | screeningData: any; // data that was screened 15 | 16 | @Column() 17 | overallRiskScore: number; // 0-100 18 | 19 | @Column() 20 | status: string; // 'clear', 'potential_match', 'blocked' 21 | 22 | @Column({ type: 'jsonb' }) 23 | matches: any[]; // array of match details 24 | 25 | @Column({ default: false }) 26 | isFalsePositive: boolean; 27 | 28 | @Column({ nullable: true }) 29 | reviewedBy: string; 30 | 31 | @Column({ nullable: true }) 32 | reviewNotes: string; 33 | 34 | @CreateDateColumn() 35 | screenedAt: Date; 36 | } 37 | -------------------------------------------------------------------------------- /backend/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import eslint from '@eslint/js'; 3 | import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; 4 | import globals from 'globals'; 5 | import tseslint from 'typescript-eslint'; 6 | 7 | export default tseslint.config( 8 | { 9 | ignores: ['eslint.config.mjs'], 10 | }, 11 | eslint.configs.recommended, 12 | ...tseslint.configs.recommendedTypeChecked, 13 | eslintPluginPrettierRecommended, 14 | { 15 | languageOptions: { 16 | globals: { 17 | ...globals.node, 18 | ...globals.jest, 19 | }, 20 | sourceType: 'commonjs', 21 | parserOptions: { 22 | projectService: true, 23 | tsconfigRootDir: import.meta.dirname, 24 | }, 25 | }, 26 | }, 27 | { 28 | rules: { 29 | '@typescript-eslint/no-explicit-any': 'off', 30 | '@typescript-eslint/no-floating-promises': 'warn', 31 | '@typescript-eslint/no-unsafe-argument': 'warn' 32 | }, 33 | }, 34 | ); -------------------------------------------------------------------------------- /backend/src/users/utils/encryption.util.ts: -------------------------------------------------------------------------------- 1 | import * as crypto from 'crypto'; 2 | 3 | const ALGORITHM = 'aes-256-cbc'; 4 | const IV_LENGTH = 16; 5 | 6 | export function encrypt(text: string, key: string): string { 7 | const iv = crypto.randomBytes(IV_LENGTH); 8 | const cipher = crypto.createCipheriv(ALGORITHM, Buffer.from(key, 'hex'), iv); 9 | let encrypted = cipher.update(text, 'utf8', 'hex'); 10 | encrypted += cipher.final('hex'); 11 | return iv.toString('hex') + ':' + encrypted; 12 | } 13 | 14 | export function decrypt(encrypted: string, key: string): string { 15 | const [ivHex, encryptedText] = encrypted.split(':'); 16 | const iv = Buffer.from(ivHex, 'hex'); 17 | const decipher = crypto.createDecipheriv(ALGORITHM, Buffer.from(key, 'hex'), iv); 18 | let decrypted = decipher.update(encryptedText, 'hex', 'utf8'); 19 | decrypted += decipher.final('utf8'); 20 | return decrypted; 21 | } 22 | 23 | // Key management should be handled securely (e.g., env vars, vault) 24 | -------------------------------------------------------------------------------- /backend/src/banking/entities/bank-account.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | PrimaryGeneratedColumn, 4 | Column, 5 | ManyToOne, 6 | JoinColumn, 7 | } from 'typeorm'; 8 | import { BankToken } from './bank-token.entity'; 9 | 10 | @Entity({ name: 'bank_accounts' }) 11 | export class BankAccount { 12 | @PrimaryGeneratedColumn('uuid') 13 | id: string; 14 | 15 | @Column() 16 | providerAccountId: string; // provider-specific account id 17 | 18 | @Column() 19 | name: string; 20 | 21 | @Column() 22 | mask?: string; 23 | 24 | @Column() 25 | type?: string; 26 | 27 | @Column() 28 | subtype?: string; 29 | 30 | @Column('decimal', { precision: 18, scale: 2, nullable: true }) 31 | currentBalance?: number; 32 | 33 | @Column('decimal', { precision: 18, scale: 2, nullable: true }) 34 | availableBalance?: number; 35 | 36 | @ManyToOne(() => BankToken) 37 | @JoinColumn({ name: 'bankTokenId' }) 38 | bankToken: BankToken; 39 | 40 | @Column() 41 | bankTokenId: string; 42 | } 43 | -------------------------------------------------------------------------------- /frontend/public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/src/api-gateway/entities/api-usage.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | PrimaryGeneratedColumn, 4 | Column, 5 | CreateDateColumn, 6 | Index, 7 | } from 'typeorm'; 8 | 9 | @Entity('api_usage') 10 | @Index(['apiKeyId', 'timestamp']) 11 | @Index(['endpoint', 'timestamp']) 12 | export class ApiUsage { 13 | @PrimaryGeneratedColumn('uuid') 14 | id: string; 15 | 16 | @Column() 17 | apiKeyId: string; 18 | 19 | @Column() 20 | endpoint: string; 21 | 22 | @Column() 23 | method: string; 24 | 25 | @Column() 26 | statusCode: number; 27 | 28 | @Column({ type: 'bigint' }) 29 | responseTime: number; 30 | 31 | @Column({ type: 'int', default: 0 }) 32 | requestSize: number; 33 | 34 | @Column({ type: 'int', default: 0 }) 35 | responseSize: number; 36 | 37 | @Column({ type: 'json', nullable: true }) 38 | metadata: Record; 39 | 40 | @Column() 41 | userAgent: string; 42 | 43 | @Column() 44 | ipAddress: string; 45 | 46 | @CreateDateColumn() 47 | timestamp: Date; 48 | } 49 | 50 | 51 | -------------------------------------------------------------------------------- /backend/src/data-source.ts: -------------------------------------------------------------------------------- 1 | import { DataSource, DataSourceOptions } from 'typeorm'; 2 | import { config } from 'dotenv'; 3 | 4 | config(); // Load the .env file 5 | 6 | // This helper function checks if a variable exists, and if not, throws a clear error. 7 | function getEnv(key: string): string { 8 | const value = process.env[key]; 9 | if (typeof value === 'undefined') { 10 | throw new Error( 11 | `Environment variable ${key} is not set. Please check your .env file.`, 12 | ); 13 | } 14 | return value; 15 | } 16 | 17 | export const dataSourceOptions: DataSourceOptions = { 18 | type: 'postgres', 19 | host: getEnv('DB_HOST'), 20 | port: parseInt(getEnv('DB_PORT'), 10), 21 | username: getEnv('DB_USERNAME'), 22 | password: getEnv('DB_PASSWORD'), 23 | database: getEnv('DB_DATABASE'), 24 | entities: ['src/**/*.entity.ts'], 25 | migrations: ['dist/migration/*.js'], 26 | logging: true, 27 | synchronize: false, 28 | }; 29 | 30 | const dataSource = new DataSource(dataSourceOptions); 31 | export default dataSource; 32 | -------------------------------------------------------------------------------- /backend/src/documents/documents.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { BullModule } from '@nestjs/bull'; 4 | import { TypeOrmModule } from '@nestjs/typeorm'; 5 | import { DocumentProcessing } from './entities/document-processing.entity'; 6 | import { DocumentsService } from './documents.service'; 7 | import { DocumentsController } from './documents.controller'; 8 | import { DocumentProcessingService } from './document-processing.service'; 9 | import { S3StorageService } from '../storage/s3-storage.service'; 10 | import { RedisModule } from '../redis/redis.module'; 11 | 12 | @Module({ 13 | imports: [ 14 | BullModule.registerQueue({ 15 | name: 'document-processing', 16 | }), 17 | TypeOrmModule.forFeature([DocumentProcessing]), 18 | RedisModule, 19 | ], 20 | providers: [DocumentsService, DocumentProcessingService, S3StorageService], 21 | controllers: [DocumentsController], 22 | exports: [DocumentsService, DocumentProcessingService, S3StorageService], 23 | }) 24 | export class DocumentsModule {} 25 | -------------------------------------------------------------------------------- /backend/src/redis/redis.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; 2 | import { RedisService } from './redis.service'; 3 | import { CreateRediDto } from './dto/create-redi.dto'; 4 | import { UpdateRediDto } from './dto/update-redi.dto'; 5 | 6 | @Controller('redis') 7 | export class RedisController { 8 | constructor(private readonly redisService: RedisService) {} 9 | 10 | @Post() 11 | create(@Body() createRediDto: CreateRediDto) { 12 | return this.redisService.create(createRediDto); 13 | } 14 | 15 | @Get() 16 | findAll() { 17 | return this.redisService.findAll(); 18 | } 19 | 20 | @Get(':id') 21 | findOne(@Param('id') id: string) { 22 | return this.redisService.findOne(+id); 23 | } 24 | 25 | @Patch(':id') 26 | update(@Param('id') id: string, @Body() updateRediDto: UpdateRediDto) { 27 | return this.redisService.update(+id, updateRediDto); 28 | } 29 | 30 | @Delete(':id') 31 | remove(@Param('id') id: string) { 32 | return this.redisService.remove(+id); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /backend/src/api-gateway/scripts/generate-docs.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; 3 | import { AppModule } from '../../app.module'; 4 | import * as fs from 'fs'; 5 | 6 | async function generateApiDocs() { 7 | const app = await NestFactory.create(AppModule); 8 | 9 | const config = new DocumentBuilder() 10 | .setTitle('Credora API Gateway') 11 | .setDescription('Comprehensive API Gateway with rate limiting, analytics, and load balancing') 12 | .setVersion('1.0') 13 | .addApiKey({ type: 'apiKey', name: 'X-API-Key', in: 'header' }, 'ApiKeyAuth') 14 | .addBearerAuth({ type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }, 'BearerAuth') 15 | .build(); 16 | 17 | const document = SwaggerModule.createDocument(app, config); 18 | 19 | 20 | fs.writeFileSync('./api-docs.json', JSON.stringify(document, null, 2)); 21 | console.log('API documentation generated: api-docs.json'); 22 | 23 | await app.close(); 24 | } 25 | 26 | generateApiDocs().catch(console.error); 27 | -------------------------------------------------------------------------------- /backend/src/email/email.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; 2 | import { EmailService } from './email.service'; 3 | import { CreateEmailDto } from './dto/create-email.dto'; 4 | import { UpdateEmailDto } from './dto/update-email.dto'; 5 | 6 | @Controller('email') 7 | export class EmailController { 8 | constructor(private readonly emailService: EmailService) {} 9 | 10 | @Post() 11 | create(@Body() createEmailDto: CreateEmailDto) { 12 | return this.emailService.create(createEmailDto); 13 | } 14 | 15 | @Get() 16 | findAll() { 17 | return this.emailService.findAll(); 18 | } 19 | 20 | @Get(':id') 21 | findOne(@Param('id') id: string) { 22 | return this.emailService.findOne(+id); 23 | } 24 | 25 | @Patch(':id') 26 | update(@Param('id') id: string, @Body() updateEmailDto: UpdateEmailDto) { 27 | return this.emailService.update(+id, updateEmailDto); 28 | } 29 | 30 | @Delete(':id') 31 | remove(@Param('id') id: string) { 32 | return this.emailService.remove(+id); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /backend/src/auth/strategies/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { PassportStrategy } from '@nestjs/passport'; 3 | import { ExtractJwt, Strategy } from 'passport-jwt'; 4 | import { ConfigService } from '@nestjs/config'; 5 | 6 | export interface JwtPayload { 7 | sub: string; 8 | email: string; 9 | role: string; 10 | iat?: number; 11 | exp?: number; 12 | } 13 | 14 | @Injectable() 15 | export class JwtStrategy extends PassportStrategy(Strategy) { 16 | constructor(private configService: ConfigService) { 17 | const jwtSecret = configService.get('JWT_SECRET'); 18 | if (!jwtSecret) { 19 | throw new Error('JWT_SECRET is not defined in environment variables'); 20 | } 21 | super({ 22 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 23 | ignoreExpiration: false, 24 | secretOrKey: jwtSecret, 25 | }); 26 | } 27 | 28 | validate(payload: JwtPayload) { 29 | return { 30 | userId: payload.sub, 31 | email: payload.email, 32 | role: payload.role, 33 | }; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /backend/src/api-gateway/entities/api-key.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | PrimaryGeneratedColumn, 4 | Column, 5 | CreateDateColumn, 6 | UpdateDateColumn, 7 | Index, 8 | } from 'typeorm'; 9 | 10 | @Entity('api_keys') 11 | @Index(['key']) 12 | @Index(['userId']) 13 | export class ApiKey { 14 | @PrimaryGeneratedColumn('uuid') 15 | id: string; 16 | 17 | @Column({ unique: true }) 18 | key: string; 19 | 20 | @Column() 21 | userId: string; 22 | 23 | @Column({ nullable: true }) 24 | name: string; 25 | 26 | @Column({ default: true }) 27 | isActive: boolean; 28 | 29 | @Column({ type: 'json', nullable: true }) 30 | permissions: string[]; 31 | 32 | @Column({ type: 'int', default: 1000 }) 33 | rateLimit: number; 34 | 35 | @Column({ default: 'hour' }) 36 | rateLimitPeriod: string; 37 | 38 | @Column({ type: 'timestamp', nullable: true }) 39 | expiresAt: Date; 40 | 41 | @Column({ type: 'timestamp', nullable: true }) 42 | lastUsedAt: Date; 43 | 44 | @CreateDateColumn() 45 | createdAt: Date; 46 | 47 | @UpdateDateColumn() 48 | updatedAt: Date; 49 | } -------------------------------------------------------------------------------- /backend/src/auth/guards/jwt-auth.guard.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | /* eslint-disable @typescript-eslint/no-unsafe-return */ 3 | import { 4 | Injectable, 5 | ExecutionContext, 6 | UnauthorizedException, 7 | } from '@nestjs/common'; 8 | import { AuthGuard } from '@nestjs/passport'; 9 | import { Reflector } from '@nestjs/core'; 10 | import { IS_PUBLIC_KEY } from '../decorators/public.decorator'; 11 | 12 | @Injectable() 13 | export class JwtAuthGuard extends AuthGuard('jwt') { 14 | constructor(private reflector: Reflector) { 15 | super(); 16 | } 17 | 18 | canActivate(context: ExecutionContext) { 19 | const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ 20 | context.getHandler(), 21 | context.getClass(), 22 | ]); 23 | if (isPublic) { 24 | return true; 25 | } 26 | return super.canActivate(context); 27 | } 28 | 29 | handleRequest(err: any, user: any, info: any) { 30 | if (err || !user) { 31 | throw err || new UnauthorizedException(); 32 | } 33 | return user; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /backend/src/banking/banking.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; 2 | import { BankingService } from './banking.service'; 3 | import { CreateBankingDto } from './dto/create-banking.dto'; 4 | import { UpdateBankingDto } from './dto/update-banking.dto'; 5 | 6 | @Controller('banking') 7 | export class BankingController { 8 | constructor(private readonly bankingService: BankingService) {} 9 | 10 | @Post() 11 | create(@Body() createBankingDto: CreateBankingDto) { 12 | return this.bankingService.create(createBankingDto); 13 | } 14 | 15 | @Get() 16 | findAll() { 17 | return this.bankingService.findAll(); 18 | } 19 | 20 | @Get(':id') 21 | findOne(@Param('id') id: string) { 22 | return this.bankingService.findOne(+id); 23 | } 24 | 25 | @Patch(':id') 26 | update(@Param('id') id: string, @Body() updateBankingDto: UpdateBankingDto) { 27 | return this.bankingService.update(+id, updateBankingDto); 28 | } 29 | 30 | @Delete(':id') 31 | remove(@Param('id') id: string) { 32 | return this.bankingService.remove(+id); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /backend/src/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AuthService } from './auth.service'; 3 | import { AuthController } from './auth.controller'; 4 | import { TypeOrmModule } from '@nestjs/typeorm'; 5 | import { User, RefreshToken } from '../users/entities'; 6 | import { EmailModule } from 'src/email/email.module'; 7 | import { RedisModule } from 'src/redis/redis.module'; 8 | import { JwtModule } from '@nestjs/jwt'; 9 | import { PassportModule } from '@nestjs/passport'; 10 | import { ConfigModule, ConfigService } from '@nestjs/config'; 11 | 12 | @Module({ 13 | imports: [ 14 | TypeOrmModule.forFeature([User, RefreshToken]), 15 | EmailModule, 16 | RedisModule, 17 | JwtModule.registerAsync({ 18 | imports: [ConfigModule], 19 | useFactory: (configService: ConfigService) => ({ 20 | secret: configService.get('JWT_SECRET'), 21 | signOptions: { expiresIn: '15m' }, 22 | }), 23 | inject: [ConfigService], 24 | }), 25 | PassportModule, 26 | ], 27 | controllers: [AuthController], 28 | providers: [AuthService], 29 | }) 30 | export class AuthModule {} 31 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomicfoundation/hardhat-toolbox"); 2 | require("@openzeppelin/hardhat-upgrades"); 3 | 4 | const PRIVATE_KEY = process.env.PRIVATE_KEY || ""; 5 | 6 | module.exports = { 7 | solidity: { 8 | version: "0.8.19", 9 | settings: { 10 | optimizer: { 11 | enabled: true, 12 | runs: 200 13 | } 14 | } 15 | }, 16 | networks: { 17 | // Flare Mainnet 18 | flare: { 19 | url: "https://flare-api.flare.network/ext/C/rpc", 20 | accounts: PRIVATE_KEY ? [PRIVATE_KEY] : [], 21 | chainId: 14 22 | }, 23 | // Songbird Testnet 24 | songbird: { 25 | url: "https://songbird-api.flare.network/ext/C/rpc", 26 | accounts: PRIVATE_KEY ? [PRIVATE_KEY] : [], 27 | chainId: 19 28 | }, 29 | // Coston2 Testnet (recommended for testing) 30 | coston2: { 31 | url: "https://coston2-api.flare.network/ext/C/rpc", 32 | accounts: PRIVATE_KEY ? [PRIVATE_KEY] : [], 33 | chainId: 114 34 | } 35 | }, 36 | etherscan: { 37 | apiKey: { 38 | flare: "flare", // placeholder 39 | songbird: "songbird", 40 | coston2: "coston2" 41 | } 42 | } 43 | }; -------------------------------------------------------------------------------- /backend/src/transaction/transaction.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; 2 | import { TransactionService } from './transaction.service'; 3 | import { CreateTransactionDto } from './dto/create-transaction.dto'; 4 | import { UpdateTransactionDto } from './dto/update-transaction.dto'; 5 | 6 | @Controller('transaction') 7 | export class TransactionController { 8 | constructor(private readonly transactionService: TransactionService) {} 9 | 10 | @Post() 11 | create(@Body() createTransactionDto: CreateTransactionDto) { 12 | return this.transactionService.create(createTransactionDto); 13 | } 14 | 15 | @Get() 16 | findAll() { 17 | return this.transactionService.findAll(); 18 | } 19 | 20 | @Get(':id') 21 | findOne(@Param('id') id: string) { 22 | return this.transactionService.findOne(+id); 23 | } 24 | 25 | @Patch(':id') 26 | update(@Param('id') id: string, @Body() updateTransactionDto: UpdateTransactionDto) { 27 | return this.transactionService.update(+id, updateTransactionDto); 28 | } 29 | 30 | @Delete(':id') 31 | remove(@Param('id') id: string) { 32 | return this.transactionService.remove(+id); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /backend/src/transaction/entities/transaction.entity.ts: -------------------------------------------------------------------------------- 1 | import { BankAccount } from 'src/banking/entities/bank-account.entity'; 2 | import { 3 | Entity, 4 | PrimaryGeneratedColumn, 5 | Column, 6 | ManyToOne, 7 | JoinColumn, 8 | Index, 9 | CreateDateColumn, 10 | } from 'typeorm'; 11 | 12 | @Entity({ name: 'transactions' }) 13 | export class Transaction { 14 | @PrimaryGeneratedColumn('uuid') 15 | id: string; 16 | 17 | @Index() 18 | @Column() 19 | providerTransactionId: string; // external id from provider 20 | 21 | @ManyToOne(() => BankAccount) 22 | @JoinColumn({ name: 'bankAccountId' }) 23 | bankAccount: BankAccount; 24 | 25 | @Column() 26 | bankAccountId: string; 27 | 28 | @Column({ type: 'date' }) 29 | date: string; 30 | 31 | @Column('decimal', { precision: 18, scale: 2 }) 32 | amount: number; 33 | 34 | @Column() 35 | currency: string; 36 | 37 | @Column({ nullable: true }) 38 | merchantName?: string; 39 | 40 | @Column({ nullable: true }) 41 | rawDescription?: string; 42 | 43 | @Column({ nullable: true }) 44 | category?: string; 45 | 46 | @Column({ default: false }) 47 | pending: boolean; 48 | 49 | @CreateDateColumn() 50 | createdAt: Date; 51 | } 52 | -------------------------------------------------------------------------------- /backend/src/auth/guards/roles.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | CanActivate, 4 | ExecutionContext, 5 | ForbiddenException, 6 | } from '@nestjs/common'; 7 | import { Reflector } from '@nestjs/core'; 8 | import { UserRole } from '../../users/entities/user.entity'; 9 | import { ROLES_KEY } from '../decorators/roles.decorator'; 10 | 11 | @Injectable() 12 | export class RolesGuard implements CanActivate { 13 | constructor(private reflector: Reflector) {} 14 | 15 | canActivate(context: ExecutionContext): boolean { 16 | const requiredRoles = this.reflector.getAllAndOverride( 17 | ROLES_KEY, 18 | [context.getHandler(), context.getClass()], 19 | ); 20 | if (!requiredRoles) { 21 | return true; 22 | } 23 | 24 | const request = context 25 | .switchToHttp() 26 | .getRequest<{ user?: { role?: UserRole } }>(); 27 | const user = request.user; 28 | if (!user) { 29 | throw new ForbiddenException('User not authenticated'); 30 | } 31 | 32 | const hasRole = requiredRoles.some((role) => user.role === role); 33 | if (!hasRole) { 34 | throw new ForbiddenException('Insufficient permissions'); 35 | } 36 | return true; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /backend/src/cache/cache.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@nestjs/common'; 2 | import { CACHE_MANAGER } from '@nestjs/cache-manager'; 3 | import { Cluster } from 'ioredis'; 4 | 5 | @Injectable() 6 | export class CacheService { 7 | constructor(@Inject(CACHE_MANAGER) private readonly cacheManager: Cluster) {} 8 | 9 | /** 10 | * Implements the cache-aside pattern. 11 | * @param key The cache key. 12 | * @param fallback A function to fetch the data if it's not in the cache. 13 | * @param ttl Time-to-live in seconds. 14 | * @returns The cached or freshly fetched data. 15 | */ 16 | async getOrSet( 17 | key: string, 18 | fallback: () => Promise, 19 | ttl: number, 20 | ): Promise { 21 | const cachedData = await this.cacheManager.get(key); 22 | 23 | if (cachedData) { 24 | return cachedData; 25 | } 26 | 27 | const freshData = await fallback(); 28 | await this.cacheManager.set(key, freshData, ttl); 29 | return freshData; 30 | } 31 | 32 | /** 33 | * Invalidates a cache entry. 34 | * @param key The cache key to delete. 35 | */ 36 | async invalidate(key: string): Promise { 37 | await this.cacheManager.del(key); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /backend/src/api-gateway/entities/api-endpoint.entity.ts: -------------------------------------------------------------------------------- 1 | // src/api-gateway/entities/api-endpoint.entity.ts 2 | import { 3 | Entity, 4 | PrimaryGeneratedColumn, 5 | Column, 6 | CreateDateColumn, 7 | UpdateDateColumn, 8 | Index, 9 | } from 'typeorm'; 10 | 11 | @Entity('api_endpoints') 12 | @Index(['path', 'method']) 13 | @Index(['version']) 14 | export class ApiEndpoint { 15 | @PrimaryGeneratedColumn('uuid') 16 | id: string; 17 | 18 | @Column() 19 | path: string; 20 | 21 | @Column() 22 | method: string; 23 | 24 | @Column() 25 | version: string; 26 | 27 | @Column() 28 | targetUrl: string; 29 | 30 | @Column({ default: true }) 31 | isActive: boolean; 32 | 33 | @Column({ type: 'json', nullable: true }) 34 | transformationRules: Record; 35 | 36 | @Column({ type: 'json', nullable: true }) 37 | rateLimitConfig: Record; 38 | 39 | @Column({ type: 'json', nullable: true }) 40 | circuitBreakerConfig: Record; 41 | 42 | @Column({ type: 'json', nullable: true }) 43 | headers: Record; 44 | 45 | @Column({ type: 'int', default: 30000 }) 46 | timeout: number; 47 | 48 | @CreateDateColumn() 49 | createdAt: Date; 50 | 51 | @UpdateDateColumn() 52 | updatedAt: Date; 53 | } 54 | -------------------------------------------------------------------------------- /backend/src/utils/crypto.utils.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto'; 2 | 3 | const ALGO = 'aes-256-gcm'; 4 | const IV_LEN = 12; // recommended for GCM 5 | 6 | export function encrypt(text: string, base64Key: string) { 7 | const key = Buffer.from(base64Key, 'base64'); 8 | if (key.length !== 32) 9 | throw new Error('TOKEN_ENC_KEY must be base64 of 32 bytes'); 10 | 11 | const iv = crypto.randomBytes(IV_LEN); 12 | const cipher = crypto.createCipheriv(ALGO, key, iv); 13 | const encrypted = Buffer.concat([ 14 | cipher.update(text, 'utf8'), 15 | cipher.final(), 16 | ]); 17 | const tag = cipher.getAuthTag(); 18 | 19 | // package: iv | tag | encrypted, base64 20 | return Buffer.concat([iv, tag, encrypted]).toString('base64'); 21 | } 22 | 23 | export function decrypt(ciphertextB64: string, base64Key: string) { 24 | const key = Buffer.from(base64Key, 'base64'); 25 | const buff = Buffer.from(ciphertextB64, 'base64'); 26 | 27 | const iv = buff.slice(0, IV_LEN); 28 | const tag = buff.slice(IV_LEN, IV_LEN + 16); 29 | const encrypted = buff.slice(IV_LEN + 16); 30 | 31 | const decipher = crypto.createDecipheriv(ALGO, key, iv); 32 | decipher.setAuthTag(tag); 33 | const out = Buffer.concat([decipher.update(encrypted), decipher.final()]); 34 | return out.toString('utf8'); 35 | } 36 | -------------------------------------------------------------------------------- /backend/src/screening/controllers/screening.controller.ts: -------------------------------------------------------------------------------- 1 | // src/screening/controllers/screening.controller.ts 2 | @Controller('screening') 3 | @UseGuards(AuthGuard) // Assuming you have auth guards 4 | export class ScreeningController { 5 | constructor(private screeningService: ScreeningService) {} 6 | 7 | @Post('screen') 8 | async screenEntity(@Body() screenEntityDto: any) { 9 | const jobId = await this.screeningService.screenEntity( 10 | screenEntityDto.entityId, 11 | screenEntityDto.entityType, 12 | screenEntityDto.screeningData, 13 | ); 14 | 15 | return { jobId, message: 'Screening job queued successfully' }; 16 | } 17 | 18 | @Get('result/:id') 19 | async getScreeningResult(@Param('id') id: string) { 20 | return this.screeningService.getScreeningResult(id); 21 | } 22 | 23 | @Post('false-positive/:id') 24 | async markAsFalsePositive( 25 | @Param('id') id: string, 26 | @Body() body: { reviewedBy: string; notes: string }, 27 | ) { 28 | return this.screeningService.markAsFalsePositive( 29 | id, 30 | body.reviewedBy, 31 | body.notes, 32 | ); 33 | } 34 | 35 | @Get('history/:entityId') 36 | async getScreeningHistory(@Param('entityId') entityId: string) { 37 | return this.screeningService.getScreeningHistory(entityId); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /backend/src/verification/verification.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Post, Body, Headers } from '@nestjs/common'; 2 | import { VerificationService } from './verification.service'; 3 | import { VerificationRequestDto } from './dto/verification-request.dto'; 4 | import { VerificationResponseDto } from './dto/verification-response.dto'; 5 | 6 | 7 | @Controller('verification') 8 | export class VerificationController { 9 | constructor(private readonly verificationService: VerificationService) {} 10 | 11 | @Post('webhook/jumio') 12 | handleJumioWebhook(@Body() payload: any, @Headers() headers: any) { 13 | // Process Jumio webhook 14 | console.log('Jumio verification result received:', payload); 15 | // Store or update metadata as needed 16 | } 17 | 18 | @Post('webhook/onfido') 19 | handleOnfidoWebhook(@Body() payload: any, @Headers() headers: any) { 20 | // Process Onfido webhook 21 | console.log('Onfido verification result received:', payload); 22 | // Store or update metadata as needed 23 | } 24 | 25 | // Handle the request to perform identity verification 26 | @Post('perform-verification') 27 | async performVerification( 28 | @Body() verificationData: VerificationRequestDto 29 | ): Promise { 30 | return this.verificationService.performVerification(verificationData); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /frontend/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/.env.example: -------------------------------------------------------------------------------- 1 | REDIS_HOST= 'localhost', 2 | REDIS_PORT= 6379, 3 | REDIS_PASSWORD= 4 | JWT_SECRET= 'your_jwt_secret', 5 | DB_HOST=localhost 6 | DB_PORT=5432 7 | DB_USER=postgres 8 | DB_PASS=postgres 9 | DB_NAME=payments 10 | PLAID_CLIENT_ID=your_plaid_client_id 11 | PLAID_SECRET=your_plaid_secret 12 | PLAID_ENV=sandbox 13 | PLAID_WEBHOOK_URL=https://your-ngrok-url/plaid/webhook 14 | TOKEN_ENC_KEY= 15 | 16 | 17 | 18 | # API Gateway Configuration 19 | API_GATEWAY_URL=http://localhost:3000 20 | API_GATEWAY_RATE_LIMIT_WINDOW=3600000 21 | API_GATEWAY_RATE_LIMIT_MAX=1000 22 | API_GATEWAY_CIRCUIT_BREAKER_FAILURE_THRESHOLD=5 23 | API_GATEWAY_CIRCUIT_BREAKER_SUCCESS_THRESHOLD=3 24 | API_GATEWAY_CIRCUIT_BREAKER_TIMEOUT=60000 25 | API_GATEWAY_HEALTH_CHECK_INTERVAL=30000 26 | 27 | # Documents Configuration 28 | DOC_MAX_SIZE_BYTES=10485760 29 | DOC_ALLOWED_MIME=application/pdf,image/jpeg,image/png 30 | 31 | # S3 Storage (encryption at rest) 32 | S3_REGION=us-east-1 33 | S3_BUCKET= 34 | S3_ACCESS_KEY_ID= 35 | S3_SECRET_ACCESS_KEY= 36 | # Optional KMS key for SSE-KMS (leave empty to use SSE-S3/AES256) 37 | S3_KMS_KEY_ID= 38 | 39 | # ClamAV (fail closed if unavailable) 40 | CLAMAV_HOST=127.0.0.1 41 | CLAMAV_PORT=3310 42 | 43 | # Webhooks 44 | # Comma-separated allowlist of domains (e.g., example.com,webhooks.myapp.com) 45 | WEBHOOK_ALLOWED_DOMAINS= 46 | WEBHOOK_SECRET= -------------------------------------------------------------------------------- /backend/src/documents/config.ts: -------------------------------------------------------------------------------- 1 | import { config } from 'dotenv'; 2 | config(); 3 | 4 | // Centralized configuration for document handling 5 | 6 | export const ALLOWED_MIME_TYPES: string[] = (process.env.DOC_ALLOWED_MIME 7 | ? process.env.DOC_ALLOWED_MIME.split(',') 8 | .map((s) => s.trim()) 9 | .filter(Boolean) 10 | : ['application/pdf', 'image/jpeg', 'image/png'] 11 | ).map((m) => m.toLowerCase()); 12 | 13 | export const MAX_FILE_SIZE_BYTES: number = Number.parseInt( 14 | process.env.DOC_MAX_SIZE_BYTES || `${10 * 1024 * 1024}`, 15 | 10, 16 | ); 17 | 18 | export const CLAMAV_HOST = process.env.CLAMAV_HOST || '127.0.0.1'; 19 | export const CLAMAV_PORT = Number.parseInt(process.env.CLAMAV_PORT || '3310', 10); 20 | 21 | export const WEBHOOK_ALLOWED_DOMAINS: string[] = (process.env.WEBHOOK_ALLOWED_DOMAINS 22 | ? process.env.WEBHOOK_ALLOWED_DOMAINS.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean) 23 | : []); 24 | 25 | export const WEBHOOK_SECRET: string = process.env.WEBHOOK_SECRET || ''; 26 | 27 | export function isHostAllowed(hostname: string): boolean { 28 | if (WEBHOOK_ALLOWED_DOMAINS.length === 0) return false; // default deny 29 | const host = (hostname || '').toLowerCase(); 30 | return WEBHOOK_ALLOWED_DOMAINS.some((allowed) => { 31 | if (host === allowed) return true; 32 | return host.endsWith(`.${allowed}`); 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /backend/src/ipfs/ipfs.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Post, Body, Get, Query } from '@nestjs/common'; 2 | import { IpfsService } from './ipfs.service'; 3 | import { IpfsDocumentService } from './ipfs-document.service'; 4 | 5 | @Controller('ipfs') 6 | export class IpfsController { 7 | constructor( 8 | private readonly ipfsService: IpfsService, 9 | private readonly ipfsDocumentService: IpfsDocumentService, 10 | ) {} 11 | 12 | @Get('health') 13 | async healthCheck() { 14 | return { healthy: await this.ipfsService.healthCheck() }; 15 | } 16 | 17 | @Post('pin') 18 | async pinDocument(@Body('content') content: string, @Body('filename') filename: string, @Body('owner') owner: string) { 19 | const cid = await this.ipfsService.pinDocument(content); 20 | if (cid) { 21 | await this.ipfsDocumentService.saveMetadata(filename || 'unknown', owner || 'unknown', cid); 22 | } 23 | return { cid }; 24 | } 25 | 26 | @Get('status') 27 | async getPinStatus(@Query('cid') cid: string) { 28 | const status = await this.ipfsService.getPinStatus(cid); 29 | return { pinned: status }; 30 | } 31 | 32 | @Post('unpin') 33 | async unpinDocument(@Body('cid') cid: string) { 34 | const result = await this.ipfsService.unpinDocument(cid); 35 | return { success: result }; 36 | } 37 | 38 | @Get('gateway') 39 | getGatewayUrl(@Query('cid') cid: string) { 40 | return { url: this.ipfsService.getGatewayUrl(cid) }; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /backend/src/anonymization/anonymization.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AnonymizationController } from './anonymization.controller'; 3 | import { PiiDetectionService } from './services/pii-detection.service'; 4 | import { PseudonymizationService } from './services/pseudonymization.service'; 5 | 6 | @Module({ 7 | imports: [], 8 | controllers: [AnonymizationController], 9 | providers: [PiiDetectionService, PseudonymizationService], 10 | }) 11 | export class AnonymizationModule {} 12 | 13 | // src/anonymization/services/pii-detection.service.ts 14 | // A service to detect and mask PII using a simple regex-based approach. 15 | import { Injectable } from '@nestjs/common'; 16 | 17 | @Injectable() 18 | export class PiiDetectionService { 19 | /** 20 | * Detects and masks common PII types using regular expressions. 21 | * @param text The input string to be anonymized. 22 | * @returns The string with PII masked. 23 | */ 24 | detectAndMaskPii(text: string): string { 25 | // Basic regex for email, phone numbers, and common names. 26 | const emailRegex = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g; 27 | const phoneRegex = /(\+?\d{1,2}\s?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4})/g; 28 | 29 | let maskedText = text.replace(emailRegex, '[EMAIL_MASKED]'); 30 | maskedText = maskedText.replace(phoneRegex, '[PHONE_MASKED]'); 31 | // Note: A more robust implementation would use a dedicated PII detection library. 32 | return maskedText; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /backend/src/ipfs/ipfs.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { IpfsService } from './ipfs.service'; 3 | 4 | describe('IpfsService', () => { 5 | let service: IpfsService; 6 | 7 | const mockIpfsClient = { 8 | id: async () => ({ id: 'test-node' }), 9 | add: async (content: any) => ({ cid: { toString: () => 'testcid' } }), 10 | pin: { 11 | add: async () => {}, 12 | ls: async function* () { yield { cid: { toString: () => 'testcid' } }; }, 13 | rm: async () => {}, 14 | }, 15 | }; 16 | 17 | beforeAll(async () => { 18 | const module: TestingModule = await Test.createTestingModule({ 19 | providers: [ 20 | { 21 | provide: IpfsService, 22 | useValue: new IpfsService(mockIpfsClient), 23 | }, 24 | ], 25 | }).compile(); 26 | service = module.get(IpfsService); 27 | }); 28 | 29 | it('should be defined', () => { 30 | expect(service).toBeDefined(); 31 | }); 32 | 33 | it('should pin and validate a document', async () => { 34 | const content = 'Hello IPFS!'; 35 | const cid = await service.pinDocument(content); 36 | expect(cid).toBeDefined(); 37 | if (cid) { 38 | // Validate CID matches content 39 | const calculatedCid = await service.calculateContentCid(content); 40 | expect(cid).toEqual(calculatedCid); 41 | // Check pin status 42 | const status = await service.getPinStatus(cid); 43 | expect(typeof status).toBe('boolean'); 44 | } 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /scripts/get-flare-addresses.js: -------------------------------------------------------------------------------- 1 | const { FlareContractRegistry } = require("@flarenetwork/flare-periphery-contracts"); 2 | 3 | async function main() { 4 | const network = hre.network.name; 5 | 6 | // Correct Flare Contract Registry addresses by network 7 | const registryAddresses = { 8 | flare: "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", 9 | songbird: "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", 10 | coston2: "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019" 11 | }; 12 | 13 | const registryAddress = registryAddresses[network]; 14 | console.log(`Flare Contract Registry on ${network}: ${registryAddress}`); 15 | 16 | // Use the correct interface from Flare periphery contracts 17 | const FlareContractRegistryABI = [ 18 | "function getContractAddressByName(string calldata _name) external view returns (address)" 19 | ]; 20 | 21 | const registry = await ethers.getContractAt(FlareContractRegistryABI, registryAddress); 22 | 23 | try { 24 | const ftsoRegistry = await registry.getContractAddressByName("FtsoRegistry"); 25 | const stateConnector = await registry.getContractAddressByName("StateConnector"); 26 | 27 | console.log(`FTSO Registry: ${ftsoRegistry}`); 28 | console.log(`State Connector: ${stateConnector}`); 29 | } catch (error) { 30 | console.log("Error getting contract addresses:", error.message); 31 | console.log("You may need to use hardcoded addresses for testnet"); 32 | } 33 | } 34 | 35 | main().catch((error) => { 36 | console.error(error); 37 | process.exitCode = 1; 38 | }); -------------------------------------------------------------------------------- /backend/src/screening/services/watchlist.service.ts: -------------------------------------------------------------------------------- 1 | // src/screening/services/watchlist.service.ts 2 | @Injectable() 3 | export class WatchlistService { 4 | constructor( 5 | @InjectRepository(Watchlist) 6 | private watchlistRepository: Repository, 7 | ) {} 8 | 9 | async createWatchlist(createWatchlistDto: any): Promise { 10 | const watchlist = this.watchlistRepository.create(createWatchlistDto); 11 | return this.watchlistRepository.save(watchlist); 12 | } 13 | 14 | async getAllWatchlists(): Promise { 15 | return this.watchlistRepository.find(); 16 | } 17 | 18 | async getWatchlistsByType(type: string): Promise { 19 | return this.watchlistRepository.find({ where: { type } }); 20 | } 21 | 22 | async updateWatchlist(id: string, updateData: any): Promise { 23 | await this.watchlistRepository.update(id, updateData); 24 | return this.watchlistRepository.findOne({ where: { id } }); 25 | } 26 | 27 | async deleteWatchlist(id: string): Promise { 28 | await this.watchlistRepository.delete(id); 29 | } 30 | 31 | // Method to bulk import watchlist data 32 | async bulkImportWatchlistData( 33 | type: string, 34 | source: string, 35 | data: any[], 36 | ): Promise { 37 | const watchlistEntries = data.map((entry) => ({ 38 | name: `${type}_${source}_${Date.now()}`, 39 | type, 40 | source, 41 | data: entry, 42 | })); 43 | 44 | await this.watchlistRepository.save(watchlistEntries); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /backend/src/risk/risk.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Post, Body, Get } from '@nestjs/common'; 2 | import { RiskService } from './risk.service'; 3 | 4 | @Controller('risk') 5 | export class RiskController { 6 | constructor(private readonly riskService: RiskService) {} 7 | 8 | @Post('score') 9 | getRiskScore(@Body() data: any) { 10 | const result = this.riskService.calculateRiskScore(data); 11 | return result; 12 | } 13 | 14 | @Post('fraud') 15 | detectFraud(@Body() data: any) { 16 | return this.riskService.detectFraud(data); 17 | } 18 | 19 | @Post('anomaly') 20 | detectAnomaly(@Body() data: any) { 21 | return this.riskService.detectAnomaly(data); 22 | } 23 | 24 | @Post('geo') 25 | assessGeo(@Body('location') location: string) { 26 | return this.riskService.assessGeographicRisk(location); 27 | } 28 | 29 | @Post('device') 30 | fingerprintDevice(@Body('deviceInfo') deviceInfo: any) { 31 | return this.riskService.fingerprintDevice(deviceInfo); 32 | } 33 | 34 | @Get('threshold') 35 | getThreshold() { 36 | return this.riskService.getRiskThreshold(); 37 | } 38 | 39 | @Post('adjust') 40 | adjustRisk(@Body() body: { score: number; context: any }) { 41 | return this.riskService.adjustRiskScore(body.score, body.context); 42 | } 43 | 44 | @Post('explanation') 45 | getExplanation(@Body() data: any) { 46 | return this.riskService.getRiskExplanation(data); 47 | } 48 | 49 | @Get('performance') 50 | getPerformance() { 51 | return this.riskService.getModelPerformance(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /backend/src/plaid/plaid.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Post, Body, Headers, Req, Logger } from '@nestjs/common'; 2 | import { PlaidService } from './plaid.service'; 3 | import { Request } from 'express'; 4 | 5 | @Controller('plaid') 6 | export class PlaidController { 7 | private readonly logger = new Logger(PlaidController.name); 8 | constructor(private plaidService: PlaidService) {} 9 | 10 | @Post('create-link-token') 11 | async createLinkToken(@Body() body: { userId: string }) { 12 | return this.plaidService.createLinkToken(body.userId); 13 | } 14 | 15 | @Post('exchange') 16 | async exchange(@Body() body: { public_token: string }) { 17 | return this.plaidService.exchangePublicToken(body.public_token); 18 | } 19 | 20 | // Webhook endpoint Plaid -> POST /plaid/webhook 21 | // Make sure to set rawBody parser so we can verify signature. See note below. 22 | @Post('webhook') 23 | async webhook( 24 | @Req() req: Request, 25 | @Headers('Plaid-Signature') plaidSignature: string, 26 | @Headers('Plaid-Verification') plaidVerification: string, 27 | ) { 28 | // Prefer raw body (req.rawBody) — must be configured in main.ts to preserve raw body 29 | const raw = (req as any).rawBody || JSON.stringify(req.body); 30 | const sig = plaidVerification || plaidSignature || ''; 31 | try { 32 | await this.plaidService.handlePlaidWebhook(raw, sig); 33 | return { status: 'ok' }; 34 | } catch (err) { 35 | this.logger.error('webhook handling failed', err); 36 | return { status: 'error', message: (err as Error).message }; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /backend/src/redis/redis.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; 2 | import Redis from 'ioredis'; 3 | import { ConfigService } from '@nestjs/config'; 4 | 5 | @Injectable() 6 | export class RedisService implements OnModuleInit, OnModuleDestroy { 7 | incr(redisKey: string) { 8 | throw new Error('Method not implemented.'); 9 | } 10 | keys(pattern: string): Promise { 11 | throw new Error('Method not implemented.'); 12 | } 13 | expire(redisKey: string, arg1: number) { 14 | throw new Error('Method not implemented.'); 15 | } 16 | private client: Redis; 17 | 18 | constructor(private configService: ConfigService) {} 19 | 20 | onModuleInit() { 21 | this.client = new Redis({ 22 | host: this.configService.get('REDIS_HOST', 'localhost'), 23 | port: this.configService.get('REDIS_PORT', 6379), 24 | password: this.configService.get('REDIS_PASSWORD'), 25 | }); 26 | } 27 | 28 | onModuleDestroy() { 29 | this.client.disconnect(); 30 | } 31 | 32 | async set(key: string, value: string, ttl?: number): Promise { 33 | if (ttl) { 34 | await this.client.setex(key, ttl, value); 35 | } else { 36 | await this.client.set(key, value); 37 | } 38 | } 39 | 40 | async get(key: string): Promise { 41 | return this.client.get(key); 42 | } 43 | 44 | async del(key: string): Promise { 45 | await this.client.del(key); 46 | } 47 | 48 | async exists(key: string): Promise { 49 | const result = await this.client.exists(key); 50 | return result === 1; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /backend/src/anonymization/services/pseudonymization.service.ts: -------------------------------------------------------------------------------- 1 | // A service to replace PII with a unique, reversible pseudonym. 2 | import { Injectable } from '@nestjs/common'; 3 | import { createHash } from 'crypto'; 4 | 5 | @Injectable() 6 | export class PseudonymizationService { 7 | private pseudonymMap = new Map(); 8 | // In a real-world scenario, this would be a secure, persistent store (e.g., a database). 9 | 10 | /** 11 | * Pseudonymizes a piece of data, replacing it with a consistent, unique identifier. 12 | * @param data The input data to pseudonymize. 13 | * @returns The pseudonymized string. 14 | */ 15 | pseudonymize(data: string): string { 16 | if (this.pseudonymMap.has(data)) { 17 | return this.pseudonymMap.get(data); 18 | } 19 | 20 | const salt = 'your-secure-and-secret-salt'; // A strong, unique salt is critical. 21 | const pseudonym = createHash('sha256') 22 | .update(data + salt) 23 | .digest('hex'); 24 | this.pseudonymMap.set(data, pseudonym); 25 | return pseudonym; 26 | } 27 | 28 | /** 29 | * Reverses the pseudonymization process to retrieve the original data. 30 | * This is a simple example; a production system would be more complex. 31 | * @param pseudonym The pseudonym to reverse. 32 | * @returns The original data, or null if not found. 33 | */ 34 | reversePseudonym(pseudonym: string): string | null { 35 | // Find the original data by value in the map 36 | for (const [key, value] of this.pseudonymMap.entries()) { 37 | if (value === pseudonym) { 38 | return key; 39 | } 40 | } 41 | return null; 42 | } 43 | } -------------------------------------------------------------------------------- /backend/src/screening/services/risk-scoring.service.ts: -------------------------------------------------------------------------------- 1 | // src/screening/services/risk-scoring.service.ts 2 | @Injectable() 3 | export class RiskScoringService { 4 | calculateRiskScore(matches: any[]): number { 5 | if (matches.length === 0) return 0; 6 | 7 | let totalScore = 0; 8 | let weightedScore = 0; 9 | 10 | for (const match of matches) { 11 | const baseScore = match.score; 12 | const typeWeight = this.getTypeWeight(match.entry.type); 13 | const sourceWeight = this.getSourceWeight(match.entry.source); 14 | 15 | const adjustedScore = baseScore * typeWeight * sourceWeight; 16 | weightedScore += adjustedScore; 17 | totalScore += 100; // max possible score 18 | } 19 | 20 | return Math.min(100, (weightedScore / totalScore) * 100); 21 | } 22 | 23 | private getTypeWeight(type: string): number { 24 | const weights = { 25 | sanctions: 1.0, 26 | pep: 0.8, 27 | adverse_media: 0.6, 28 | custom: 0.7, 29 | }; 30 | return weights[type] || 0.5; 31 | } 32 | 33 | private getSourceWeight(source: string): number { 34 | const weights = { 35 | ofac: 1.0, 36 | un: 0.9, 37 | eu: 0.9, 38 | custom: 0.6, 39 | }; 40 | return weights[source] || 0.5; 41 | } 42 | 43 | determineRiskLevel(score: number): string { 44 | if (score >= 80) return 'high'; 45 | if (score >= 50) return 'medium'; 46 | return 'low'; 47 | } 48 | 49 | determineStatus(riskScore: number, matches: any[]): string { 50 | if (matches.length === 0) return 'clear'; 51 | if (riskScore >= 80) return 'blocked'; 52 | return 'potential_match'; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /backend/src/credit-bureaus/test/credit-bureau.factory.ts: -------------------------------------------------------------------------------- 1 | import { NormalizedCreditReport } from '../credit-bureau.adapter'; 2 | 3 | export class CreditBureauFactory { 4 | /** 5 | * Creates a mock credit report for testing purposes 6 | */ 7 | static createMockCreditReport(userId: string, bureau: string): NormalizedCreditReport { 8 | return { 9 | userId, 10 | bureauSource: bureau, 11 | score: Math.floor(Math.random() * 300) + 550, // Random score between 550-850 12 | scoreRange: { 13 | min: 300, 14 | max: 850, 15 | }, 16 | accounts: [ 17 | { 18 | type: 'Credit Card', 19 | balance: 1250.75, 20 | paymentStatus: 'Current', 21 | accountNumber: '****1234', 22 | }, 23 | { 24 | type: 'Mortgage', 25 | balance: 250000.00, 26 | paymentStatus: 'Current', 27 | accountNumber: '****5678', 28 | }, 29 | { 30 | type: 'Auto Loan', 31 | balance: 15000.50, 32 | paymentStatus: 'Current', 33 | accountNumber: '****9012', 34 | }, 35 | ], 36 | inquiries: [ 37 | { 38 | date: new Date('2025-05-15'), 39 | type: 'Credit Card Application', 40 | requestor: 'Big Bank', 41 | }, 42 | { 43 | date: new Date('2025-03-22'), 44 | type: 'Auto Loan', 45 | requestor: 'Auto Finance Co.', 46 | }, 47 | ], 48 | publicRecords: [ 49 | { 50 | type: 'Bankruptcy', 51 | date: new Date('2020-01-10'), 52 | amount: 25000, 53 | status: 'Discharged', 54 | }, 55 | ], 56 | }; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /backend/src/privacy/privacy.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Param, Delete, Query } from '@nestjs/common'; 2 | import { PrivacyService } from './privacy.service'; 3 | import { CreateConsentDto } from './dto/create-consent.dto'; 4 | import { RequestDataExportDto } from './dto/request-data-export.dto'; 5 | import { RequestDataDeletionDto } from './dto/request-data-deletion.dto'; 6 | import { CreatePrivacyAssessmentDto } from './dto/create-privacy-assessment.dto'; 7 | 8 | @Controller('privacy') 9 | export class PrivacyController { 10 | constructor(private readonly privacyService: PrivacyService) {} 11 | 12 | // Data subject rights 13 | @Get('access/:userId') 14 | getUserData(@Param('userId') userId: string) { 15 | return this.privacyService.getUserData(userId); 16 | } 17 | 18 | @Post('deletion') 19 | requestDataDeletion(@Body() dto: RequestDataDeletionDto) { 20 | return this.privacyService.requestDataDeletion(dto); 21 | } 22 | 23 | @Post('export') 24 | requestDataExport(@Body() dto: RequestDataExportDto) { 25 | return this.privacyService.requestDataExport(dto); 26 | } 27 | 28 | // Consent management 29 | @Post('consent') 30 | createConsent(@Body() dto: CreateConsentDto) { 31 | return this.privacyService.createConsent(dto); 32 | } 33 | 34 | @Get('consent/:userId') 35 | getUserConsent(@Param('userId') userId: string) { 36 | return this.privacyService.getUserConsent(userId); 37 | } 38 | 39 | // Privacy impact assessment 40 | @Post('assessment') 41 | createAssessment(@Body() dto: CreatePrivacyAssessmentDto) { 42 | return this.privacyService.createAssessment(dto); 43 | } 44 | 45 | @Get('assessment/:userId') 46 | getUserAssessments(@Param('userId') userId: string) { 47 | return this.privacyService.getUserAssessments(userId); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /backend/bankingAnalysis/encrytion-utility.ts: -------------------------------------------------------------------------------- 1 | import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'crypto'; 2 | 3 | const ALGORITHM = 'aes-256-gcm'; 4 | const IV_LENGTH = 16; 5 | const SALT_LENGTH = 64; 6 | const TAG_LENGTH = 16; 7 | const ENCODING = 'hex'; 8 | 9 | /** 10 | * Encrypts a piece of text. 11 | * @param text The text to encrypt. 12 | * @param secretKey A 32-byte secret key. 13 | * @returns The encrypted string. 14 | */ 15 | export function encrypt(text: string, secretKey: string): string { 16 | const iv = randomBytes(IV_LENGTH); 17 | const salt = randomBytes(SALT_LENGTH); 18 | const key = scryptSync(secretKey, salt, 32) as Buffer; 19 | 20 | const cipher = createCipheriv(ALGORITHM, key, iv); 21 | const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]); 22 | const tag = cipher.getAuthTag(); 23 | 24 | return Buffer.concat([salt, iv, tag, encrypted]).toString(ENCODING); 25 | } 26 | 27 | /** 28 | * Decrypts a piece of text. 29 | * @param encryptedText The encrypted string. 30 | * @param secretKey The 32-byte secret key used to encrypt. 31 | * @returns The original decrypted text. 32 | */ 33 | export function decrypt(encryptedText: string, secretKey: string): string { 34 | const data = Buffer.from(encryptedText, ENCODING); 35 | const salt = data.subarray(0, SALT_LENGTH); 36 | const iv = data.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH); 37 | const tag = data.subarray(SALT_LENGTH + IV_LENGTH, SALT_LENGTH + IV_LENGTH + TAG_LENGTH); 38 | const encrypted = data.subarray(SALT_LENGTH + IV_LENGTH + TAG_LENGTH); 39 | 40 | const key = scryptSync(secretKey, salt, 32) as Buffer; 41 | const decipher = createDecipheriv(ALGORITHM, key, iv); 42 | decipher.setAuthTag(tag); 43 | 44 | return decipher.update(encrypted, 'hex', 'utf8') + decipher.final('utf8'); 45 | } 46 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from "hardhat/config"; 2 | import "@nomicfoundation/hardhat-toolbox"; 3 | import "@typechain/hardhat"; 4 | import "@nomicfoundation/hardhat-ethers"; 5 | import "@nomicfoundation/hardhat-chai-matchers"; 6 | import "hardhat-gas-reporter"; 7 | import "solidity-coverage"; 8 | 9 | const config: HardhatUserConfig = { 10 | solidity: { 11 | version: "0.8.19", 12 | settings: { 13 | optimizer: { 14 | enabled: true, 15 | runs: 200, 16 | }, 17 | }, 18 | }, 19 | networks: { 20 | hardhat: { 21 | chainId: 31337, 22 | }, 23 | flare: { 24 | url: "https://flare-api.flare.network/ext/C/rpc", 25 | chainId: 14, 26 | accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [], 27 | }, 28 | coston2: { 29 | url: "https://coston2-api.flare.network/ext/C/rpc", 30 | chainId: 114, 31 | accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [], 32 | }, 33 | }, 34 | gasReporter: { 35 | enabled: process.env.REPORT_GAS !== undefined, 36 | currency: "USD", 37 | }, 38 | etherscan: { 39 | apiKey: { 40 | flare: "flare", // Placeholder - Flare doesn't use API keys 41 | }, 42 | customChains: [ 43 | { 44 | network: "flare", 45 | chainId: 14, 46 | urls: { 47 | apiURL: "https://flare-explorer.flare.network/api", 48 | browserURL: "https://flare-explorer.flare.network", 49 | }, 50 | }, 51 | ], 52 | }, 53 | typechain: { 54 | outDir: "typechain-types", 55 | target: "ethers-v6", 56 | }, 57 | paths: { 58 | sources: "./contracts", 59 | tests: "./test", 60 | cache: "./cache", 61 | artifacts: "./artifacts", 62 | }, 63 | mocha: { 64 | timeout: 40000, 65 | }, 66 | }; 67 | 68 | export default config; -------------------------------------------------------------------------------- /backend/src/email/email.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import * as nodemailer from 'nodemailer'; 3 | import { ConfigService } from '@nestjs/config'; 4 | 5 | @Injectable() 6 | export class EmailService { 7 | private transporter: nodemailer.Transporter; 8 | 9 | constructor(private configService: ConfigService) { 10 | this.transporter = nodemailer.createTransport({ 11 | host: this.configService.get('SMTP_HOST'), 12 | port: this.configService.get('SMTP_PORT'), 13 | secure: false, 14 | auth: { 15 | user: this.configService.get('SMTP_USER'), 16 | pass: this.configService.get('SMTP_PASS'), 17 | }, 18 | }); 19 | } 20 | 21 | async sendEmailVerification(email: string, token: string) { 22 | const verificationUrl = `${this.configService.get('FRONTEND_URL')}/verify-email?token=${token}`; 23 | 24 | await this.transporter.sendMail({ 25 | from: this.configService.get('FROM_EMAIL'), 26 | to: email, 27 | subject: 'Email Verification', 28 | html: ` 29 |

Email Verification

30 |

Please click the link below to verify your email address:

31 | Verify Email 32 | `, 33 | }); 34 | } 35 | 36 | async sendPasswordReset(email: string, token: string) { 37 | const resetUrl = `${this.configService.get('FRONTEND_URL')}/reset-password?token=${token}`; 38 | 39 | await this.transporter.sendMail({ 40 | from: this.configService.get('FROM_EMAIL'), 41 | to: email, 42 | subject: 'Password Reset', 43 | html: ` 44 |

Password Reset

45 |

Please click the link below to reset your password:

46 | Reset Password 47 |

This link expires in 1 hour.

48 | `, 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /backend/src/audit/audit.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Query, Delete } from '@nestjs/common'; 2 | import { AuditService } from './audit.service'; 3 | 4 | @Controller('audit') 5 | export class AuditController { 6 | constructor(private readonly auditService: AuditService) {} 7 | 8 | @Get('search') 9 | async search( 10 | @Query('userId') userId?: string, 11 | @Query('action') action?: string, 12 | @Query('eventType') eventType?: string, 13 | @Query('resource') resource?: string, 14 | @Query('resourceId') resourceId?: string, 15 | @Query('outcome') outcome?: string, 16 | @Query('requestId') requestId?: string, 17 | @Query('from') from?: string, 18 | @Query('to') to?: string, 19 | ) { 20 | const logs = await this.auditService.searchLogs({ 21 | userId, 22 | action, 23 | eventType, 24 | resource, 25 | resourceId, 26 | outcome, 27 | requestId, 28 | from: from ? new Date(from) : undefined, 29 | to: to ? new Date(to) : undefined, 30 | }); 31 | return { logs }; 32 | } 33 | 34 | @Post('anchor') 35 | async anchor(@Body('fromId') fromId?: string) { 36 | return this.auditService.anchorBatchToIpfs(fromId); 37 | } 38 | 39 | @Get('verify') 40 | async verify() { 41 | return this.auditService.verifyChain(); 42 | } 43 | 44 | @Delete('purge') 45 | async purge(@Query('before') before?: string) { 46 | const cutoff = before ? new Date(before) : new Date(Date.now() - 365 * 24 * 60 * 60 * 1000); 47 | return this.auditService.purgeBefore(cutoff); 48 | } 49 | 50 | @Get('export') 51 | async exportCsv( 52 | @Query('from') from?: string, 53 | @Query('to') to?: string, 54 | ) { 55 | return this.auditService.exportCsv({ 56 | from: from ? new Date(from) : undefined, 57 | to: to ? new Date(to) : undefined, 58 | }); 59 | } 60 | } 61 | 62 | 63 | -------------------------------------------------------------------------------- /backend/src/transaction-analysis/entities/analysis-rule.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, Index } from "typeorm" 2 | 3 | export enum RuleType { 4 | CATEGORIZATION = "categorization", 5 | FRAUD_DETECTION = "fraud_detection", 6 | RISK_ASSESSMENT = "risk_assessment", 7 | SPENDING_LIMIT = "spending_limit", 8 | PATTERN_DETECTION = "pattern_detection", 9 | } 10 | 11 | export enum RuleStatus { 12 | ACTIVE = "active", 13 | INACTIVE = "inactive", 14 | TESTING = "testing", 15 | } 16 | 17 | @Entity("analysis_rules") 18 | @Index(["ruleType", "status"]) 19 | export class AnalysisRule { 20 | @PrimaryGeneratedColumn("uuid") 21 | id: string 22 | 23 | @Column({ unique: true }) 24 | name: string 25 | 26 | @Column({ type: "text", nullable: true }) 27 | description: string 28 | 29 | @Column({ type: "enum", enum: RuleType, name: "rule_type" }) 30 | ruleType: RuleType 31 | 32 | @Column({ type: "enum", enum: RuleStatus, default: RuleStatus.ACTIVE }) 33 | status: RuleStatus 34 | 35 | @Column({ type: "jsonb" }) 36 | conditions: Record // Rule conditions in JSON format 37 | 38 | @Column({ type: "jsonb" }) 39 | actions: Record // Actions to take when rule matches 40 | 41 | @Column({ type: "integer", default: 0 }) 42 | priority: number // Rule execution priority 43 | 44 | @Column({ type: "decimal", precision: 5, scale: 4, nullable: true }) 45 | threshold: number // Threshold for rule activation 46 | 47 | @Column({ name: "version", default: "1.0.0" }) 48 | version: string 49 | 50 | @Column({ name: "created_by" }) 51 | createdBy: string 52 | 53 | @Column({ name: "updated_by", nullable: true }) 54 | updatedBy: string 55 | 56 | @CreateDateColumn({ name: "created_at" }) 57 | createdAt: Date 58 | 59 | @UpdateDateColumn({ name: "updated_at" }) 60 | updatedAt: Date 61 | } 62 | -------------------------------------------------------------------------------- /backend/src/documents/documents.service.spec.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Test, TestingModule } from '@nestjs/testing'; 3 | import { DocumentsService } from './documents.service'; 4 | import { DocumentProcessingService } from './document-processing.service'; 5 | 6 | import { getRepositoryToken } from '@nestjs/typeorm'; 7 | import { DocumentProcessing } from './entities/document-processing.entity'; 8 | import { getQueueToken } from '@nestjs/bull'; 9 | 10 | const mockDocuments = [ 11 | { id: 'doc1', userId: 'user1' }, 12 | { id: 'doc2', userId: 'user1' }, 13 | { id: 'doc3', userId: 'user2' }, 14 | ]; 15 | 16 | const mockDocumentRepo = { 17 | find: jest.fn(({ where }) => mockDocuments.filter(doc => doc.userId === where.userId)), 18 | delete: jest.fn((id) => Promise.resolve({ affected: 1 })), 19 | }; 20 | 21 | describe('DocumentsService', () => { 22 | let service: DocumentsService; 23 | 24 | beforeEach(async () => { 25 | const module: TestingModule = await Test.createTestingModule({ 26 | providers: [ 27 | DocumentsService, 28 | { 29 | provide: getRepositoryToken(DocumentProcessing), 30 | useValue: mockDocumentRepo, 31 | }, 32 | { provide: getQueueToken('document-processing'), useValue: {} }, 33 | ], 34 | }).compile(); 35 | 36 | service = module.get(DocumentsService); 37 | }); 38 | 39 | it('should get all documents for a user', async () => { 40 | const docs = await service.getUserDocuments('user1'); 41 | expect(docs).toHaveLength(2); 42 | expect(docs[0].userId).toBe('user1'); 43 | }); 44 | 45 | it('should delete all documents for a user', async () => { 46 | const result = await service.deleteUserDocuments('user1'); 47 | expect(result.status).toBe('deleted'); 48 | expect(result.userId).toBe('user1'); 49 | expect(mockDocumentRepo.delete).toHaveBeenCalledTimes(2); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Credit Oracle Frontend 2 | 3 | A Next.js application for the Credit Oracle system built on the Flare Network 4 | 5 | ## 🚀 Features 6 | 7 | - **Modern Stack**: Next.js 15, React 19, TypeScript, Tailwind CSS 4 8 | - **Web3 Integration**: Ethers.js and Web3.js for blockchain interactions 9 | - **Flare Network**: Built specifically for Flare Network integration 10 | - **Credit Scoring**: Decentralized credit score verification and display 11 | - **Responsive Design**: Mobile-first approach 12 | 13 | ## 🛠️ Tech Stack 14 | 15 | - **Framework**: Next.js 15.4.5 16 | - **Language**: TypeScript 5+ 17 | - **Styling**: Tailwind CSS 4 18 | - **Web3**: Ethers.js 6+ and Web3.js 4+ 19 | - **Development**: ESLint, Prettier 20 | 21 | ## 📋 Prerequisites 22 | 23 | - Node.js 18+ 24 | - npm or yarn 25 | - Git 26 | 27 | ## 🚀 Getting Started 28 | 29 | ### 1. Clone the repository 30 | 31 | \`\`\`bash 32 | git clone 33 | cd credora/frontend 34 | \`\`\` 35 | 36 | ### 2. Install dependencies 37 | 38 | \`\`\`bash 39 | npm install 40 | \`\`\` 41 | 42 | ### 3. Environment Setup 43 | 44 | Copy the example environment file and configure your variables: 45 | 46 | \`\`\`bash 47 | cp .env.example .env.local 48 | \`\`\` 49 | 50 | Update the environment variables in `.env.local` with your actual values. See `.env.example` for all required variables. 51 | 52 | ### 4. Run the development server 53 | 54 | \`\`\`bash 55 | npm run dev 56 | \`\`\` 57 | 58 | Open [http://localhost:3000](http://localhost:3000) in your browser. 59 | 60 | ## 🔧 Available Scripts 61 | 62 | - `npm run dev` - Start development server 63 | - `npm run build` - Build for production 64 | - `npm run start` - Start production server 65 | - `npm run lint` - Run ESLint 66 | - `npm run format` - Format code with Prettier 67 | 68 | ## 🤝 Contributing 69 | 70 | Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for details on contributing to this project. 71 | -------------------------------------------------------------------------------- /backend/bankingAnalysis/banking.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, Post, Param, UsePipes, ValidationPipe } from '@nestjs/common'; 2 | import { BankingService } from './banking.service'; 3 | import { LinkAccountDto } from './dto/link-account.dto'; 4 | 5 | @Controller('banking') 6 | export class BankingController { 7 | constructor(private readonly bankingService: BankingService) {} 8 | 9 | /** 10 | * Endpoint for the frontend to get a link_token to initialize Plaid Link. 11 | * In a real app, the userId would come from an authenticated session. 12 | */ 13 | @Post('create-link-token/:userId') 14 | createLinkToken(@Param('userId') userId: string) { 15 | return this.bankingService.generateLinkToken(userId); 16 | } 17 | 18 | /** 19 | * Endpoint to link a bank account. The frontend sends the public_token 20 | * it receives from a successful Plaid Link flow. 21 | */ 22 | @Post('link-account') 23 | @UsePipes(new ValidationPipe({ whitelist: true })) 24 | linkAccount(@Body() linkAccountDto: LinkAccountDto) { 25 | return this.bankingService.setLinkedAccount( 26 | linkAccountDto.userId, 27 | linkAccountDto.public_token, 28 | ); 29 | } 30 | 31 | /** 32 | * Endpoint to fetch transactions for a specific user. 33 | */ 34 | @Get('transactions/:userId') 35 | getTransactions(@Param('userId') userId: string) { 36 | return this.bankingService.getUserTransactions(userId); 37 | } 38 | 39 | /** 40 | * Webhook endpoint for Plaid to send real-time transaction updates. 41 | * This endpoint needs to be publicly accessible. 42 | */ 43 | @Post('plaid/webhook') 44 | handlePlaidWebhook(@Body() webhookData: any) { 45 | console.log('Received Plaid Webhook:', webhookData); 46 | // TODO: Add logic to handle webhook events, e.g., new transactions, item errors. 47 | // This requires implementing webhook verification for security. 48 | return { status: 'received' }; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /backend/src/screening/services/fuzzy-matching.service.ts: -------------------------------------------------------------------------------- 1 | // src/screening/services/fuzzy-matching.service.ts 2 | @Injectable() 3 | export class FuzzyMatchingService { 4 | calculateSimilarity(str1: string, str2: string): number { 5 | // Levenshtein distance implementation 6 | const matrix = Array(str2.length + 1) 7 | .fill(null) 8 | .map(() => Array(str1.length + 1).fill(null)); 9 | 10 | for (let i = 0; i <= str1.length; i++) matrix[0][i] = i; 11 | for (let j = 0; j <= str2.length; j++) matrix[j][0] = j; 12 | 13 | for (let j = 1; j <= str2.length; j++) { 14 | for (let i = 1; i <= str1.length; i++) { 15 | const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1; 16 | matrix[j][i] = Math.min( 17 | matrix[j][i - 1] + 1, 18 | matrix[j - 1][i] + 1, 19 | matrix[j - 1][i - 1] + indicator, 20 | ); 21 | } 22 | } 23 | 24 | const distance = matrix[str2.length][str1.length]; 25 | const maxLength = Math.max(str1.length, str2.length); 26 | return ((maxLength - distance) / maxLength) * 100; 27 | } 28 | 29 | normalizeString(str: string): string { 30 | return str 31 | .toLowerCase() 32 | .replace(/[^\w\s]/g, '') 33 | .replace(/\s+/g, ' ') 34 | .trim(); 35 | } 36 | 37 | findMatches( 38 | searchTerm: string, 39 | watchlistEntries: any[], 40 | threshold = 80, 41 | ): any[] { 42 | const normalizedSearch = this.normalizeString(searchTerm); 43 | const matches = []; 44 | 45 | for (const entry of watchlistEntries) { 46 | const normalizedEntry = this.normalizeString(entry.name); 47 | const score = this.calculateSimilarity(normalizedSearch, normalizedEntry); 48 | 49 | if (score >= threshold) { 50 | matches.push({ 51 | entry, 52 | score, 53 | matchedField: 'name', 54 | }); 55 | } 56 | } 57 | 58 | return matches.sort((a, b) => b.score - a.score); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /backend/src/api-gateway/dto/api-key.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsString, IsOptional, IsArray, IsNumber, IsDateString, IsBoolean } from 'class-validator'; 2 | import { ApiProperty } from '@nestjs/swagger'; 3 | 4 | export class CreateApiKeyDto { 5 | @ApiProperty({ description: 'API key name', required: false }) 6 | @IsOptional() 7 | @IsString() 8 | name?: string; 9 | 10 | @ApiProperty({ description: 'API key permissions', required: false }) 11 | @IsOptional() 12 | @IsArray() 13 | @IsString({ each: true }) 14 | permissions?: string[]; 15 | 16 | @ApiProperty({ description: 'Rate limit per period', required: false, default: 1000 }) 17 | @IsOptional() 18 | @IsNumber() 19 | rateLimit?: number; 20 | 21 | @ApiProperty({ description: 'Rate limit period', required: false, default: 'hour' }) 22 | @IsOptional() 23 | @IsString() 24 | rateLimitPeriod?: string; 25 | 26 | @ApiProperty({ description: 'API key expiration date', required: false }) 27 | @IsOptional() 28 | @IsDateString() 29 | expiresAt?: Date; 30 | } 31 | 32 | export class UpdateApiKeyDto { 33 | @ApiProperty({ description: 'API key name', required: false }) 34 | @IsOptional() 35 | @IsString() 36 | name?: string; 37 | 38 | @ApiProperty({ description: 'API key permissions', required: false }) 39 | @IsOptional() 40 | @IsArray() 41 | @IsString({ each: true }) 42 | permissions?: string[]; 43 | 44 | @ApiProperty({ description: 'Rate limit per period', required: false }) 45 | @IsOptional() 46 | @IsNumber() 47 | rateLimit?: number; 48 | 49 | @ApiProperty({ description: 'Rate limit period', required: false }) 50 | @IsOptional() 51 | @IsString() 52 | rateLimitPeriod?: string; 53 | 54 | @ApiProperty({ description: 'API key active status', required: false }) 55 | @IsOptional() 56 | @IsBoolean() 57 | isActive?: boolean; 58 | 59 | @ApiProperty({ description: 'API key expiration date', required: false }) 60 | @IsOptional() 61 | @IsDateString() 62 | expiresAt?: Date; 63 | } 64 | 65 | -------------------------------------------------------------------------------- /backend/src/documents/entities/document-processing.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm'; 2 | 3 | export enum DocumentProcessingStatus { 4 | QUEUED = 'queued', 5 | PROCESSING = 'processing', 6 | COMPLETED = 'completed', 7 | FAILED = 'failed', 8 | RETRY = 'retry', 9 | } 10 | 11 | @Entity('document_processing') 12 | export class DocumentProcessing { 13 | @PrimaryGeneratedColumn('uuid') 14 | id: string; 15 | 16 | @Column({ unique: true }) 17 | fileId: string; 18 | 19 | @Column({ type: 'enum', enum: DocumentProcessingStatus, default: DocumentProcessingStatus.QUEUED }) 20 | status: DocumentProcessingStatus; 21 | 22 | @Column({ type: 'jsonb', nullable: true }) 23 | ocrResult?: any; 24 | 25 | @Column({ type: 'varchar', nullable: true }) 26 | documentType?: string; 27 | 28 | @Column({ type: 'jsonb', nullable: true }) 29 | extractedData?: any; 30 | 31 | @Column({ type: 'jsonb', nullable: true }) 32 | authenticityCheck?: any; 33 | 34 | @Column({ type: 'jsonb', nullable: true }) 35 | imageQuality?: any; 36 | 37 | @Column({ type: 'jsonb', nullable: true }) 38 | faceMatch?: any; 39 | 40 | @Column({ type: 'jsonb', nullable: true }) 41 | duplicateCheck?: any; 42 | 43 | // Storage metadata 44 | @Column({ type: 'varchar', nullable: true }) 45 | bucket?: string; 46 | 47 | @Column({ type: 'varchar', nullable: true }) 48 | objectKey?: string; 49 | 50 | @Column({ type: 'varchar', nullable: true }) 51 | thumbnailKey?: string; 52 | 53 | @Column({ type: 'varchar', nullable: true }) 54 | mimeType?: string; 55 | 56 | @Column({ type: 'varchar', nullable: true }) 57 | etag?: string; 58 | 59 | @Column({ type: 'int', nullable: true }) 60 | size?: number; 61 | 62 | 63 | @Column({ type: 'varchar', nullable: false }) 64 | userId: string; 65 | 66 | @Column({ type: 'jsonb', nullable: true }) 67 | error?: any; 68 | 69 | @Column({ type: 'int', default: 0 }) 70 | retryCount: number; 71 | 72 | @CreateDateColumn() 73 | createdAt: Date; 74 | 75 | @UpdateDateColumn() 76 | updatedAt: Date; 77 | } 78 | -------------------------------------------------------------------------------- /backend/src/users/entities/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | PrimaryGeneratedColumn, 4 | Column, 5 | Index, 6 | CreateDateColumn, 7 | OneToMany, 8 | UpdateDateColumn, 9 | } from 'typeorm'; 10 | import { IsOptional, IsString, IsBoolean, IsObject } from 'class-validator'; 11 | import { Exclude } from 'class-transformer'; 12 | import { RefreshToken } from './refresh-token.entity'; 13 | 14 | export enum UserRole { 15 | USER = 'user', 16 | ADMIN = 'admin', 17 | ANALYST = 'analyst', 18 | OPERATOR = 'operator', 19 | } 20 | 21 | @Entity({ name: 'users' }) 22 | export class User { 23 | @PrimaryGeneratedColumn('uuid') 24 | id: string; 25 | 26 | 27 | // Encrypted PII fields 28 | @Column({ type: 'varchar', nullable: true }) 29 | encryptedFullName: string; 30 | 31 | @Column({ type: 'varchar', nullable: true }) 32 | encryptedEmail: string; 33 | 34 | // Wallet linking 35 | @Column({ type: 'varchar', nullable: true }) 36 | walletAddress: string; 37 | 38 | // Profile completion tracking 39 | @Column({ default: false }) 40 | profileCompleted: boolean; 41 | 42 | // User preferences (JSON) 43 | @Column({ type: 'json', nullable: true }) 44 | preferences: Record; 45 | 46 | @Column({ default: true }) 47 | isActive: boolean; 48 | 49 | @Column() 50 | @Exclude() 51 | password: string; 52 | 53 | @Column({ type: 'enum', enum: UserRole, default: UserRole.USER }) 54 | role: UserRole; 55 | 56 | @Column({ default: false }) 57 | isEmailVerified: boolean; 58 | 59 | @Column({ nullable: true }) 60 | emailVerificationToken: string; 61 | 62 | @Column({ nullable: true }) 63 | passwordResetToken: string; 64 | 65 | @Column({ nullable: true }) 66 | passwordResetExpires: Date; 67 | 68 | @Column({ default: false }) 69 | twoFactorEnabled: boolean; 70 | 71 | @Column({ nullable: true }) 72 | @Exclude() 73 | twoFactorSecret: string; 74 | 75 | @CreateDateColumn() 76 | createdAt: Date; 77 | 78 | @UpdateDateColumn() 79 | updatedAt: Date; 80 | 81 | @OneToMany(() => RefreshToken, (refreshToken) => refreshToken.user) 82 | refreshTokens: RefreshToken[]; 83 | } 84 | -------------------------------------------------------------------------------- /backend/src/users/users.controller.ts: -------------------------------------------------------------------------------- 1 | // src/users/users.controller.ts 2 | import { Body, Controller, Get, Post, Put, Delete, Param, UseInterceptors } from '@nestjs/common'; 3 | import { CacheInterceptor, CacheKey, CacheTTL } from '@nestjs/cache-manager'; 4 | import { UsersService } from './users.service'; 5 | import { CreateProfileDto } from './dto/create-profile.dto'; 6 | import { UpdateProfileDto } from './dto/update-profile.dto'; 7 | import { PreferencesDto } from './dto/preferences.dto'; 8 | import { DeactivateProfileDto } from './dto/deactivate-profile.dto'; 9 | 10 | @Controller('users') 11 | @UseInterceptors(CacheInterceptor) // Apply the interceptor to the whole controller 12 | export class UsersController { 13 | constructor(private readonly usersService: UsersService) {} 14 | 15 | @Post() 16 | createProfile(@Body() dto: CreateProfileDto) { 17 | return this.usersService.createProfile(dto); 18 | } 19 | 20 | @Get(':id') 21 | @CacheKey('user_profile_response') // Custom cache key for this endpoint 22 | @CacheTTL(300) // Cache response for 5 minutes (300 seconds) 23 | getProfile(@Param('id') id: string) { 24 | return this.usersService.getProfile(id); 25 | } 26 | 27 | @Put(':id') 28 | updateProfile(@Param('id') id: string, @Body() dto: UpdateProfileDto) { 29 | return this.usersService.updateProfile(id, dto); 30 | } 31 | 32 | @Delete(':id') 33 | deleteProfile(@Param('id') id: string) { 34 | return this.usersService.deleteProfile(id); 35 | } 36 | 37 | @Put(':id/preferences') 38 | setPreferences(@Param('id') id: string, @Body() dto: PreferencesDto) { 39 | return this.usersService.setPreferences(id, dto); 40 | } 41 | 42 | @Get(':id/export') 43 | @CacheKey('user_export_response') // A different cache key for the export endpoint 44 | @CacheTTL(300) // Cache response for 5 minutes 45 | exportProfile(@Param('id') id: string) { 46 | return this.usersService.exportProfile(id); 47 | } 48 | 49 | @Put(':id/deactivate') 50 | deactivateProfile(@Param('id') id: string, @Body() dto: DeactivateProfileDto) { 51 | return this.usersService.deactivateProfile(id, dto); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /backend/src/privacy/entities/audit-log.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, Index } from 'typeorm'; 2 | 3 | @Entity('audit_log') 4 | export class AuditLog { 5 | @PrimaryGeneratedColumn('uuid') 6 | id: string; 7 | 8 | @Index() 9 | @Column() 10 | userId: string; 11 | 12 | @Column() 13 | action: string; 14 | 15 | @Column('json', { nullable: true }) 16 | details: any; 17 | 18 | // High-level classification of event (e.g., user.activity, data.access, system.event, compliance.event) 19 | @Index() 20 | @Column({ default: 'user.activity' }) 21 | eventType: string; 22 | 23 | // Target resource info for data access tracking 24 | @Index() 25 | @Column({ nullable: true }) 26 | resource?: string; 27 | 28 | @Index() 29 | @Column({ nullable: true }) 30 | resourceId?: string; 31 | 32 | // Result of the action (success/failure) 33 | @Index() 34 | @Column({ default: 'success' }) 35 | outcome: string; 36 | 37 | // Request context 38 | @Column({ nullable: true }) 39 | ip?: string; 40 | 41 | @Column({ nullable: true }) 42 | userAgent?: string; 43 | 44 | @Index() 45 | @Column({ nullable: true }) 46 | requestId?: string; 47 | 48 | @Column({ nullable: true }) 49 | sessionId?: string; 50 | 51 | // Actor and service context 52 | @Index() 53 | @Column({ default: 'user' }) 54 | actorType: string; // user | system | service 55 | 56 | @Index() 57 | @Column({ nullable: true }) 58 | service?: string; 59 | 60 | // Tamper-evident chain 61 | @Index() 62 | @Column({ type: 'varchar', length: 128, nullable: true }) 63 | prevHash?: string; 64 | 65 | @Index() 66 | @Column({ type: 'varchar', length: 128, nullable: true }) 67 | hash?: string; 68 | 69 | // Anchoring/immutability 70 | @Index() 71 | @Column({ nullable: true }) 72 | anchorCid?: string; 73 | 74 | // Sequencing within a day or stream 75 | @Index() 76 | @Column({ type: 'int', default: 0 }) 77 | sequenceNumber: number; 78 | 79 | // Retention control 80 | @Index() 81 | @Column({ type: 'timestamptz', nullable: true }) 82 | retentionUntil?: Date; 83 | 84 | @CreateDateColumn() 85 | timestamp: Date; 86 | } 87 | -------------------------------------------------------------------------------- /backend/src/api-gateway/interceptors/transformation.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | NestInterceptor, 4 | ExecutionContext, 5 | CallHandler, 6 | Logger, 7 | } from '@nestjs/common'; 8 | import { Reflector } from '@nestjs/core'; 9 | import { Observable } from 'rxjs'; 10 | import { map } from 'rxjs/operators'; 11 | import { TransformationService, TransformationRule } from '../services/transformation.service'; 12 | 13 | export const TRANSFORMATION_RULES = 'transformationRules'; 14 | export const Transform = (rules: { 15 | request?: TransformationRule[]; 16 | response?: TransformationRule[]; 17 | }) => Reflect.metadata(TRANSFORMATION_RULES, rules); 18 | 19 | @Injectable() 20 | export class TransformationInterceptor implements NestInterceptor { 21 | private readonly logger = new Logger(TransformationInterceptor.name); 22 | 23 | constructor( 24 | private readonly reflector: Reflector, 25 | private readonly transformationService: TransformationService, 26 | ) {} 27 | 28 | intercept(context: ExecutionContext, next: CallHandler): Observable { 29 | const request = context.switchToHttp().getRequest(); 30 | 31 | const transformationRules = this.reflector.get<{ 32 | request?: TransformationRule[]; 33 | response?: TransformationRule[]; 34 | }>(TRANSFORMATION_RULES, context.getHandler()); 35 | 36 | if (transformationRules?.request && request.body) { 37 | try { 38 | request.body = this.transformationService.transformRequest( 39 | request.body, 40 | transformationRules.request, 41 | ); 42 | } catch (error) { 43 | this.logger.error('Request transformation failed', error); 44 | } 45 | } 46 | 47 | if (transformationRules?.response) { 48 | return next.handle().pipe( 49 | map(data => { 50 | try { 51 | return this.transformationService.transformResponse( 52 | data, 53 | transformationRules.response!, 54 | ); 55 | } catch (error) { 56 | this.logger.error('Response transformation failed', error); 57 | return data; 58 | } 59 | }), 60 | ); 61 | } 62 | 63 | return next.handle(); 64 | } 65 | } -------------------------------------------------------------------------------- /contracts/tests/mocks/MockContracts.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | /** 5 | * @title MockFlareContractRegistry 6 | * @dev Mock contract for testing Flare contract registry functionality 7 | */ 8 | contract MockFlareContractRegistry { 9 | mapping(string => address) private contracts; 10 | 11 | function setContractAddress(string calldata _name, address _address) external { 12 | contracts[_name] = _address; 13 | } 14 | 15 | function getContractAddressByName(string calldata _name) external view returns (address) { 16 | return contracts[_name]; 17 | } 18 | } 19 | 20 | /** 21 | * @title MockFtsoRegistry 22 | * @dev Mock contract for testing FTSO price feeds 23 | */ 24 | contract MockFtsoRegistry { 25 | mapping(string => uint256) private prices; 26 | mapping(string => uint256) private timestamps; 27 | 28 | function setPrice(string calldata _symbol, uint256 _price) external { 29 | prices[_symbol] = _price; 30 | timestamps[_symbol] = block.timestamp; 31 | } 32 | 33 | function getCurrentPrice(string calldata _symbol) external view returns (uint256 _price, uint256 _timestamp) { 34 | return (prices[_symbol], timestamps[_symbol]); 35 | } 36 | } 37 | 38 | /** 39 | * @title MockStateConnector 40 | * @dev Mock contract for testing State Connector attestations 41 | */ 42 | contract MockStateConnector { 43 | mapping(bytes32 => bool) private attestationProofs; 44 | mapping(bytes32 => bytes) private attestationData; 45 | uint256 private requestCounter; 46 | 47 | function setAttestation(bytes32 _attestationId, bool _proved, bytes calldata _data) external { 48 | attestationProofs[_attestationId] = _proved; 49 | attestationData[_attestationId] = _data; 50 | } 51 | 52 | function requestAttestation(bytes calldata _attestationRequest) external returns (bytes32) { 53 | requestCounter++; 54 | return keccak256(abi.encodePacked(_attestationRequest, requestCounter, block.timestamp)); 55 | } 56 | 57 | function getAttestation(bytes32 _attestationId) external view returns (bool _proved, bytes memory _data) { 58 | return (attestationProofs[_attestationId], attestationData[_attestationId]); 59 | } 60 | } -------------------------------------------------------------------------------- /backend/src/api-gateway/scripts/initialize-endpoints.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from '../../app.module'; 3 | import { ApiGatewayService } from '../api-gateway.service'; 4 | 5 | async function initializeDefaultEndpoints() { 6 | const app = await NestFactory.createApplicationContext(AppModule); 7 | const apiGatewayService = app.get(ApiGatewayService); 8 | 9 | const defaultEndpoints = [ 10 | { 11 | path: '/auth/login', 12 | method: 'POST', 13 | version: 'v1', 14 | targetUrl: 'http://localhost:3000/auth/login', 15 | rateLimitConfig: { 16 | windowMs: 900000, 17 | maxRequests: 5, 18 | }, 19 | }, 20 | { 21 | path: '/users/:id', 22 | method: 'GET', 23 | version: 'v1', 24 | targetUrl: 'http://localhost:3000/users', 25 | transformationRules: { 26 | response: [ 27 | { type: 'filter', field: 'password' }, 28 | { type: 'rename', source: 'id', target: 'userId' }, 29 | ], 30 | }, 31 | }, 32 | { 33 | path: '/banking/accounts', 34 | method: 'GET', 35 | version: 'v1', 36 | targetUrl: 'http://localhost:3000/banking/accounts', 37 | circuitBreakerConfig: { 38 | failureThreshold: 3, 39 | successThreshold: 2, 40 | timeout: 30000, 41 | }, 42 | }, 43 | { 44 | path: '/screening/check', 45 | method: 'POST', 46 | version: 'v1', 47 | targetUrl: 'http://localhost:3000/screening/check', 48 | rateLimitConfig: { 49 | windowMs: 60000, 50 | maxRequests: 10, 51 | circuitBreakerConfig: { 52 | failureThreshold: 5, 53 | successThreshold: 3, 54 | timeout: 60000, 55 | }, 56 | }, 57 | }, 58 | ]; 59 | 60 | for (const endpoint of defaultEndpoints) { 61 | try { 62 | await apiGatewayService.registerEndpoint(endpoint); 63 | console.log(` Registered endpoint: ${endpoint.method} ${endpoint.path}`); 64 | } catch (error) { 65 | console.error(` Failed to register endpoint: ${endpoint.method} ${endpoint.path}`, error.message); 66 | } 67 | } 68 | 69 | await app.close(); 70 | console.log(' Default endpoints initialization completed'); 71 | } -------------------------------------------------------------------------------- /backend/src/verification/verification.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import axios from 'axios'; 3 | import { VerificationRequestDto } from './dto/verification-request.dto'; 4 | import { VerificationResponseDto } from './dto/verification-response.dto'; 5 | import * as crypto from 'crypto'; 6 | 7 | @Injectable() 8 | export class VerificationService { 9 | private jumioApiKey = process.env.JUMIO_API_KEY; 10 | private onfidoApiKey = process.env.ONFIDO_API_KEY; 11 | 12 | // Initiate verification with Jumio using the VerificationRequestDto 13 | async verifyWithJumio(verificationData: VerificationRequestDto): Promise { 14 | const response = await axios.post( 15 | 'https://api.jumio.com/verify', 16 | verificationData, 17 | { 18 | headers: { 19 | Authorization: `Bearer ${this.jumioApiKey}`, 20 | }, 21 | }, 22 | ); 23 | return response.data; // Return data in the shape of VerificationResponseDto 24 | } 25 | 26 | // Initiate verification with Onfido using the VerificationRequestDto 27 | async verifyWithOnfido(verificationData: VerificationRequestDto): Promise { 28 | const response = await axios.post( 29 | 'https://api.onfido.com/v3.6/checks', 30 | verificationData, 31 | { 32 | headers: { 33 | Authorization: `Bearer ${this.onfidoApiKey}`, 34 | }, 35 | }, 36 | ); 37 | return response.data; // Return data in the shape of VerificationResponseDto 38 | } 39 | 40 | // Multi-provider fallback logic 41 | async performVerification(verificationData: VerificationRequestDto): Promise { 42 | try { 43 | const jumioResult = await this.verifyWithJumio(verificationData); 44 | if (jumioResult.verificationStatus === 'verified') { 45 | return jumioResult; 46 | } 47 | } catch (error) { 48 | console.error('Jumio failed, falling back to Onfido'); 49 | } 50 | 51 | try { 52 | const onfidoResult = await this.verifyWithOnfido(verificationData); 53 | if (onfidoResult.verificationStatus === 'verified') { 54 | return onfidoResult; 55 | } 56 | } catch (error) { 57 | console.error('Both providers failed'); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /frontend/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Credit Oracle Frontend 2 | 3 | Thank you for your interest in contributing to the Credit Oracle Frontend! 4 | 5 | ## 🚀 Getting Started 6 | 7 | ### Prerequisites 8 | 9 | - Node.js 18+ 10 | - npm or yarn 11 | - Git 12 | - Basic knowledge of Next.js, React, and TypeScript 13 | 14 | ### Development Setup 15 | 16 | 1. Fork the repository 17 | 2. Clone your fork: `git clone ` 18 | 3. Navigate to frontend: `cd credora/frontend` 19 | 4. Install dependencies: `npm install` 20 | 5. Copy environment file: `cp .env.example .env.local` 21 | 6. Start development server: `npm run dev` 22 | 23 | ## 📋 Development Guidelines 24 | 25 | ### Code Style 26 | 27 | We use ESLint and Prettier for consistent code formatting: 28 | 29 | \`\`\`bash 30 | npm run lint # Check linting 31 | npm run format # Format code 32 | \`\`\` 33 | 34 | ### Best Practices 35 | 36 | - Use TypeScript with proper types 37 | - Follow Next.js App Router conventions 38 | - Use Tailwind CSS utilities 39 | - Maintain responsive design (mobile-first) 40 | - Handle Web3 wallet connections properly 41 | - Implement proper error handling 42 | 43 | ## 🔄 Workflow 44 | 45 | ### Branch Naming 46 | 47 | - `feature/description` - New features 48 | - `fix/description` - Bug fixes 49 | - `docs/description` - Documentation updates 50 | 51 | ### Pull Request Process 52 | 53 | 1. Create a feature branch from `main` 54 | 2. Make your changes following the guidelines 55 | 3. Test your changes thoroughly 56 | 4. Run `npm run lint` and `npm run format` 57 | 5. Submit a pull request with clear description 58 | 59 | ## 🧪 Before Submitting 60 | 61 | \`\`\`bash 62 | npm run lint # Check for linting errors 63 | npm run format # Format code 64 | npm run build # Ensure build succeeds 65 | \`\`\` 66 | 67 | ## 📞 Getting Help 68 | 69 | If you have any issues or questions while contributing, please reach out to the project maintainer by contacting them directly. 70 | 71 | ## 💡 Feature Requests 72 | 73 | For new features: 74 | 75 | 1. Check issues Descriptions first 76 | 2. Provide clear use case description 77 | 3. Discuss with maintainers before starting if any questions 78 | 79 | Thank you for contributing to Credit Oracle Frontend! 🚀 80 | -------------------------------------------------------------------------------- /backend/src/audit/audit.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | NestInterceptor, 4 | ExecutionContext, 5 | CallHandler, 6 | } from '@nestjs/common'; 7 | import { Observable } from 'rxjs'; 8 | import { tap, catchError } from 'rxjs/operators'; 9 | import { AuditService } from './audit.service'; 10 | import type { CreateAuditLogInput } from './audit.service'; 11 | 12 | @Injectable() 13 | export class AuditInterceptor implements NestInterceptor { 14 | constructor(private readonly auditService: AuditService) {} 15 | 16 | intercept(context: ExecutionContext, next: CallHandler): Observable { 17 | const req = context.switchToHttp().getRequest(); 18 | const userId: string | undefined = req.user?.id || req.user?.userId || undefined; 19 | const requestId: string | undefined = (req.headers['x-request-id'] as string) || (req.id as string | undefined); 20 | const sessionId: string | undefined = req.session?.id as string | undefined; 21 | const ip = (req.ip as string) || (req.headers['x-forwarded-for'] as string) || (req.connection?.remoteAddress as string | undefined); 22 | const userAgent = req.headers['user-agent'] as string | undefined; 23 | const action = `${req.method} ${req.route?.path || req.url}`; 24 | const resource = (req.route?.path || req.path) as string | undefined; 25 | 26 | const common: CreateAuditLogInput = { 27 | userId, 28 | action, 29 | eventType: 'user.activity', 30 | resource, 31 | outcome: 'success', 32 | ip: typeof ip === 'string' ? ip : Array.isArray(ip as any) ? (ip as any)[0] : undefined, 33 | userAgent, 34 | requestId: typeof requestId === 'string' ? requestId : undefined, 35 | sessionId, 36 | actorType: (userId ? 'user' : 'system'), 37 | details: { params: req.params, query: req.query, bodyKeys: Object.keys(req.body || {}) }, 38 | }; 39 | 40 | return next.handle().pipe( 41 | tap(async () => { 42 | try { 43 | await this.auditService.recordLog(common); 44 | } catch {} 45 | }), 46 | catchError((err, caught) => { 47 | // Record failure outcome 48 | try { 49 | this.auditService.recordLog({ ...common, outcome: 'failure', details: { error: err?.message } }); 50 | } catch {} 51 | throw err; 52 | }) as any, 53 | ); 54 | } 55 | } 56 | 57 | 58 | -------------------------------------------------------------------------------- /backend/src/api-gateway/api-gateway.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { RedisModule } from '../redis/redis.module'; 4 | import { ApiGatewayController } from './api-gateway.controller'; 5 | import { ApiGatewayService } from './api-gateway.service'; 6 | import { RateLimitService } from './services/rate-limit.service'; 7 | import { LoadBalancerService } from './services/load-balancer.service'; 8 | import { AnalyticsService } from './services/analytics.service'; 9 | import { ApiKeyService } from './services/api-key.service'; 10 | import { TransformationService } from './services/transformation.service'; 11 | import { CircuitBreakerService } from './services/circuit-breaker.service'; 12 | import { ApiKey } from './entities/api-key.entity'; 13 | import { ApiUsage } from './entities/api-usage.entity'; 14 | import { ApiEndpoint } from './entities/api-endpoint.entity'; 15 | import { ServiceHealth } from './entities/service-health.entity'; 16 | import { RateLimitGuard } from './guards/rate-limit.guard'; 17 | import { ApiKeyGuard } from './guards/api-key.guard'; 18 | import { AnalyticsInterceptor } from './interceptors/analytics.interceptor'; 19 | import { TransformationInterceptor } from './interceptors/transformation.interceptor'; 20 | import { CircuitBreakerInterceptor } from './interceptors/circuit-breaker.interceptor'; 21 | import { HealthMonitorService } from './services/health-monitor.service'; 22 | import { ScheduleModule } from '@nestjs/schedule'; 23 | 24 | @Module({ 25 | imports: [ 26 | TypeOrmModule.forFeature([ 27 | ApiKey, 28 | ApiUsage, 29 | ApiEndpoint, 30 | ServiceHealth, 31 | ]), 32 | RedisModule, 33 | ScheduleModule.forRoot(), 34 | ], 35 | controllers: [ApiGatewayController], 36 | providers: [ 37 | ApiGatewayService, 38 | RateLimitService, 39 | LoadBalancerService, 40 | AnalyticsService, 41 | ApiKeyService, 42 | TransformationService, 43 | CircuitBreakerService, 44 | RateLimitGuard, 45 | ApiKeyGuard, 46 | HealthMonitorService, 47 | AnalyticsInterceptor, 48 | TransformationInterceptor, 49 | CircuitBreakerInterceptor, 50 | ], 51 | exports: [ 52 | ApiGatewayService, 53 | RateLimitService, 54 | AnalyticsService, 55 | ApiKeyService, 56 | ], 57 | }) 58 | export class ApiGatewayModule {} -------------------------------------------------------------------------------- /backend/src/transaction-analysis/transaction-analysis.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common" 2 | import { TypeOrmModule } from "@nestjs/typeorm" 3 | 4 | // Entities 5 | import { Transaction, TransactionAnalysis, UserFinancialProfile, AnalysisRule } from "./entities" 6 | 7 | // Services 8 | import { TransactionAnalysisService } from "./services/transaction-analysis.service" 9 | import { CategorizationService } from "./services/categorization.service" 10 | import { SpendingPatternService } from "./services/spending-pattern.service" 11 | import { IncomeStabilityService } from "./services/income-stability.service" 12 | import { CashFlowService } from "./services/cash-flow.service" 13 | import { BehavioralScoringService } from "./services/behavioral-scoring.service" 14 | import { FraudDetectionService } from "./services/fraud-detection.service" 15 | import { RiskAssessmentService } from "./services/risk-assessment.service" 16 | import { TimeSeriesAnalysisService } from "./services/time-series-analysis.service" 17 | import { RuleEngineService } from "./services/rule-engine.service" 18 | import { RuleConditionEvaluator } from "./services/rule-condition-evaluator.service" 19 | import { RuleActionExecutor } from "./services/rule-action-executor.service" 20 | 21 | // Controllers 22 | import { TransactionAnalysisController } from "./controllers/transaction-analysis.controller" 23 | import { RuleManagementController } from "./controllers/rule-management.controller" 24 | import { UserProfileController } from "./controllers/user-profile.controller" 25 | 26 | @Module({ 27 | imports: [TypeOrmModule.forFeature([Transaction, TransactionAnalysis, UserFinancialProfile, AnalysisRule])], 28 | controllers: [TransactionAnalysisController, RuleManagementController, UserProfileController], 29 | providers: [ 30 | // Core Analysis Services 31 | TransactionAnalysisService, 32 | CategorizationService, 33 | SpendingPatternService, 34 | IncomeStabilityService, 35 | CashFlowService, 36 | BehavioralScoringService, 37 | FraudDetectionService, 38 | RiskAssessmentService, 39 | TimeSeriesAnalysisService, 40 | 41 | // Rule Engine Services 42 | RuleEngineService, 43 | RuleConditionEvaluator, 44 | RuleActionExecutor, 45 | ], 46 | exports: [ 47 | TransactionAnalysisService, 48 | RuleEngineService, 49 | CategorizationService, 50 | FraudDetectionService, 51 | RiskAssessmentService, 52 | ], 53 | }) 54 | export class TransactionAnalysisModule {} 55 | -------------------------------------------------------------------------------- /backend/src/transaction-analysis/entities/transaction-analysis.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | PrimaryGeneratedColumn, 4 | Column, 5 | CreateDateColumn, 6 | UpdateDateColumn, 7 | ManyToOne, 8 | JoinColumn, 9 | Index, 10 | } from "typeorm" 11 | import { Transaction } from "./transaction.entity" 12 | 13 | export enum AnalysisType { 14 | CATEGORIZATION = "categorization", 15 | SPENDING_PATTERN = "spending_pattern", 16 | INCOME_STABILITY = "income_stability", 17 | CASH_FLOW = "cash_flow", 18 | DEBT_TO_INCOME = "debt_to_income", 19 | BEHAVIORAL_SCORING = "behavioral_scoring", 20 | FRAUD_DETECTION = "fraud_detection", 21 | RISK_ASSESSMENT = "risk_assessment", 22 | TREND_ANALYSIS = "trend_analysis", 23 | } 24 | 25 | export enum RiskLevel { 26 | LOW = "low", 27 | MEDIUM = "medium", 28 | HIGH = "high", 29 | CRITICAL = "critical", 30 | } 31 | 32 | @Entity("transaction_analyses") 33 | @Index(["transactionId", "analysisType"]) 34 | @Index(["userId", "analysisType", "createdAt"]) 35 | export class TransactionAnalysis { 36 | @PrimaryGeneratedColumn("uuid") 37 | id: string 38 | 39 | @Column({ name: "transaction_id" }) 40 | transactionId: string 41 | 42 | @Column({ name: "user_id" }) 43 | @Index() 44 | userId: string 45 | 46 | @Column({ type: "enum", enum: AnalysisType, name: "analysis_type" }) 47 | analysisType: AnalysisType 48 | 49 | @Column({ type: "decimal", precision: 5, scale: 4, nullable: true }) 50 | confidence: number // ML confidence score (0-1) 51 | 52 | @Column({ type: "enum", enum: RiskLevel, name: "risk_level", nullable: true }) 53 | riskLevel: RiskLevel 54 | 55 | @Column({ type: "decimal", precision: 10, scale: 2, nullable: true }) 56 | score: number // Numerical score for the analysis 57 | 58 | @Column({ type: "jsonb" }) 59 | result: Record // Detailed analysis results 60 | 61 | @Column({ type: "jsonb", nullable: true }) 62 | features: Record // ML features used 63 | 64 | @Column({ name: "model_version", nullable: true }) 65 | modelVersion: string 66 | 67 | @Column({ name: "rule_version", nullable: true }) 68 | ruleVersion: string 69 | 70 | @Column({ type: "text", nullable: true }) 71 | notes: string 72 | 73 | @CreateDateColumn({ name: "created_at" }) 74 | createdAt: Date 75 | 76 | @UpdateDateColumn({ name: "updated_at" }) 77 | updatedAt: Date 78 | 79 | @ManyToOne( 80 | () => Transaction, 81 | (transaction) => transaction.analyses, 82 | ) 83 | @JoinColumn({ name: "transaction_id" }) 84 | transaction: Transaction 85 | } 86 | -------------------------------------------------------------------------------- /backend/src/api-gateway/api-gateway.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { getRepositoryToken } from '@nestjs/typeorm'; 3 | import { ApiGatewayService } from './api-gateway.service'; 4 | import { RateLimitService } from './services/rate-limit.service'; 5 | import { ApiKeyService } from './services/api-key.service'; 6 | import { AnalyticsService } from './services/analytics.service'; 7 | import { ApiKey } from './entities/api-key.entity'; 8 | import { ApiUsage } from './entities/api-usage.entity'; 9 | import { ApiEndpoint } from './entities/api-endpoint.entity'; 10 | 11 | describe('API Gateway', () => { 12 | let service: ApiGatewayService; 13 | let apiKeyService: ApiKeyService; 14 | let rateLimitService: RateLimitService; 15 | 16 | beforeEach(async () => { 17 | const module: TestingModule = await Test.createTestingModule({ 18 | providers: [ 19 | ApiGatewayService, 20 | ApiKeyService, 21 | RateLimitService, 22 | AnalyticsService, 23 | { 24 | provide: getRepositoryToken(ApiKey), 25 | useValue: { 26 | create: jest.fn(), 27 | save: jest.fn(), 28 | findOne: jest.fn(), 29 | find: jest.fn(), 30 | delete: jest.fn(), 31 | }, 32 | }, 33 | { 34 | provide: getRepositoryToken(ApiUsage), 35 | useValue: { 36 | create: jest.fn(), 37 | save: jest.fn(), 38 | find: jest.fn(), 39 | count: jest.fn(), 40 | }, 41 | }, 42 | { 43 | provide: getRepositoryToken(ApiEndpoint), 44 | useValue: { 45 | create: jest.fn(), 46 | save: jest.fn(), 47 | findOne: jest.fn(), 48 | find: jest.fn(), 49 | delete: jest.fn(), 50 | }, 51 | }, 52 | { 53 | provide: 'REDIS_SERVICE', 54 | useValue: { 55 | get: jest.fn(), 56 | set: jest.fn(), 57 | incr: jest.fn(), 58 | expire: jest.fn(), 59 | }, 60 | }, 61 | ], 62 | }).compile(); 63 | 64 | service = module.get(ApiGatewayService); 65 | apiKeyService = module.get(ApiKeyService); 66 | rateLimitService = module.get(RateLimitService); 67 | }); 68 | 69 | it('should be defined', () => { 70 | expect(service).toBeDefined(); 71 | expect(apiKeyService).toBeDefined(); 72 | expect(rateLimitService).toBeDefined(); 73 | }); 74 | 75 | }); 76 | 77 | -------------------------------------------------------------------------------- /backend/src/credit-bureaus/credit-bureau.adapter.ts: -------------------------------------------------------------------------------- 1 | // Import axios using require syntax to avoid TypeScript issues 2 | const axios = require('axios'); 3 | 4 | /** 5 | * Normalized structure for credit reports from different bureaus 6 | */ 7 | export interface NormalizedCreditReport { 8 | userId: string; 9 | bureauSource: string; 10 | score: number; 11 | scoreRange: { 12 | min: number; 13 | max: number; 14 | }; 15 | accounts: Array<{ 16 | type: string; 17 | balance: number; 18 | paymentStatus: string; 19 | accountNumber: string; 20 | }>; 21 | inquiries: Array<{ 22 | date: Date; 23 | type: string; 24 | requestor: string; 25 | }>; 26 | publicRecords: Array<{ 27 | type: string; 28 | date: Date; 29 | amount?: number; 30 | status: string; 31 | }>; 32 | rawData?: any; // Original data (may be stored encrypted) 33 | } 34 | 35 | /** 36 | * Interface that all credit bureau adapters must implement 37 | */ 38 | export interface CreditBureauAdapter { 39 | getCreditReport(userId: string, extra?: any): Promise; 40 | handleWebhook(payload: any): Promise; 41 | } 42 | 43 | /** 44 | * Base class for credit bureau adapters with common functionality 45 | */ 46 | export abstract class BaseCreditBureauAdapter implements CreditBureauAdapter { 47 | protected axios: any; // Axios instance 48 | protected maxRetries = 3; 49 | 50 | constructor(axios: any) { 51 | this.axios = axios; 52 | } 53 | 54 | abstract getCreditReport(userId: string, extra?: any): Promise; 55 | abstract handleWebhook(payload: any): Promise; 56 | 57 | /** 58 | * Helper method to normalize bureau-specific fields to common format 59 | */ 60 | protected normalizeData(rawData: any): Partial { 61 | // This is a base implementation that specific adapters can extend 62 | return { 63 | rawData 64 | }; 65 | } 66 | 67 | /** 68 | * Helper method for retrying failed API calls 69 | */ 70 | protected async withRetry( 71 | operation: () => Promise, 72 | retries = this.maxRetries 73 | ): Promise { 74 | try { 75 | return await operation(); 76 | } catch (error) { 77 | if (retries <= 0) { 78 | throw error; 79 | } 80 | 81 | // Exponential backoff: 2^retryAttempt * 100ms 82 | const delay = Math.pow(2, this.maxRetries - retries) * 100; 83 | await new Promise(resolve => setTimeout(resolve, delay)); 84 | 85 | return this.withRetry(operation, retries - 1); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /backend/bankingAnalysis/PlaidService.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { PlaidApi, Configuration, PlaidEnvironments, CountryCode, Products } from 'plaid'; 4 | 5 | @Injectable() 6 | export class PlaidService { 7 | private readonly plaidClient: PlaidApi; 8 | 9 | constructor(private configService: ConfigService) { 10 | const configuration = new Configuration({ 11 | basePath: PlaidEnvironments[this.configService.get('PLAID_ENV')], 12 | baseOptions: { 13 | headers: { 14 | 'PLAID-CLIENT-ID': this.configService.get('PLAID_CLIENT_ID'), 15 | 'PLAID-SECRET': this.configService.get('PLAID_SECRET_SANDBOX'), 16 | }, 17 | }, 18 | }); 19 | 20 | this.plaidClient = new PlaidApi(configuration); 21 | } 22 | 23 | /** 24 | * Creates a link_token required to initialize the Plaid Link flow on the frontend. 25 | * This token authenticates our app with Plaid and customizes the Link experience. 26 | * @param userId The ID of the user initiating the connection. 27 | */ 28 | async createLinkToken(userId: string) { 29 | const request = { 30 | user: { 31 | client_user_id: userId, 32 | }, 33 | client_name: 'My Banking App', 34 | products: [Products.Auth, Products.Transactions], 35 | country_codes: [CountryCode.Us], 36 | language: 'en', 37 | webhook: 'https://your-api-url.com/banking/plaid/webhook', // A public URL for Plaid to send updates 38 | }; 39 | 40 | const response = await this.plaidClient.linkTokenCreate(request); 41 | return response.data; 42 | } 43 | 44 | /** 45 | * Exchanges a temporary public_token (from the frontend) for a permanent access_token. 46 | * This access_token is what we use to make API calls on behalf of the user. 47 | * @param publicToken The temporary token from Plaid Link. 48 | */ 49 | async exchangePublicToken(publicToken: string) { 50 | const response = await this.plaidClient.itemPublicTokenExchange({ 51 | public_token: publicToken, 52 | }); 53 | return response.data; // Contains access_token and item_id 54 | } 55 | 56 | /** 57 | * Fetches transaction data for a linked bank account. 58 | * @param accessToken The permanent token for the user's item. 59 | */ 60 | async getTransactions(accessToken: string) { 61 | const response = await this.plaidClient.transactionsGet({ 62 | access_token: accessToken, 63 | start_date: '2024-01-01', // Example start date 64 | end_date: '2024-12-31', // Example end date 65 | }); 66 | return response.data; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "credora", 3 | "version": "1.0.0", 4 | "description": "Credora is a decentralized identity and credit scoring protocol built on the Flare network. It enables users to verify their identity, build on-chain/off-chain reputation, and interact with DeFi applications in a privacy-preserving, composable way.", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "npx hardhat compile", 8 | "test": "npx hardhat test", 9 | "test:coverage": "npx hardhat coverage", 10 | "test:gas": "REPORT_GAS=true npx hardhat test", 11 | "deploy:local": "npx hardhat run scripts/deploy.ts --network localhost", 12 | "deploy:flare": "npx hardhat run scripts/deploy.ts --network flare", 13 | "typechain": "npx hardhat typechain", 14 | "lint": "eslint . --ext .ts", 15 | "lint:fix": "eslint . --ext .ts --fix", 16 | "dev": "concurrently \"npm run dev:backend\" \"npm run dev:frontend\"", 17 | "dev:backend": "npm run start:dev --workspace=backend", 18 | "dev:frontend": "npm run dev --workspace=frontend", 19 | "install:all": "npm install && npm install --workspace=backend && npm install --workspace=frontend" 20 | }, 21 | "keywords": [], 22 | "author": "", 23 | "license": "ISC", 24 | "devDependencies": { 25 | "@nomicfoundation/hardhat-toolbox": "^6.1.0", 26 | "@openzeppelin/hardhat-upgrades": "^3.9.1", 27 | "concurrently": "^9.2.0", 28 | "hardhat": "^0.0.7" 29 | }, 30 | "dependencies": { 31 | "@flarenetwork/flare-periphery-contract-artifacts": "^0.1.34", 32 | "@flarenetwork/flare-periphery-contracts": "^0.1.34", 33 | "@flarenetwork/flarejs": "^4.0.5", 34 | "@nestjs/common": "^11.1.6", 35 | "@nestjs/typeorm": "^11.0.0", 36 | "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", 37 | "@nomicfoundation/hardhat-ethers": "^3.0.0", 38 | "@nomicfoundation/hardhat-network-helpers": "^1.0.0", 39 | "@nomicfoundation/hardhat-toolbox": "^4.0.0", 40 | "@nomicfoundation/hardhat-verify": "^2.0.0", 41 | "@openzeppelin/contracts": "^5.4.0", 42 | "@typechain/ethers-v6": "^0.5.0", 43 | "@typechain/hardhat": "^9.0.0", 44 | "@types/chai": "^4.2.0", 45 | "@types/mocha": ">=9.1.0", 46 | "@types/node": ">=16.0.0", 47 | "@typescript-eslint/eslint-plugin": "^6.0.0", 48 | "@typescript-eslint/parser": "^6.0.0", 49 | "chai": "^4.2.0", 50 | "dotenv": "^17.2.1", 51 | "eslint": "^8.0.0", 52 | "ethers": "^6.4.0", 53 | "hardhat": "^2.19.0", 54 | "hardhat-gas-reporter": "^1.0.8", 55 | "solidity-coverage": "^0.8.1", 56 | "ts-node": ">=8.0.0", 57 | "typechain": "^8.3.0", 58 | "typeorm": "^0.3.25", 59 | "typescript": ">=4.5.0" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /backend/src/api-gateway/interceptors/circuit-breaker.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | NestInterceptor, 4 | ExecutionContext, 5 | CallHandler, 6 | Logger, 7 | ServiceUnavailableException, 8 | } from '@nestjs/common'; 9 | import { Reflector } from '@nestjs/core'; 10 | import { Observable, throwError, from } from 'rxjs'; 11 | import { tap, catchError, mergeMap } from 'rxjs/operators'; 12 | import { CircuitBreakerService, CircuitBreakerConfig } from '../services/circuit-breaker.service'; 13 | 14 | export const CIRCUIT_BREAKER_CONFIG = 'circuitBreakerConfig'; 15 | export const CircuitBreaker = (config: CircuitBreakerConfig) => 16 | Reflect.metadata(CIRCUIT_BREAKER_CONFIG, config); 17 | 18 | @Injectable() 19 | export class CircuitBreakerInterceptor implements NestInterceptor { 20 | private readonly logger = new Logger(CircuitBreakerInterceptor.name); 21 | 22 | constructor( 23 | private readonly reflector: Reflector, 24 | private readonly circuitBreakerService: CircuitBreakerService, 25 | ) {} 26 | 27 | intercept(context: ExecutionContext, next: CallHandler): Observable { 28 | const request = context.switchToHttp().getRequest(); 29 | const serviceName = this.getServiceName(context, request); 30 | 31 | const config = this.reflector.get( 32 | CIRCUIT_BREAKER_CONFIG, 33 | context.getHandler(), 34 | ); 35 | 36 | return from(this.circuitBreakerService.checkCircuitBreaker(serviceName, config)).pipe( 37 | mergeMap(isAllowed => { 38 | if (!isAllowed) { 39 | this.logger.warn(`Circuit breaker is OPEN for service: ${serviceName}`); 40 | return throwError(() => new ServiceUnavailableException('Service is temporarily unavailable')); 41 | } 42 | 43 | return next.handle().pipe( 44 | tap(() => { 45 | this.circuitBreakerService.recordSuccess(serviceName, config).catch(err => 46 | this.logger.error('Failed to record success', err), 47 | ); 48 | }), 49 | catchError(error => { 50 | this.circuitBreakerService.recordFailure(serviceName, config).catch(err => 51 | this.logger.error('Failed to record failure', err), 52 | ); 53 | return throwError(() => error); 54 | }), 55 | ); 56 | }), 57 | ); 58 | } 59 | 60 | private getServiceName(context: ExecutionContext, request: any): string { 61 | const className = context.getClass().name; 62 | const handlerName = context.getHandler().name; 63 | const endpoint = `${request.method}_${request.route?.path || request.url}`; 64 | 65 | return `${className}_${handlerName}_${endpoint}`; 66 | } 67 | } -------------------------------------------------------------------------------- /backend/src/api-gateway/services/health-monitor.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; 2 | import { Cron } from '@nestjs/schedule'; 3 | import { LoadBalancerService } from './load-balancer.service'; 4 | import { AnalyticsService } from './analytics.service'; 5 | import { CircuitBreakerService } from './circuit-breaker.service'; 6 | 7 | @Injectable() 8 | export class HealthMonitorService implements OnModuleInit { 9 | private readonly logger = new Logger(HealthMonitorService.name); 10 | 11 | constructor( 12 | private readonly loadBalancerService: LoadBalancerService, 13 | private readonly analyticsService: AnalyticsService, 14 | private readonly circuitBreakerService: CircuitBreakerService, 15 | ) {} 16 | 17 | onModuleInit() { 18 | this.logger.log('Health Monitor Service initialized'); 19 | } 20 | 21 | @Cron('*/30 * * * * *') 22 | async performHealthChecks() { 23 | try { 24 | const circuitBreakers = await this.circuitBreakerService.getAllCircuitBreakers(); 25 | 26 | for (const [serviceName, status] of Object.entries(circuitBreakers)) { 27 | if (status.state === 'OPEN') { 28 | this.logger.warn(`Service ${serviceName} circuit breaker is OPEN`); 29 | } 30 | } 31 | } catch (error) { 32 | this.logger.error('Health check failed', error); 33 | } 34 | } 35 | 36 | 37 | @Cron('0 0 * * *') 38 | async cleanupOldData() { 39 | try { 40 | const deletedRecords = await this.analyticsService.cleanupOldUsageData(90); 41 | this.logger.log(`Cleaned up ${deletedRecords} old usage records`); 42 | } catch (error) { 43 | this.logger.error('Data cleanup failed', error); 44 | } 45 | } 46 | 47 | 48 | @Cron('0 1 * * *') 49 | async generateDailyReport() { 50 | try { 51 | const yesterday = new Date(); 52 | yesterday.setDate(yesterday.getDate() - 1); 53 | yesterday.setHours(0, 0, 0, 0); 54 | 55 | const today = new Date(); 56 | today.setHours(0, 0, 0, 0); 57 | 58 | const metrics = await this.analyticsService.getUsageMetrics(yesterday, today); 59 | const traffic = await this.analyticsService.getTrafficAnalytics(yesterday, today); 60 | const performance = await this.analyticsService.getPerformanceMetrics(yesterday, today); 61 | 62 | this.logger.log('Daily Report Generated', { 63 | date: yesterday.toISOString().split('T')[0], 64 | totalRequests: metrics.totalRequests, 65 | successRate: metrics.successRate.toFixed(2) + '%', 66 | avgResponseTime: performance.p50ResponseTime + 'ms', 67 | topEndpoints: traffic.trafficByEndpoint.slice(0, 5), 68 | }); 69 | 70 | } catch (error) { 71 | this.logger.error('Daily report generation failed', error); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /backend/src/transaction-analysis/services/__tests__/fraud-detection.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, type TestingModule } from "@nestjs/testing" 2 | import { FraudDetectionService } from "../fraud-detection.service" 3 | import { mockTransactions } from "../../../test/mocks/transaction.mock" 4 | 5 | describe("FraudDetectionService", () => { 6 | let service: FraudDetectionService 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | providers: [FraudDetectionService], 11 | }).compile() 12 | 13 | service = module.get(FraudDetectionService) 14 | }) 15 | 16 | describe("detectFraud", () => { 17 | it("should detect low risk for normal transaction", async () => { 18 | const transaction = mockTransactions[0] 19 | 20 | const result = await service.detectFraud(transaction) 21 | 22 | expect(result.riskScore).toBeLessThan(0.3) 23 | expect(result.fraudScore).toBeLessThan(0.2) 24 | expect(result.indicators).toHaveLength(0) 25 | }) 26 | 27 | it("should detect high risk for unusual amount", async () => { 28 | const transaction = { 29 | ...mockTransactions[0], 30 | amount: -5000, // Unusually high amount 31 | } 32 | 33 | const result = await service.detectFraud(transaction) 34 | 35 | expect(result.riskScore).toBeGreaterThan(0.7) 36 | expect(result.indicators).toContain("UNUSUAL_AMOUNT") 37 | }) 38 | 39 | it("should detect suspicious location patterns", async () => { 40 | const transaction = { 41 | ...mockTransactions[0], 42 | location: "Unknown Location, Foreign Country", 43 | metadata: { country: "XX", city: "Unknown" }, 44 | } 45 | 46 | const result = await service.detectFraud(transaction) 47 | 48 | expect(result.indicators).toContain("SUSPICIOUS_LOCATION") 49 | }) 50 | 51 | it("should detect velocity fraud patterns", async () => { 52 | const transactions = Array(10) 53 | .fill(null) 54 | .map((_, i) => ({ 55 | ...mockTransactions[0], 56 | id: `tx-${i}`, 57 | timestamp: new Date(Date.now() - i * 60000), // 1 minute apart 58 | amount: -100, 59 | })) 60 | 61 | const result = await service.detectVelocityFraud(transactions) 62 | 63 | expect(result.riskScore).toBeGreaterThan(0.8) 64 | expect(result.indicators).toContain("HIGH_VELOCITY") 65 | }) 66 | }) 67 | 68 | describe("analyzeTransactionPatterns", () => { 69 | it("should identify normal spending patterns", async () => { 70 | const transactions = mockTransactions 71 | 72 | const result = await service.analyzeTransactionPatterns(transactions) 73 | 74 | expect(result.patterns).toBeDefined() 75 | expect(result.anomalies).toBeDefined() 76 | expect(result.riskFactors).toBeDefined() 77 | }) 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /backend/src/api-gateway/guards/api-key.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | CanActivate, 4 | ExecutionContext, 5 | UnauthorizedException, 6 | ForbiddenException, 7 | Logger, 8 | SetMetadata, 9 | } from '@nestjs/common'; 10 | import { Reflector } from '@nestjs/core'; 11 | import { ApiKeyService } from '../services/api-key.service'; 12 | 13 | export const API_KEY_REQUIRED = 'apiKeyRequired'; 14 | export const REQUIRED_PERMISSIONS = 'requiredPermissions'; 15 | 16 | export const RequireApiKey = () => SetMetadata(API_KEY_REQUIRED, true); 17 | export const RequirePermissions = (...permissions: string[]) => 18 | SetMetadata(REQUIRED_PERMISSIONS, permissions); 19 | 20 | @Injectable() 21 | export class ApiKeyGuard implements CanActivate { 22 | private readonly logger = new Logger(ApiKeyGuard.name); 23 | 24 | constructor( 25 | private readonly reflector: Reflector, 26 | private readonly apiKeyService: ApiKeyService, 27 | ) {} 28 | 29 | async canActivate(context: ExecutionContext): Promise { 30 | const request = context.switchToHttp().getRequest(); 31 | 32 | const apiKeyRequired = this.reflector.getAllAndOverride( 33 | API_KEY_REQUIRED, 34 | [context.getHandler(), context.getClass()], 35 | ); 36 | 37 | if (!apiKeyRequired) { 38 | return true; 39 | } 40 | 41 | const apiKeyHeader = request.headers['x-api-key']; 42 | 43 | if (!apiKeyHeader) { 44 | throw new UnauthorizedException('API key is required'); 45 | } 46 | 47 | try { 48 | const apiKey = await this.apiKeyService.validateApiKey(apiKeyHeader); 49 | 50 | if (!apiKey) { 51 | throw new UnauthorizedException('Invalid API key'); 52 | } 53 | 54 | const requiredPermissions = this.reflector.getAllAndOverride( 55 | REQUIRED_PERMISSIONS, 56 | [context.getHandler(), context.getClass()], 57 | ); 58 | 59 | if (requiredPermissions && requiredPermissions.length > 0) { 60 | const hasPermission = await this.checkPermissions(apiKey, requiredPermissions); 61 | 62 | if (!hasPermission) { 63 | throw new ForbiddenException('Insufficient permissions'); 64 | } 65 | } 66 | 67 | request.apiKey = apiKey; 68 | 69 | return true; 70 | } catch (error) { 71 | if (error instanceof UnauthorizedException || error instanceof ForbiddenException) { 72 | throw error; 73 | } 74 | 75 | this.logger.error('API key validation failed', error); 76 | throw new UnauthorizedException('API key validation failed'); 77 | } 78 | } 79 | 80 | private async checkPermissions(apiKey: any, requiredPermissions: string[]): Promise { 81 | for (const permission of requiredPermissions) { 82 | const hasPermission = await this.apiKeyService.hasPermission(apiKey, permission); 83 | if (!hasPermission) { 84 | return false; 85 | } 86 | } 87 | return true; 88 | } 89 | } -------------------------------------------------------------------------------- /backend/src/credit-bureaus/README.md: -------------------------------------------------------------------------------- 1 | # Credit Bureau Integration 2 | 3 | This module provides integration with major credit bureaus (Experian, Equifax, TransUnion) to fetch credit reports and process credit data updates. 4 | 5 | ## Features 6 | 7 | - Adapter pattern for different credit bureau APIs 8 | - Normalized response format for consistent data structure 9 | - Circuit breaker pattern for resilience 10 | - Rate limiting and quota management via axios interceptors 11 | - Error handling and retry logic with exponential backoff 12 | - Webhook handling for updates from credit bureaus 13 | - Support for sandbox/production environment switching 14 | - Comprehensive test coverage 15 | 16 | ## Usage 17 | 18 | ### Getting Credit Reports 19 | 20 | ```typescript 21 | // Inject CreditBureauService 22 | constructor(private creditBureauService: CreditBureauService) {} 23 | 24 | // Get credit report from a specific bureau 25 | const experianReport = await this.creditBureauService.getCreditReport( 26 | 'experian', 27 | userId, 28 | { 29 | firstName: 'John', 30 | lastName: 'Doe', 31 | ssn: '123-45-6789', 32 | dob: '1990-01-01' 33 | } 34 | ); 35 | 36 | // Get reports from all bureaus 37 | const allReports = await this.creditBureauService.getAllCreditReports(userId); 38 | ``` 39 | 40 | ### API Endpoints 41 | 42 | - `GET /credit-bureaus/reports/:userId/:bureau` - Get credit report from specific bureau 43 | - `GET /credit-bureaus/reports/:userId` - Get credit reports from all bureaus 44 | - `POST /credit-bureaus/reports/request` - Request new credit report 45 | - `POST /credit-bureaus/webhooks/:bureau` - Handle webhook from bureau 46 | 47 | ## Configuration 48 | 49 | Add the following configuration to your `.env` file: 50 | 51 | ``` 52 | # Experian 53 | CREDIT_BUREAU_EXPERIAN_API_KEY=your_experian_api_key 54 | CREDIT_BUREAU_EXPERIAN_SANDBOX=true 55 | CREDIT_BUREAU_EXPERIAN_SANDBOX_URL=https://sandbox-api.experian.com 56 | CREDIT_BUREAU_EXPERIAN_PRODUCTION_URL=https://api.experian.com 57 | 58 | # Equifax 59 | CREDIT_BUREAU_EQUIFAX_API_KEY=your_equifax_api_key 60 | CREDIT_BUREAU_EQUIFAX_SANDBOX=true 61 | CREDIT_BUREAU_EQUIFAX_SANDBOX_URL=https://sandbox-api.equifax.com 62 | CREDIT_BUREAU_EQUIFAX_PRODUCTION_URL=https://api.equifax.com 63 | 64 | # TransUnion 65 | CREDIT_BUREAU_TRANSUNION_API_KEY=your_transunion_api_key 66 | CREDIT_BUREAU_TRANSUNION_SANDBOX=true 67 | CREDIT_BUREAU_TRANSUNION_SANDBOX_URL=https://sandbox-api.transunion.com 68 | CREDIT_BUREAU_TRANSUNION_PRODUCTION_URL=https://api.transunion.com 69 | ``` 70 | 71 | ## Architecture 72 | 73 | The module uses the adapter pattern to normalize interactions with different credit bureau APIs: 74 | 75 | - `CreditBureauAdapter` - Interface that all adapters implement 76 | - `BaseCreditBureauAdapter` - Common functionality for all adapters 77 | - Specific adapters (`ExperianAdapter`, `EquifaxAdapter`, `TransUnionAdapter`) 78 | - `CreditBureauService` - Orchestrates adapters and provides circuit breaking 79 | - `CreditBureauController` - Exposes API endpoints 80 | -------------------------------------------------------------------------------- /backend/src/api-gateway/dto/endpoint.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsString, IsOptional, IsObject, IsNumber, IsBoolean, IsUrl } from 'class-validator'; 2 | import { ApiProperty } from '@nestjs/swagger'; 3 | 4 | export class CreateEndpointDto { 5 | @ApiProperty({ description: 'API endpoint path' }) 6 | @IsString() 7 | path: string; 8 | 9 | @ApiProperty({ description: 'HTTP method' }) 10 | @IsString() 11 | method: string; 12 | 13 | @ApiProperty({ description: 'API version', default: 'v1' }) 14 | @IsString() 15 | version: string; 16 | 17 | @ApiProperty({ description: 'Target URL or service instances' }) 18 | @IsString() 19 | targetUrl: string; 20 | 21 | @ApiProperty({ description: 'Request/Response transformation rules', required: false }) 22 | @IsOptional() 23 | @IsObject() 24 | transformationRules?: Record; 25 | 26 | @ApiProperty({ description: 'Rate limiting configuration', required: false }) 27 | @IsOptional() 28 | @IsObject() 29 | rateLimitConfig?: Record; 30 | 31 | @ApiProperty({ description: 'Circuit breaker configuration', required: false }) 32 | @IsOptional() 33 | @IsObject() 34 | circuitBreakerConfig?: Record; 35 | 36 | @ApiProperty({ description: 'Custom headers', required: false }) 37 | @IsOptional() 38 | @IsObject() 39 | headers?: Record; 40 | 41 | @ApiProperty({ description: 'Request timeout in milliseconds', required: false, default: 30000 }) 42 | @IsOptional() 43 | @IsNumber() 44 | timeout?: number; 45 | } 46 | 47 | export class UpdateEndpointDto { 48 | @ApiProperty({ description: 'API endpoint path', required: false }) 49 | @IsOptional() 50 | @IsString() 51 | path?: string; 52 | 53 | @ApiProperty({ description: 'HTTP method', required: false }) 54 | @IsOptional() 55 | @IsString() 56 | method?: string; 57 | 58 | @ApiProperty({ description: 'API version', required: false }) 59 | @IsOptional() 60 | @IsString() 61 | version?: string; 62 | 63 | @ApiProperty({ description: 'Target URL or service instances', required: false }) 64 | @IsOptional() 65 | @IsString() 66 | targetUrl?: string; 67 | 68 | @ApiProperty({ description: 'Active status', required: false }) 69 | @IsOptional() 70 | @IsBoolean() 71 | isActive?: boolean; 72 | 73 | @ApiProperty({ description: 'Request/Response transformation rules', required: false }) 74 | @IsOptional() 75 | @IsObject() 76 | transformationRules?: Record; 77 | 78 | @ApiProperty({ description: 'Rate limiting configuration', required: false }) 79 | @IsOptional() 80 | @IsObject() 81 | rateLimitConfig?: Record; 82 | 83 | @ApiProperty({ description: 'Circuit breaker configuration', required: false }) 84 | @IsOptional() 85 | @IsObject() 86 | circuitBreakerConfig?: Record; 87 | 88 | @ApiProperty({ description: 'Custom headers', required: false }) 89 | @IsOptional() 90 | @IsObject() 91 | headers?: Record; 92 | 93 | @ApiProperty({ description: 'Request timeout in milliseconds', required: false }) 94 | @IsOptional() 95 | @IsNumber() 96 | timeout?: number; 97 | } 98 | -------------------------------------------------------------------------------- /backend/bankingAnalysis/banking-service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, InternalServerErrorException, UnauthorizedException } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { PlaidService } from './plaid.service'; 4 | import { encrypt, decrypt } from './utils/encryption.util'; 5 | 6 | @Injectable() 7 | export class BankingService { 8 | // In a real application, you would store this in a database (e.g., using TypeORM). 9 | // We map a user ID to their encrypted Plaid access token. 10 | private linkedAccounts = new Map(); 11 | private readonly encryptionKey: string; 12 | 13 | constructor( 14 | private readonly plaidService: PlaidService, 15 | private readonly configService: ConfigService, 16 | ) { 17 | this.encryptionKey = this.configService.get('ENCRYPTION_KEY'); 18 | if (!this.encryptionKey || this.encryptionKey.length < 32) { 19 | throw new InternalServerErrorException('A 32-byte ENCRYPTION_KEY must be configured.'); 20 | } 21 | } 22 | 23 | /** 24 | * Orchestrates the creation of a Plaid Link token for the frontend. 25 | * @param userId The ID of the user linking their account. 26 | */ 27 | async generateLinkToken(userId: string) { 28 | // This calls the PlaidService to get a token from the Plaid API. 29 | return this.plaidService.createLinkToken(userId); 30 | } 31 | 32 | /** 33 | * Sets a linked account for a user by exchanging a public token for an access token 34 | * and storing the encrypted access token. 35 | * @param userId The ID of the user. 36 | * @param publicToken The temporary public token from the Plaid Link frontend flow. 37 | */ 38 | async setLinkedAccount(userId: string, publicToken: string) { 39 | const tokenData = await this.plaidService.exchangePublicToken(publicToken); 40 | const accessToken = tokenData.access_token; 41 | 42 | // Encrypt the access token before storing it. 43 | const encryptedAccessToken = encrypt(accessToken, this.encryptionKey); 44 | 45 | // Store the encrypted token, linking it to our internal user ID. 46 | this.linkedAccounts.set(userId, encryptedAccessToken); 47 | console.log(`Account linked for user ${userId}.`); 48 | 49 | return { message: 'Bank account linked successfully.' }; 50 | } 51 | 52 | /** 53 | * Retrieves transactions for a user's linked account. 54 | * @param userId The ID of the user. 55 | */ 56 | async getUserTransactions(userId: string) { 57 | const encryptedAccessToken = this.linkedAccounts.get(userId); 58 | if (!encryptedAccessToken) { 59 | throw new UnauthorizedException('User does not have a linked bank account.'); 60 | } 61 | 62 | // Decrypt the token just before using it. 63 | const accessToken = decrypt(encryptedAccessToken, this.encryptionKey); 64 | 65 | // Use the decrypted token to fetch transactions from Plaid. 66 | const transactionsData = await this.plaidService.getTransactions(accessToken); 67 | 68 | // Here, you could implement transaction categorization and analysis algorithms. 69 | console.log(`Fetched ${transactionsData.transactions.length} transactions for user ${userId}.`); 70 | 71 | return transactionsData; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /backend/src/transaction-analysis/services/__tests__/categorization.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, type TestingModule } from "@nestjs/testing" 2 | import { CategorizationService } from "../categorization.service" 3 | import { mockTransactions } from "../../../test/mocks/transaction.mock" 4 | 5 | describe("CategorizationService", () => { 6 | let service: CategorizationService 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | providers: [CategorizationService], 11 | }).compile() 12 | 13 | service = module.get(CategorizationService) 14 | }) 15 | 16 | describe("categorizeTransaction", () => { 17 | it("should categorize restaurant transaction correctly", async () => { 18 | const transaction = mockTransactions.find((t) => t.description.includes("Restaurant")) 19 | 20 | const result = await service.categorizeTransaction(transaction) 21 | 22 | expect(result.category).toBe("FOOD_DINING") 23 | expect(result.subcategory).toBe("Restaurant") 24 | expect(result.confidence).toBeGreaterThan(0.8) 25 | }) 26 | 27 | it("should categorize gas station transaction correctly", async () => { 28 | const transaction = { 29 | ...mockTransactions[0], 30 | description: "SHELL GAS STATION", 31 | merchant: "Shell", 32 | amount: -45.5, 33 | } 34 | 35 | const result = await service.categorizeTransaction(transaction) 36 | 37 | expect(result.category).toBe("TRANSPORTATION") 38 | expect(result.subcategory).toBe("Gas") 39 | expect(result.confidence).toBeGreaterThan(0.8) 40 | }) 41 | 42 | it("should handle unknown merchants with lower confidence", async () => { 43 | const transaction = { 44 | ...mockTransactions[0], 45 | description: "UNKNOWN MERCHANT XYZ", 46 | merchant: "Unknown XYZ", 47 | amount: -25.0, 48 | } 49 | 50 | const result = await service.categorizeTransaction(transaction) 51 | 52 | expect(result.category).toBeDefined() 53 | expect(result.confidence).toBeLessThan(0.7) 54 | }) 55 | }) 56 | 57 | describe("categorizeTransactionsBatch", () => { 58 | it("should categorize multiple transactions efficiently", async () => { 59 | const transactions = mockTransactions.slice(0, 5) 60 | 61 | const results = await service.categorizeTransactionsBatch(transactions) 62 | 63 | expect(results).toHaveLength(5) 64 | results.forEach((result) => { 65 | expect(result.category).toBeDefined() 66 | expect(result.subcategory).toBeDefined() 67 | expect(result.confidence).toBeGreaterThan(0) 68 | }) 69 | }) 70 | }) 71 | 72 | describe("trainModel", () => { 73 | it("should train categorization model with labeled data", async () => { 74 | const labeledData = mockTransactions.map((t) => ({ 75 | transaction: t, 76 | category: "FOOD_DINING", 77 | subcategory: "Restaurant", 78 | })) 79 | 80 | const result = await service.trainModel(labeledData) 81 | 82 | expect(result.accuracy).toBeGreaterThan(0.8) 83 | expect(result.modelVersion).toBeDefined() 84 | }) 85 | }) 86 | }) 87 | -------------------------------------------------------------------------------- /backend/src/transaction-analysis/controllers/__tests__/transaction-analysis.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, type TestingModule } from "@nestjs/testing" 2 | import { TransactionAnalysisController } from "../transaction-analysis.controller" 3 | import { TransactionAnalysisService } from "../../services/transaction-analysis.service" 4 | import { jest } from "@jest/globals" 5 | 6 | describe("TransactionAnalysisController", () => { 7 | let controller: TransactionAnalysisController 8 | let service: TransactionAnalysisService 9 | 10 | beforeEach(async () => { 11 | const module: TestingModule = await Test.createTestingModule({ 12 | controllers: [TransactionAnalysisController], 13 | providers: [ 14 | { 15 | provide: TransactionAnalysisService, 16 | useValue: { 17 | analyzeTransaction: jest.fn(), 18 | analyzeUserTransactions: jest.fn(), 19 | calculateCreditScore: jest.fn(), 20 | getAnalysisHistory: jest.fn(), 21 | }, 22 | }, 23 | ], 24 | }).compile() 25 | 26 | controller = module.get(TransactionAnalysisController) 27 | service = module.get(TransactionAnalysisService) 28 | }) 29 | 30 | describe("analyzeTransaction", () => { 31 | it("should analyze single transaction", async () => { 32 | const transactionId = "tx-1" 33 | const mockAnalysis = { 34 | id: "1", 35 | transactionId, 36 | category: "FOOD_DINING", 37 | riskScore: 0.2, 38 | fraudScore: 0.1, 39 | } 40 | 41 | jest.spyOn(service, "analyzeTransaction").mockResolvedValue(mockAnalysis as any) 42 | 43 | const result = await controller.analyzeTransaction(transactionId) 44 | 45 | expect(result).toEqual(mockAnalysis) 46 | expect(service.analyzeTransaction).toHaveBeenCalledWith(transactionId) 47 | }) 48 | }) 49 | 50 | describe("analyzeUserTransactions", () => { 51 | it("should analyze all user transactions", async () => { 52 | const userId = "user-1" 53 | const mockAnalysis = { 54 | userId, 55 | totalTransactions: 10, 56 | spendingPatterns: {}, 57 | riskAssessment: {}, 58 | } 59 | 60 | jest.spyOn(service, "analyzeUserTransactions").mockResolvedValue(mockAnalysis as any) 61 | 62 | const result = await controller.analyzeUserTransactions(userId) 63 | 64 | expect(result).toEqual(mockAnalysis) 65 | expect(service.analyzeUserTransactions).toHaveBeenCalledWith(userId) 66 | }) 67 | }) 68 | 69 | describe("getCreditScore", () => { 70 | it("should calculate and return credit score", async () => { 71 | const userId = "user-1" 72 | const mockScore = { 73 | score: 750, 74 | factors: ["Payment history", "Credit utilization"], 75 | recommendations: ["Pay bills on time"], 76 | } 77 | 78 | jest.spyOn(service, "calculateCreditScore").mockResolvedValue(mockScore as any) 79 | 80 | const result = await controller.getCreditScore(userId) 81 | 82 | expect(result).toEqual(mockScore) 83 | expect(service.calculateCreditScore).toHaveBeenCalledWith(userId) 84 | }) 85 | }) 86 | }) 87 | -------------------------------------------------------------------------------- /backend/src/anonymization/controllers/anonymization.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Post, Body, Query, ParseIntPipe } from '@nestjs/common'; 2 | import { AnonymizeDataDto } from '../dto/anonymize-data.dto'; 3 | import { PiiDetectionService } from '../services/pii-detection.service'; 4 | import { PseudonymizationService } from '../services/pseudonymization.service'; 5 | import { KAnonymityService } from '../services/k-anonymity.service'; 6 | import { DifferentialPrivacyService } from '../services/differential-privacy.service'; 7 | import { IsNumber, IsNotEmpty } from 'class-validator'; 8 | 9 | // A DTO for the K-anonymity endpoint 10 | class KAnonymityDto { 11 | @IsNotEmpty() 12 | readonly data: any[]; 13 | } 14 | 15 | // A DTO for the differential privacy endpoint 16 | class DifferentialPrivacyDto { 17 | @IsNumber() 18 | readonly value: number; 19 | } 20 | 21 | @Controller('anonymize') 22 | export class AnonymizationController { 23 | constructor( 24 | private readonly piiDetectionService: PiiDetectionService, 25 | private readonly pseudonymizationService: PseudonymizationService, 26 | private readonly kAnonymityService: KAnonymityService, 27 | private readonly differentialPrivacyService: DifferentialPrivacyService, 28 | ) {} 29 | 30 | @Post('mask') 31 | maskData(@Body() anonymizeDataDto: AnonymizeDataDto) { 32 | const maskedData = this.piiDetectionService.detectAndMaskPii(anonymizeDataDto.data); 33 | return { original: anonymizeDataDto.data, masked: maskedData }; 34 | } 35 | 36 | @Post('pseudonymize') 37 | pseudonymizeData(@Body() anonymizeDataDto: AnonymizeDataDto) { 38 | const pseudonymizedData = this.pseudonymizationService.pseudonymize(anonymizeDataDto.data); 39 | return { original: anonymizeDataDto.data, pseudonymized: pseudonymizedData }; 40 | } 41 | 42 | @Post('reverse-pseudonymize') 43 | reversePseudonymizeData(@Body() anonymizeDataDto: AnonymizeDataDto) { 44 | const originalData = this.pseudonymizationService.reversePseudonym(anonymizeDataDto.data); 45 | return { pseudonymized: anonymizeDataDto.data, original: originalData }; 46 | } 47 | 48 | @Post('k-anonymity') 49 | applyKAnonymity( 50 | @Body() kAnonymityDto: KAnonymityDto, 51 | @Query('k', ParseIntPipe) k: number, 52 | ) { 53 | // You would replace this with your actual quasi-identifiers from the data. 54 | const quasiIdentifiers = ['city', 'age']; 55 | const anonymizedData = this.kAnonymityService.applyKAnonymity( 56 | kAnonymityDto.data, 57 | quasiIdentifiers, 58 | k, 59 | ); 60 | return { original: kAnonymityDto.data, anonymized: anonymizedData }; 61 | } 62 | 63 | @Post('differential-privacy') 64 | addDifferentialPrivacy( 65 | @Body() differentialPrivacyDto: DifferentialPrivacyDto, 66 | @Query('epsilon', ParseIntPipe) epsilon: number, 67 | ) { 68 | // A simplified sensitivity for demonstration purposes. 69 | const sensitivity = 1; 70 | const noisyValue = this.differentialPrivacyService.addNoise( 71 | differentialPrivacyDto.value, 72 | epsilon, 73 | sensitivity, 74 | ); 75 | return { 76 | original: differentialPrivacyDto.value, 77 | noisy: noisyValue, 78 | epsilon: epsilon, 79 | }; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /backend/src/api-gateway/services/rate-limit.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Logger } from '@nestjs/common'; 2 | import { RedisService } from '../../redis/redis.service'; 3 | 4 | export interface RateLimitConfig { 5 | windowMs: number; 6 | maxRequests: number; 7 | keyGenerator?: (req: any) => string; 8 | skipSuccessfulRequests?: boolean; 9 | skipFailedRequests?: boolean; 10 | } 11 | 12 | export interface RateLimitResult { 13 | allowed: boolean; 14 | remaining: number; 15 | resetTime: Date; 16 | totalHits: number; 17 | } 18 | 19 | @Injectable() 20 | export class RateLimitService { 21 | private readonly logger = new Logger(RateLimitService.name); 22 | 23 | constructor(private readonly redisService: RedisService) {} 24 | 25 | async checkRateLimit( 26 | key: string, 27 | config: RateLimitConfig, 28 | ): Promise { 29 | try { 30 | const windowStart = Math.floor(Date.now() / config.windowMs); 31 | const redisKey = `rate_limit:${key}:${windowStart}`; 32 | 33 | this.redisService.incr(redisKey); 34 | const currentStr = await this.redisService.get(redisKey); 35 | const current = parseInt(currentStr ?? '0', 10); 36 | if (current === 1) { 37 | this.redisService.expire(redisKey, Math.ceil(config.windowMs / 1000)); 38 | } 39 | const resetTime = new Date((windowStart + 1) * config.windowMs); 40 | const remaining = Math.max(0, config.maxRequests - current); 41 | 42 | return { 43 | allowed: current <= config.maxRequests, 44 | remaining, 45 | resetTime, 46 | totalHits: current, 47 | }; 48 | } catch (error) { 49 | this.logger.error('Rate limit check failed', error); 50 | return { 51 | allowed: true, 52 | remaining: config.maxRequests, 53 | resetTime: new Date(Date.now() + config.windowMs), 54 | totalHits: 0, 55 | }; 56 | } 57 | } 58 | 59 | async resetRateLimit(key: string): Promise { 60 | try { 61 | const pattern = `rate_limit:${key}:*`; 62 | const keys = await this.redisService.keys(pattern); 63 | 64 | if (keys && keys.length > 0) { 65 | await this.redisService.del(keys[0]); 66 | } 67 | } catch (error) { 68 | this.logger.error('Rate limit reset failed', error); 69 | } 70 | } 71 | 72 | async getRateLimitStatus(key: string, windowMs: number): Promise { 73 | try { 74 | const windowStart = Math.floor(Date.now() / windowMs); 75 | const redisKey = `rate_limit:${key}:${windowStart}`; 76 | 77 | const current = await this.redisService.get(redisKey); 78 | return parseInt(current ?? '0', 10); 79 | } catch (error) { 80 | this.logger.error('Rate limit status check failed', error); 81 | return 0; 82 | } 83 | } 84 | 85 | generateApiKeyRateLimitKey(apiKey: string): string { 86 | return `api_key:${apiKey}`; 87 | } 88 | 89 | generateUserRateLimitKey(userId: string): string { 90 | return `user:${userId}`; 91 | } 92 | 93 | generateIPRateLimitKey(ip: string): string { 94 | return `ip:${ip}`; 95 | } 96 | 97 | generateEndpointRateLimitKey(endpoint: string, method: string): string { 98 | return `endpoint:${method}:${endpoint}`; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /backend/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ConfigModule, ConfigService } from '@nestjs/config'; 3 | import { TypeOrmModule } from '@nestjs/typeorm'; 4 | import { AppController } from './app.controller'; 5 | import { AppService } from './app.service'; 6 | import { UsersModule } from './users/users.module'; 7 | import { PrivacyModule } from './privacy/privacy.module'; 8 | import { AuthModule } from './auth/auth.module'; 9 | import { RedisModule } from './redis/redis.module'; 10 | import { EmailModule } from './email/email.module'; 11 | import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core'; 12 | import { JwtAuthGuard } from './auth/guards/jwt-auth.guard'; 13 | import { ThrottlerModule } from '@nestjs/throttler'; 14 | import { IpfsModule } from './ipfs/ipfs.module'; 15 | import { CreditBureauModule } from './credit-bureaus/credit-bureau.module'; 16 | import { DocumentsModule } from './documents/documents.module'; 17 | import { BullModule } from '@nestjs/bull'; 18 | import { RiskModule } from './risk/risk.module'; 19 | import { AuditModule } from './audit/audit.module'; 20 | import { AuditInterceptor } from './audit/audit.interceptor'; 21 | import { ScreeningModule } from './screening/screening.module'; 22 | import { ApiGatewayModule } from './api-gateway/api-gateway.module'; 23 | 24 | @Module({ 25 | imports: [ 26 | // 1. Load the .env file 27 | ConfigModule.forRoot({ 28 | isGlobal: true, // Makes the .env variables available everywhere 29 | envFilePath: '.env', 30 | }), 31 | 32 | // 2. Setup the TypeORM connection 33 | TypeOrmModule.forRootAsync({ 34 | imports: [UsersModule, ConfigModule.forRoot({ isGlobal: true })], 35 | inject: [ConfigService], 36 | useFactory: (configService: ConfigService) => ({ 37 | type: 'postgres', 38 | host: configService.get('DB_HOST'), 39 | port: configService.get('DB_PORT'), 40 | username: configService.get('DB_USERNAME'), 41 | password: configService.get('DB_PASSWORD'), 42 | database: configService.get('DB_NAME'), 43 | entities: ['src/**/*.entity.ts', 'dist/**/*.entity.js'], 44 | synchronize: false, // Use migrations instead 45 | logging: true, 46 | extra: { 47 | max: configService.get('DB_POOL_MAX', 20), // Default to 20 if not set 48 | }, 49 | }), 50 | }), 51 | // 3. Register Bull queue for document processing 52 | BullModule.forRoot({ 53 | redis: { 54 | host: process.env.REDIS_HOST || 'localhost', 55 | port: Number.parseInt(process.env.REDIS_PORT || '6379', 10), 56 | }, 57 | }), 58 | ThrottlerModule.forRoot([ 59 | { 60 | ttl: 60000, 61 | limit: 10, 62 | }, 63 | ]), 64 | UsersModule, 65 | AuthModule, 66 | RedisModule, 67 | EmailModule, 68 | PrivacyModule, 69 | DocumentsModule, 70 | IpfsModule, 71 | // Credit Bureau Integration 72 | CreditBureauModule, 73 | RiskModule, 74 | AuditModule, 75 | ScreeningModule, 76 | ApiGatewayModule, 77 | ], 78 | controllers: [AppController], 79 | providers: [ 80 | AppService, 81 | { 82 | provide: APP_GUARD, 83 | useClass: JwtAuthGuard, 84 | }, 85 | { 86 | provide: APP_INTERCEPTOR, 87 | useClass: AuditInterceptor, 88 | }, 89 | ], 90 | }) 91 | export class AppModule {} 92 | -------------------------------------------------------------------------------- /backend/src/anonymization/services/k-anonymity.service.ts: -------------------------------------------------------------------------------- 1 | // A service for implementing K-anonymity on datasets. 2 | import { Injectable } from '@nestjs/common'; 3 | 4 | @Injectable() 5 | export class KAnonymityService { 6 | /** 7 | * Applies k-anonymity to a dataset based on specified quasi-identifiers. 8 | * This is a simplified example using generalization. 9 | * In a real-world scenario, this would involve more complex algorithms. 10 | * 11 | * @param data The input array of objects to anonymize. 12 | * @param quasiIdentifiers An array of keys to identify quasi-identifiers. 13 | * @param k The anonymity factor. 14 | * @returns The anonymized dataset. 15 | */ 16 | applyKAnonymity(data: T[], quasiIdentifiers: (keyof T)[], k: number): T[] { 17 | const anonymizedData: T[] = []; 18 | const groups = this.groupDataByQuasiIdentifiers(data, quasiIdentifiers); 19 | 20 | for (const groupKey in groups) { 21 | if (groups[groupKey].length >= k) { 22 | // This group meets the k-anonymity requirement. Add it to the result. 23 | anonymizedData.push(...groups[groupKey]); 24 | } else { 25 | // If a group does not meet the requirement, generalize or suppress it. 26 | // Here, we'll demonstrate a simple generalization. 27 | anonymizedData.push(...this.generalizeGroup(groups[groupKey], quasiIdentifiers)); 28 | } 29 | } 30 | 31 | return anonymizedData; 32 | } 33 | 34 | /** 35 | * Groups data objects by the values of their quasi-identifiers. 36 | * @param data The input array of objects. 37 | * @param quasiIdentifiers The keys to group by. 38 | * @returns An object where keys are the combined quasi-identifier values and 39 | * values are the arrays of data objects belonging to that group. 40 | */ 41 | private groupDataByQuasiIdentifiers(data: T[], quasiIdentifiers: (keyof T)[]): { [key: string]: T[] } { 42 | const groups: { [key: string]: T[] } = {}; 43 | for (const item of data) { 44 | const groupKey = quasiIdentifiers.map(key => item[key]).join('|'); 45 | if (!groups[groupKey]) { 46 | groups[groupKey] = []; 47 | } 48 | groups[groupKey].push(item); 49 | } 50 | return groups; 51 | } 52 | 53 | /** 54 | * Generalizes a group's quasi-identifiers if the group is too small. 55 | * This is a placeholder for a more sophisticated generalization strategy. 56 | * @param group The small group of data objects. 57 | * @param quasiIdentifiers The keys to generalize. 58 | * @returns The generalized data objects. 59 | */ 60 | private generalizeGroup(group: T[], quasiIdentifiers: (keyof T)[]): T[] { 61 | return group.map(item => { 62 | const generalizedItem = { ...item }; 63 | for (const key of quasiIdentifiers) { 64 | if (typeof generalizedItem[key] === 'string' && generalizedItem[key].toString().includes('@')) { 65 | // Example: Generalize email domain. 66 | (generalizedItem[key] as any) = '[generalized-email]'; 67 | } else if (typeof generalizedItem[key] === 'number') { 68 | // Example: Generalize age into ranges. 69 | const age = generalizedItem[key] as number; 70 | (generalizedItem[key] as any) = `${Math.floor(age / 10) * 10}-${Math.floor(age / 10) * 10 + 9}`; 71 | } 72 | } 73 | return generalizedItem; 74 | }); 75 | } 76 | } --------------------------------------------------------------------------------