Add initial project structure and documentation files

- Created .gitignore to exclude sensitive files and directories.
- Added API documentation in API_DOCUMENTATION.md.
- Included deployment instructions in DEPLOYMENT.md.
- Established project structure documentation in PROJECT_STRUCTURE.md.
- Updated README.md with project status and team information.
- Added recommendations and status tracking documents.
- Introduced testing guidelines in TESTING.md.
- Set up CI workflow in .github/workflows/ci.yml.
- Created Dockerfile for backend and frontend setups.
- Added various service and utility files for backend functionality.
- Implemented frontend components and pages for user interface.
- Included mobile app structure and services.
- Established scripts for deployment across multiple chains.
This commit is contained in:
defiQUG
2025-12-03 21:22:31 -08:00
commit 507d9a35b1
261 changed files with 47004 additions and 0 deletions

43
backend/Dockerfile Normal file
View File

@@ -0,0 +1,43 @@
FROM node:20-alpine AS base
# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Generate Prisma Client
RUN npx prisma generate
RUN npm run build
# Production image, copy all the files and run the app
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nodejs
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/prisma ./prisma
COPY --from=builder /app/package.json ./package.json
USER nodejs
EXPOSE 4000
ENV PORT 4000
CMD ["node", "dist/index.js"]

24
backend/jest.config.js Normal file
View File

@@ -0,0 +1,24 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
transform: {
'^.+\\.ts$': 'ts-jest',
},
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/**/*.test.ts',
'!src/**/*.spec.ts',
'!src/index.ts',
],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
setupFilesAfterEnv: ['<rootDir>/src/__tests__/setup.ts'],
testTimeout: 10000,
};

3282
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

67
backend/package.json Normal file
View File

@@ -0,0 +1,67 @@
{
"name": "backend",
"version": "1.0.0",
"description": "ASLE Backend API Server",
"main": "dist/index.js",
"scripts": {
"dev": "nodemon --exec ts-node src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"prisma:generate": "prisma generate",
"prisma:migrate": "prisma migrate dev",
"prisma:studio": "prisma studio",
"prisma:seed": "ts-node prisma/seed.ts",
"setup:admin": "ts-node scripts/setup-admin.ts",
"setup:db": "ts-node scripts/init-db.ts",
"lint": "eslint src --ext .ts",
"lint:fix": "eslint src --ext .ts --fix"
},
"keywords": ["asle", "defi", "liquidity", "api"],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"@apollo/server": "^5.2.0",
"@prisma/client": "^5.20.0",
"apollo-server-express": "^3.13.0",
"axios": "^1.7.9",
"cors": "^2.8.5",
"dotenv": "^17.2.3",
"ethers": "^6.15.0",
"express": "^4.22.1",
"express-rate-limit": "^7.4.1",
"firebase-admin": "^12.0.0",
"graphql": "^16.12.0",
"graphql-tag": "^2.12.6",
"helmet": "^8.0.0",
"ioredis": "^5.4.2",
"jsonwebtoken": "^9.0.2",
"winston": "^3.15.0",
"ws": "^8.18.0",
"zod": "^3.24.1",
"@aws-sdk/client-sns": "^3.700.0",
"apn": "^2.2.0",
"bcryptjs": "^2.4.3"
},
"devDependencies": {
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/jsonwebtoken": "^9.0.7",
"@types/node": "^24.10.1",
"@types/ws": "^8.5.13",
"@types/bcryptjs": "^2.4.6",
"@typescript-eslint/eslint-plugin": "^8.15.0",
"@typescript-eslint/parser": "^8.15.0",
"eslint": "^9.17.0",
"nodemon": "^3.1.11",
"prisma": "^5.20.0",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typescript": "^5.9.3",
"@types/jest": "^29.5.14",
"@types/supertest": "^6.0.2"
}
}

View File

@@ -0,0 +1,525 @@
// Prisma schema for ASLE Backend
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Pool {
id String @id @default(uuid())
poolId BigInt @unique
baseToken String
quoteToken String
baseReserve String @default("0")
quoteReserve String @default("0")
virtualBaseReserve String @default("0")
virtualQuoteReserve String @default("0")
k String @default("0")
oraclePrice String @default("0")
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
transactions Transaction[]
lpPositions LPPosition[]
}
model Vault {
id String @id @default(uuid())
vaultId BigInt @unique
asset String?
isMultiAsset Boolean @default(false)
totalAssets String @default("0")
totalSupply String @default("0")
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deposits Deposit[]
withdrawals Withdrawal[]
}
model Transaction {
id String @id @default(uuid())
txHash String @unique
poolId BigInt
pool Pool @relation(fields: [poolId], references: [poolId])
user String
tokenIn String
tokenOut String
amountIn String
amountOut String
timestamp DateTime @default(now())
blockNumber BigInt?
status String @default("pending")
}
model LPPosition {
id String @id @default(uuid())
poolId BigInt
pool Pool @relation(fields: [poolId], references: [poolId])
user String
lpShares String @default("0")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([poolId, user])
}
model Deposit {
id String @id @default(uuid())
vaultId BigInt
vault Vault @relation(fields: [vaultId], references: [vaultId])
user String
assets String
shares String
txHash String @unique
timestamp DateTime @default(now())
}
model Withdrawal {
id String @id @default(uuid())
vaultId BigInt
vault Vault @relation(fields: [vaultId], references: [vaultId])
user String
assets String
shares String
txHash String @unique
timestamp DateTime @default(now())
}
model ComplianceRecord {
id String @id @default(uuid())
userAddress String
complianceMode String
kycVerified Boolean @default(false)
amlVerified Boolean @default(false)
kycProvider String?
amlProvider String?
lastKYCUpdate DateTime?
lastAMLUpdate DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userAddress])
}
model AuditTrail {
id String @id @default(uuid())
userAddress String
action String
details Json
complianceMode String?
timestamp DateTime @default(now())
txHash String?
}
model CcipMessage {
id String @id @default(uuid())
messageId String @unique
sourceChainId BigInt
targetChainId BigInt
messageType String
payload Json
status String @default("pending")
timestamp DateTime @default(now())
deliveredAt DateTime?
error String?
@@map("ccip_messages")
}
model Proposal {
id String @id @default(uuid())
proposalId BigInt @unique
proposalType String
status String @default("pending")
proposer String
description String @db.Text
data Json
forVotes String @default("0")
againstVotes String @default("0")
startTime DateTime
endTime DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
votes Vote[]
}
model Vote {
id String @id @default(uuid())
proposalId BigInt
proposal Proposal @relation(fields: [proposalId], references: [proposalId])
voter String
support Boolean
votingPower String
timestamp DateTime @default(now())
@@unique([proposalId, voter])
}
model SystemAlert {
id String @id @default(uuid())
alertType String
severity String
message String @db.Text
metadata Json?
resolved Boolean @default(false)
resolvedAt DateTime?
createdAt DateTime @default(now())
}
model Metric {
id String @id @default(uuid())
metricType String
value String
metadata Json?
timestamp DateTime @default(now())
}
model ChainConfig {
id String @id @default(uuid())
chainId BigInt @unique
name String
nativeToken String?
explorerUrl String
gasLimit BigInt @default("3000000")
messageTimeout BigInt @default("300") // seconds
active Boolean @default(true)
ccipSelector BigInt?
rpcUrl String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("chain_configs")
}
model Delegation {
id String @id @default(uuid())
delegator String @unique
delegatee String
votingPower String @default("0")
timestamp DateTime @default(now())
@@map("delegations")
}
model ProposalTemplate {
id String @id @default(uuid())
name String
description String @db.Text
proposalType String
templateData Json
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("proposal_templates")
}
model SARReport {
id String @id @default(uuid())
reportId String @unique
transactionHash String
userAddress String
amount String
reason String @db.Text
status String @default("draft")
submittedAt DateTime?
jurisdiction String @default("US")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([status])
@@index([userAddress])
@@map("sar_reports")
}
model CTRReport {
id String @id @default(uuid())
reportId String @unique
transactionHash String
userAddress String
amount String
currency String
transactionType String
status String @default("draft")
submittedAt DateTime?
jurisdiction String @default("US")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([status])
@@index([userAddress])
@@map("ctr_reports")
}
model ScreeningResult {
id String @id @default(uuid())
address String
riskScore Float
sanctions Boolean @default(false)
passed Boolean @default(true)
providers String[]
action String
timestamp DateTime @default(now())
@@index([address])
@@index([timestamp])
@@map("screening_results")
}
model ComplianceWorkflow {
id String @id @default(uuid())
name String
description String @db.Text
steps Json
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
executions WorkflowExecution[]
@@map("compliance_workflows")
}
model WorkflowExecution {
id String @id @default(uuid())
workflowId String
workflow ComplianceWorkflow @relation(fields: [workflowId], references: [id])
userAddress String
currentStep Int @default(0)
status String @default("pending")
results Json @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([workflowId])
@@index([userAddress])
@@map("workflow_executions")
}
model Comment {
id String @id @default(uuid())
proposalId BigInt
author String
content String @db.Text
parentId String?
upvotes Int @default(0)
downvotes Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
votes CommentVote[]
@@index([proposalId])
@@index([parentId])
@@map("comments")
}
model CommentVote {
id String @id @default(uuid())
commentId String
comment Comment @relation(fields: [commentId], references: [id])
voter String
upvote Boolean
timestamp DateTime @default(now())
@@unique([commentId, voter])
@@map("comment_votes")
}
model DeviceToken {
id String @id @default(uuid())
userAddress String
deviceToken String
platform String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userAddress, deviceToken])
@@index([userAddress])
@@map("device_tokens")
}
model CrossChainMessage {
id String @id @default(uuid())
messageId String @unique
sourceChain String
targetChain String
payload Json
status String @default("pending")
timestamp DateTime @default(now())
receivedAt DateTime?
@@index([sourceChain])
@@index([targetChain])
@@index([status])
@@map("cross_chain_messages")
}
model PoolMetrics {
id String @id @default(uuid())
poolId BigInt
tvl String @default("0")
volume24h String @default("0")
volume7d String @default("0")
volume30d String @default("0")
fees24h String @default("0")
fees7d String @default("0")
fees30d String @default("0")
utilizationRate Float @default(0)
timestamp DateTime @default(now())
@@index([poolId, timestamp])
@@map("pool_metrics")
}
model UserPortfolio {
id String @id @default(uuid())
userAddress String
totalValue String @default("0")
poolPositions Json @default("{}")
vaultPositions Json @default("{}")
timestamp DateTime @default(now())
@@unique([userAddress, timestamp])
@@index([userAddress])
@@map("user_portfolios")
}
model TransactionAnalytics {
id String @id @default(uuid())
poolId BigInt?
transactionType String
volume String @default("0")
count Int @default(0)
averageSize String @default("0")
timestamp DateTime @default(now())
@@index([poolId, timestamp])
@@index([transactionType, timestamp])
@@map("transaction_analytics")
}
model AdminUser {
id String @id @default(uuid())
email String @unique
passwordHash String
role String @default("admin") // admin, super_admin, operator
permissions String[] @default([])
active Boolean @default(true)
lastLogin DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
sessions AdminSession[]
auditLogs AdminAuditLog[]
@@index([email])
@@index([role])
@@map("admin_users")
}
model AdminSession {
id String @id @default(uuid())
adminUserId String
adminUser AdminUser @relation(fields: [adminUserId], references: [id], onDelete: Cascade)
token String @unique
ipAddress String?
userAgent String?
expiresAt DateTime
createdAt DateTime @default(now())
@@index([adminUserId])
@@index([token])
@@map("admin_sessions")
}
model AdminAuditLog {
id String @id @default(uuid())
adminUserId String
adminUser AdminUser @relation(fields: [adminUserId], references: [id])
action String
resource String?
resourceId String?
details Json?
ipAddress String?
timestamp DateTime @default(now())
@@index([adminUserId])
@@index([action])
@@index([timestamp])
@@map("admin_audit_logs")
}
model SystemConfig {
id String @id @default(uuid())
key String @unique
value Json
description String?
category String @default("general")
updatedBy String?
updatedAt DateTime @updatedAt
createdAt DateTime @default(now())
@@index([key])
@@index([category])
@@map("system_configs")
}
model Deployment {
id String @id @default(uuid())
name String
environment String // staging, production
version String
status String @default("pending") // pending, deploying, success, failed
config Json
deployedBy String?
deployedAt DateTime?
rollbackVersion String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
logs DeploymentLog[]
@@index([environment])
@@index([status])
@@map("deployments")
}
model DeploymentLog {
id String @id @default(uuid())
deploymentId String
deployment Deployment @relation(fields: [deploymentId], references: [id], onDelete: Cascade)
level String // info, warning, error
message String @db.Text
metadata Json?
timestamp DateTime @default(now())
@@index([deploymentId])
@@index([timestamp])
@@map("deployment_logs")
}
model WhiteLabelConfig {
id String @id @default(uuid())
name String @unique
domain String @unique
logoUrl String?
primaryColor String?
secondaryColor String?
theme Json @default("{}")
features String[] @default([])
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([domain])
@@map("white_label_configs")
}

View File

@@ -0,0 +1,67 @@
/**
* Initialize database with default configurations
* Run with: npx ts-node scripts/init-db.ts
*/
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function initDatabase() {
try {
console.log('=== Initializing Database ===\n');
// Create default system configs
const defaultConfigs = [
{
key: 'push_notification_provider',
value: 'firebase',
description: 'Default push notification provider',
category: 'notifications',
},
{
key: 'max_deployment_retries',
value: 3,
description: 'Maximum number of deployment retries',
category: 'deployment',
},
{
key: 'deployment_timeout',
value: 300000, // 5 minutes
description: 'Deployment timeout in milliseconds',
category: 'deployment',
},
{
key: 'audit_log_retention_days',
value: 90,
description: 'Number of days to retain audit logs',
category: 'logging',
},
{
key: 'rate_limit_requests_per_minute',
value: 100,
description: 'Default rate limit for API requests',
category: 'security',
},
];
for (const config of defaultConfigs) {
await prisma.systemConfig.upsert({
where: { key: config.key },
update: {},
create: config,
});
console.log(`✅ Created config: ${config.key}`);
}
console.log('\n✅ Database initialization complete!');
} catch (error: any) {
console.error('\n❌ Error initializing database:', error.message);
process.exit(1);
} finally {
await prisma.$disconnect();
}
}
initDatabase();

View File

@@ -0,0 +1,69 @@
/**
* Setup script to create initial admin user
* Run with: npx ts-node scripts/setup-admin.ts
*/
import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcryptjs';
import readline from 'readline';
const prisma = new PrismaClient();
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
function question(query: string): Promise<string> {
return new Promise((resolve) => {
rl.question(query, resolve);
});
}
async function setupAdmin() {
try {
console.log('=== ASLE Admin Setup ===\n');
const email = await question('Admin email: ');
const password = await question('Admin password: ');
const role = await question('Role (admin/super_admin) [admin]: ') || 'admin';
// Check if admin already exists
const existing = await prisma.adminUser.findUnique({
where: { email },
});
if (existing) {
console.log('\n❌ Admin user already exists!');
process.exit(1);
}
// Hash password
const passwordHash = await bcrypt.hash(password, 10);
// Create admin user
const admin = await prisma.adminUser.create({
data: {
email,
passwordHash,
role: role as 'admin' | 'super_admin',
permissions: [],
active: true,
},
});
console.log('\n✅ Admin user created successfully!');
console.log(` ID: ${admin.id}`);
console.log(` Email: ${admin.email}`);
console.log(` Role: ${admin.role}`);
} catch (error: any) {
console.error('\n❌ Error creating admin user:', error.message);
process.exit(1);
} finally {
rl.close();
await prisma.$disconnect();
}
}
setupAdmin();

View File

@@ -0,0 +1,62 @@
import request from 'supertest';
import express from 'express';
import adminRouter from '../../api/admin';
import { AdminService } from '../../services/admin';
// Mock services
jest.mock('../../services/admin');
jest.mock('../../services/system-config');
jest.mock('../../services/deployment');
jest.mock('../../services/white-label');
const app = express();
app.use(express.json());
app.use('/api/admin', adminRouter);
describe('Admin API', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('POST /api/admin/auth/login', () => {
it('should login successfully', async () => {
const mockAdminService = AdminService as jest.MockedClass<typeof AdminService>;
const mockInstance = {
login: jest.fn().mockResolvedValue({
user: { id: '1', email: 'admin@test.com', role: 'admin', permissions: [], active: true },
token: 'mock-token',
}),
};
mockAdminService.mockImplementation(() => mockInstance as any);
const response = await request(app)
.post('/api/admin/auth/login')
.send({
email: 'admin@test.com',
password: 'password123',
});
expect(response.status).toBe(200);
expect(response.body).toHaveProperty('token');
expect(response.body).toHaveProperty('user');
});
it('should return 401 for invalid credentials', async () => {
const mockAdminService = AdminService as jest.MockedClass<typeof AdminService>;
const mockInstance = {
login: jest.fn().mockRejectedValue(new Error('Invalid credentials')),
};
mockAdminService.mockImplementation(() => mockInstance as any);
const response = await request(app)
.post('/api/admin/auth/login')
.send({
email: 'admin@test.com',
password: 'wrongpassword',
});
expect(response.status).toBe(401);
});
});
});

View File

@@ -0,0 +1,120 @@
import { AdminService } from '../../services/admin';
import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcryptjs';
// Mock Prisma
jest.mock('@prisma/client', () => {
const mockPrisma = {
adminUser: {
findUnique: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
findMany: jest.fn(),
},
adminSession: {
create: jest.fn(),
findUnique: jest.fn(),
deleteMany: jest.fn(),
},
adminAuditLog: {
create: jest.fn(),
findMany: jest.fn(),
},
};
return {
PrismaClient: jest.fn(() => mockPrisma),
};
});
describe('AdminService', () => {
let adminService: AdminService;
let mockPrisma: any;
beforeEach(() => {
adminService = new AdminService();
mockPrisma = new PrismaClient();
jest.clearAllMocks();
});
describe('login', () => {
it('should login successfully with valid credentials', async () => {
const hashedPassword = await bcrypt.hash('password123', 10);
mockPrisma.adminUser.findUnique.mockResolvedValue({
id: '1',
email: 'admin@test.com',
passwordHash: hashedPassword,
role: 'admin',
permissions: [],
active: true,
});
mockPrisma.adminUser.update.mockResolvedValue({});
mockPrisma.adminSession.create.mockResolvedValue({});
mockPrisma.adminAuditLog.create.mockResolvedValue({});
const result = await adminService.login({
email: 'admin@test.com',
password: 'password123',
});
expect(result.user.email).toBe('admin@test.com');
expect(result.token).toBeDefined();
});
it('should throw error with invalid credentials', async () => {
mockPrisma.adminUser.findUnique.mockResolvedValue(null);
await expect(
adminService.login({
email: 'admin@test.com',
password: 'wrongpassword',
})
).rejects.toThrow('Invalid credentials');
});
});
describe('createAdmin', () => {
it('should create admin user successfully', async () => {
mockPrisma.adminUser.findUnique.mockResolvedValue(null);
mockPrisma.adminUser.create.mockResolvedValue({
id: '1',
email: 'newadmin@test.com',
role: 'admin',
permissions: [],
active: true,
});
mockPrisma.adminAuditLog.create.mockResolvedValue({});
const result = await adminService.createAdmin(
{
email: 'newadmin@test.com',
password: 'password123',
role: 'admin',
},
'creator-id'
);
expect(result.email).toBe('newadmin@test.com');
expect(mockPrisma.adminUser.create).toHaveBeenCalled();
});
it('should throw error if admin already exists', async () => {
mockPrisma.adminUser.findUnique.mockResolvedValue({
id: '1',
email: 'existing@test.com',
});
await expect(
adminService.createAdmin(
{
email: 'existing@test.com',
password: 'password123',
},
'creator-id'
)
).rejects.toThrow('Admin user already exists');
});
});
});

View File

@@ -0,0 +1,17 @@
/**
* Jest setup file
*/
// Mock environment variables
process.env.JWT_SECRET = 'test-secret-key';
process.env.DATABASE_URL = 'postgresql://test:test@localhost:5432/test';
process.env.NODE_ENV = 'test';
// Increase timeout for async operations
jest.setTimeout(10000);
// Clean up after tests
afterAll(async () => {
// Add cleanup logic here if needed
});

278
backend/src/api/admin.ts Normal file
View File

@@ -0,0 +1,278 @@
import { Router } from 'express';
import { AdminService } from '../services/admin';
import { SystemConfigService } from '../services/system-config';
import { DeploymentService } from '../services/deployment';
import { WhiteLabelService } from '../services/white-label';
import { PushProviderFactory } from '../services/push-providers/factory';
const router = Router();
const adminService = new AdminService();
const systemConfigService = new SystemConfigService();
const deploymentService = new DeploymentService();
const whiteLabelService = new WhiteLabelService();
/**
* Middleware to verify admin token
*/
async function verifyAdmin(req: any, res: any, next: any) {
try {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
const admin = await adminService.verifyToken(token);
req.admin = admin;
next();
} catch (error: any) {
return res.status(401).json({ error: error.message });
}
}
// Auth routes
router.post('/auth/login', async (req, res) => {
try {
const { email, password } = req.body;
const ipAddress = req.ip || req.headers['x-forwarded-for'] as string;
const userAgent = req.headers['user-agent'];
const result = await adminService.login(
{ email, password },
ipAddress,
userAgent
);
res.json(result);
} catch (error: any) {
res.status(401).json({ error: error.message });
}
});
router.post('/auth/logout', verifyAdmin, async (req, res) => {
try {
const token = req.headers.authorization?.replace('Bearer ', '');
await adminService.logout(token!);
res.json({ success: true });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
// Admin user management
router.get('/users', verifyAdmin, async (req, res) => {
try {
const admins = await adminService.getAdmins();
res.json(admins);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
router.post('/users', verifyAdmin, async (req, res) => {
try {
const admin = await adminService.createAdmin(req.body, req.admin.id);
res.json(admin);
} catch (error: any) {
res.status(400).json({ error: error.message });
}
});
router.put('/users/:id', verifyAdmin, async (req, res) => {
try {
const admin = await adminService.updateAdmin(req.params.id, req.body, req.admin.id);
res.json(admin);
} catch (error: any) {
res.status(400).json({ error: error.message });
}
});
router.delete('/users/:id', verifyAdmin, async (req, res) => {
try {
await adminService.deleteAdmin(req.params.id, req.admin.id);
res.json({ success: true });
} catch (error: any) {
res.status(400).json({ error: error.message });
}
});
// Audit logs
router.get('/audit-logs', verifyAdmin, async (req, res) => {
try {
const logs = await adminService.getAuditLogs({
adminUserId: req.query.adminUserId as string,
action: req.query.action as string,
startDate: req.query.startDate ? new Date(req.query.startDate as string) : undefined,
endDate: req.query.endDate ? new Date(req.query.endDate as string) : undefined,
limit: req.query.limit ? parseInt(req.query.limit as string) : undefined,
});
res.json(logs);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
// System config
router.get('/config', verifyAdmin, async (req, res) => {
try {
const configs = await systemConfigService.getAllConfigs();
res.json(configs);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
router.get('/config/:key', verifyAdmin, async (req, res) => {
try {
const value = await systemConfigService.getConfig(req.params.key);
res.json({ key: req.params.key, value });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
router.post('/config', verifyAdmin, async (req, res) => {
try {
await systemConfigService.setConfig(req.body, req.admin.id);
res.json({ success: true });
} catch (error: any) {
res.status(400).json({ error: error.message });
}
});
router.delete('/config/:key', verifyAdmin, async (req, res) => {
try {
await systemConfigService.deleteConfig(req.params.key);
res.json({ success: true });
} catch (error: any) {
res.status(400).json({ error: error.message });
}
});
// Deployments
router.get('/deployments', verifyAdmin, async (req, res) => {
try {
const deployments = await deploymentService.getDeployments(
req.query.environment as string
);
res.json(deployments);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
router.get('/deployments/:id', verifyAdmin, async (req, res) => {
try {
const deployment = await deploymentService.getDeployment(req.params.id);
res.json(deployment);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
router.post('/deployments', verifyAdmin, async (req, res) => {
try {
const deployment = await deploymentService.createDeployment(
req.body,
req.admin.id
);
res.json(deployment);
} catch (error: any) {
res.status(400).json({ error: error.message });
}
});
router.post('/deployments/:id/status', verifyAdmin, async (req, res) => {
try {
await deploymentService.updateDeploymentStatus(
req.params.id,
req.body.status,
req.body.deployedAt ? new Date(req.body.deployedAt) : undefined
);
res.json({ success: true });
} catch (error: any) {
res.status(400).json({ error: error.message });
}
});
router.post('/deployments/:id/logs', verifyAdmin, async (req, res) => {
try {
await deploymentService.addLog({
deploymentId: req.params.id,
...req.body,
});
res.json({ success: true });
} catch (error: any) {
res.status(400).json({ error: error.message });
}
});
router.post('/deployments/:id/rollback', verifyAdmin, async (req, res) => {
try {
await deploymentService.rollbackDeployment(
req.params.id,
req.body.version
);
res.json({ success: true });
} catch (error: any) {
res.status(400).json({ error: error.message });
}
});
// White-label configs
router.get('/white-label', verifyAdmin, async (req, res) => {
try {
const configs = await whiteLabelService.getAllConfigs();
res.json(configs);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
router.post('/white-label', verifyAdmin, async (req, res) => {
try {
const config = await whiteLabelService.createConfig(req.body);
res.json(config);
} catch (error: any) {
res.status(400).json({ error: error.message });
}
});
router.put('/white-label/:id', verifyAdmin, async (req, res) => {
try {
const config = await whiteLabelService.updateConfig(req.params.id, req.body);
res.json(config);
} catch (error: any) {
res.status(400).json({ error: error.message });
}
});
router.delete('/white-label/:id', verifyAdmin, async (req, res) => {
try {
await whiteLabelService.deleteConfig(req.params.id);
res.json({ success: true });
} catch (error: any) {
res.status(400).json({ error: error.message });
}
});
router.post('/white-label/:id/toggle', verifyAdmin, async (req, res) => {
try {
const config = await whiteLabelService.toggleActive(req.params.id);
res.json(config);
} catch (error: any) {
res.status(400).json({ error: error.message });
}
});
// Push notification providers
router.get('/push-providers', verifyAdmin, async (req, res) => {
try {
const providers = PushProviderFactory.getAvailableProviders();
res.json(providers);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
export default router;

View File

@@ -0,0 +1,112 @@
import express from 'express';
import { AnalyticsService } from '../services/analytics';
import { chartDataProcessor } from '../utils/chart-data-processor';
const router = express.Router();
const analyticsService = new AnalyticsService();
/**
* GET /api/analytics/pools
* Get pool analytics
*/
router.get('/pools', async (req, res) => {
try {
const poolId = req.query.poolId ? BigInt(req.query.poolId as string) : undefined;
const startDate = req.query.startDate ? new Date(req.query.startDate as string) : undefined;
const endDate = req.query.endDate ? new Date(req.query.endDate as string) : undefined;
if (!poolId) {
return res.status(400).json({ error: 'poolId is required' });
}
const analytics = await analyticsService.getPoolAnalytics(poolId, startDate, endDate);
res.json(analytics);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/analytics/portfolio/:address
* Get user portfolio analytics
*/
router.get('/portfolio/:address', async (req, res) => {
try {
const { address } = req.params;
const startDate = req.query.startDate ? new Date(req.query.startDate as string) : undefined;
const endDate = req.query.endDate ? new Date(req.query.endDate as string) : undefined;
if (startDate || endDate) {
const history = await analyticsService.getUserPortfolioHistory(address, startDate, endDate);
res.json(history);
} else {
const portfolio = await analyticsService.calculateUserPortfolio(address);
res.json(portfolio);
}
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/analytics/metrics
* Get system-wide metrics
*/
router.get('/metrics', async (req, res) => {
try {
const metrics = await analyticsService.calculateSystemMetrics();
res.json(metrics);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/analytics/historical
* Get historical analytics data
*/
router.get('/historical', async (req, res) => {
try {
const poolId = req.query.poolId ? BigInt(req.query.poolId as string) : undefined;
const startDate = req.query.startDate ? new Date(req.query.startDate as string) : undefined;
const endDate = req.query.endDate ? new Date(req.query.endDate as string) : undefined;
const period = (req.query.period as 'hour' | 'day' | 'week' | 'month') || 'day';
if (!poolId) {
return res.status(400).json({ error: 'poolId is required' });
}
const analytics = await analyticsService.getPoolAnalytics(poolId, startDate, endDate);
// Process for chart data
const chartData = analytics.map((a) => ({
timestamp: a.timestamp,
value: parseFloat(a.tvl),
}));
const processed = chartDataProcessor.aggregateByPeriod(chartData, period);
res.json(processed);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/analytics/transactions
* Get transaction analytics
*/
router.get('/transactions', async (req, res) => {
try {
const poolId = req.query.poolId ? BigInt(req.query.poolId as string) : undefined;
const startDate = req.query.startDate ? new Date(req.query.startDate as string) : undefined;
const endDate = req.query.endDate ? new Date(req.query.endDate as string) : undefined;
const analytics = await analyticsService.getTransactionAnalytics(poolId, startDate, endDate);
res.json(analytics);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
export { router as analyticsRouter };

109
backend/src/api/bank.ts Normal file
View File

@@ -0,0 +1,109 @@
import { Router } from 'express';
import { BankService } from '../services/bank';
import { authenticateToken, AuthRequest } from '../middleware/auth';
import { strictRateLimiter } from '../middleware/rateLimit';
import { validate } from '../middleware/validation';
import { z } from 'zod';
const router = Router();
const bankService = new BankService();
const swiftMessageSchema = z.object({
body: z.object({
messageType: z.string(),
senderBIC: z.string(),
receiverBIC: z.string(),
amount: z.string(),
currency: z.string(),
reference: z.string(),
details: z.any().optional()
})
});
const iso20022MessageSchema = z.object({
body: z.object({
messageType: z.string(),
sender: z.string(),
receiver: z.string(),
document: z.any()
})
});
const paymentSchema = z.object({
body: z.object({
amount: z.string(),
currency: z.string(),
recipientBIC: z.string(),
details: z.any().optional()
})
});
router.use(authenticateToken);
router.use(strictRateLimiter);
router.post('/swift/send', validate(swiftMessageSchema), async (req: AuthRequest, res) => {
try {
const message = req.body;
const messageRef = await bankService.sendSWIFTMessage(message);
res.json({ success: true, messageRef });
} catch (error: any) {
console.error('Error sending SWIFT message:', error);
res.status(500).json({ error: error.message || 'Failed to send SWIFT message' });
}
});
router.post('/iso20022/send', validate(iso20022MessageSchema), async (req: AuthRequest, res) => {
try {
const message = req.body;
const messageId = await bankService.sendISO20022Message(message);
res.json({ success: true, messageId });
} catch (error: any) {
console.error('Error sending ISO 20022 message:', error);
res.status(500).json({ error: error.message || 'Failed to send ISO 20022 message' });
}
});
router.post('/convert/swift', validate(iso20022MessageSchema), async (req: AuthRequest, res) => {
try {
const iso20022Message = req.body;
const swiftMessage = await bankService.convertToSWIFT(iso20022Message);
res.json({ success: true, swiftMessage });
} catch (error: any) {
console.error('Error converting to SWIFT:', error);
res.status(500).json({ error: error.message || 'Failed to convert message' });
}
});
router.post('/payment', validate(paymentSchema), async (req: AuthRequest, res) => {
try {
const { amount, currency, recipientBIC, details } = req.body;
const messageId = await bankService.processPayment(amount, currency, recipientBIC, details || {});
res.json({ success: true, messageId });
} catch (error: any) {
console.error('Error processing payment:', error);
res.status(500).json({ error: error.message || 'Failed to process payment' });
}
});
router.get('/statements/:accountId', async (req: AuthRequest, res) => {
try {
const { accountId } = req.params;
const { dateFrom, dateTo } = req.query;
if (!dateFrom || !dateTo) {
return res.status(400).json({ error: 'dateFrom and dateTo are required' });
}
const statement = await bankService.getBankStatement(
accountId,
dateFrom as string,
dateTo as string
);
res.json({ success: true, statement });
} catch (error: any) {
console.error('Error fetching bank statement:', error);
res.status(500).json({ error: error.message || 'Failed to fetch statement' });
}
});
export { router as bankRouter };

110
backend/src/api/ccip.ts Normal file
View File

@@ -0,0 +1,110 @@
import { Router } from 'express';
import { ethers } from 'ethers';
import { CCIPService } from '../services/ccip';
import { authenticateToken, optionalAuth, AuthRequest } from '../middleware/auth';
import { apiRateLimiter, strictRateLimiter } from '../middleware/rateLimit';
import { validate, schemas } from '../middleware/validation';
import { z } from 'zod';
const router = Router();
function initProvider() {
const rpcUrl = process.env.RPC_URL || 'http://localhost:8545';
return new ethers.JsonRpcProvider(rpcUrl);
}
const ccipService = new CCIPService(
initProvider(),
process.env.DIAMOND_ADDRESS || ''
);
const trackMessageSchema = z.object({
body: z.object({
messageId: z.string(),
sourceChainId: z.number(),
targetChainId: z.number(),
messageType: z.string(),
payload: z.any()
})
});
router.use(apiRateLimiter);
router.get('/messages', optionalAuth, async (req, res) => {
try {
const messages = await ccipService.getAllMessages();
res.json({ success: true, messages });
} catch (error: any) {
console.error('Error fetching CCIP messages:', error);
res.status(500).json({ error: error.message || 'Failed to fetch messages' });
}
});
router.get('/messages/:messageId', optionalAuth, async (req, res) => {
try {
const { messageId } = req.params;
const message = await ccipService.getMessage(messageId);
if (!message) {
return res.status(404).json({ error: 'Message not found' });
}
res.json({ success: true, message });
} catch (error: any) {
console.error('Error fetching CCIP message:', error);
res.status(500).json({ error: error.message || 'Failed to fetch message' });
}
});
router.get('/chains/:chainId', optionalAuth, async (req, res) => {
try {
const chainId = parseInt(req.params.chainId);
if (isNaN(chainId)) {
return res.status(400).json({ error: 'Invalid chain ID' });
}
const messages = await ccipService.getMessagesByChain(chainId);
res.json({ success: true, messages });
} catch (error: any) {
console.error('Error fetching chain messages:', error);
res.status(500).json({ error: error.message || 'Failed to fetch messages' });
}
});
router.post('/track', authenticateToken, strictRateLimiter, validate(trackMessageSchema), async (req: AuthRequest, res) => {
try {
const { messageId, sourceChainId, targetChainId, messageType, payload } = req.body;
await ccipService.trackMessage(messageId, sourceChainId, targetChainId, messageType, payload);
res.json({ success: true, messageId });
} catch (error: any) {
console.error('Error tracking CCIP message:', error);
res.status(500).json({ error: error.message || 'Failed to track message' });
}
});
router.post('/sync/liquidity', authenticateToken, async (req: AuthRequest, res) => {
try {
const { poolId, chainId } = req.body;
if (!poolId || !chainId) {
return res.status(400).json({ error: 'poolId and chainId are required' });
}
await ccipService.syncLiquidityState(poolId, chainId);
res.json({ success: true });
} catch (error: any) {
console.error('Error syncing liquidity:', error);
res.status(500).json({ error: error.message || 'Failed to sync liquidity' });
}
});
router.post('/sync/vault', authenticateToken, async (req: AuthRequest, res) => {
try {
const { vaultId, chainId } = req.body;
if (!vaultId || !chainId) {
return res.status(400).json({ error: 'vaultId and chainId are required' });
}
await ccipService.syncVaultBalance(vaultId, chainId);
res.json({ success: true });
} catch (error: any) {
console.error('Error syncing vault:', error);
res.status(500).json({ error: error.message || 'Failed to sync vault' });
}
});
export { router as ccipRouter };

View File

@@ -0,0 +1,209 @@
import express from 'express';
import { RealTimeScreeningService } from '../services/real-time-screening';
import { ComplianceWorkflowService } from '../services/compliance-workflow';
import { ComplianceAnalyticsService } from '../services/compliance-analytics';
import { ComplianceService } from '../services/compliance';
import { SARGenerator } from '../services/sar-generator';
import { CTRGenerator } from '../services/ctr-generator';
import { RegulatoryReportingService } from '../services/regulatory-reporting';
import { ethers } from 'ethers';
const router = express.Router();
// Initialize services
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL || 'http://localhost:8545');
const diamondAddress = process.env.DIAMOND_ADDRESS || '';
const complianceService = new ComplianceService(provider, diamondAddress);
const reportingService = new RegulatoryReportingService(complianceService);
const sarGenerator = new SARGenerator(reportingService);
const ctrGenerator = new CTRGenerator(reportingService);
const screeningService = new RealTimeScreeningService(complianceService, sarGenerator, ctrGenerator);
const workflowService = new ComplianceWorkflowService(complianceService);
const analyticsService = new ComplianceAnalyticsService();
/**
* POST /api/compliance/screening/screen
* Screen an address
*/
router.post('/screening/screen', async (req, res) => {
try {
const { address } = req.body;
if (!address) {
return res.status(400).json({ error: 'Address is required' });
}
const result = await screeningService.screenAddress(address);
res.json(result);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* POST /api/compliance/screening/transaction
* Screen a transaction
*/
router.post('/screening/transaction', async (req, res) => {
try {
const { transactionHash, fromAddress, toAddress, amount, currency } = req.body;
if (!transactionHash || !fromAddress || !toAddress || !amount) {
return res.status(400).json({ error: 'Missing required fields' });
}
const result = await screeningService.screenTransaction(
transactionHash,
fromAddress,
toAddress,
amount,
currency || 'ETH'
);
res.json(result);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/compliance/screening/recent
* Get recent screening results
*/
router.get('/screening/recent', async (req, res) => {
try {
const limit = parseInt(req.query.limit as string) || 100;
const address = req.query.address as string | undefined;
if (address) {
const results = await screeningService.getScreeningHistory(address, limit);
res.json(results);
} else {
// Get all recent results
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
const results = await prisma.screeningResult.findMany({
orderBy: { timestamp: 'desc' },
take: limit,
});
res.json(results);
}
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* POST /api/compliance/workflows
* Create workflow
*/
router.post('/workflows', async (req, res) => {
try {
const { name, description, steps } = req.body;
if (!name || !steps || !Array.isArray(steps)) {
return res.status(400).json({ error: 'Missing required fields' });
}
const workflow = await workflowService.createWorkflow(name, description, steps);
res.json(workflow);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/compliance/workflows
* Get all workflows
*/
router.get('/workflows', async (req, res) => {
try {
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
const workflows = await prisma.complianceWorkflow.findMany({
orderBy: { createdAt: 'desc' },
});
res.json(workflows);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* POST /api/compliance/workflows/:id/start
* Start workflow execution
*/
router.post('/workflows/:id/start', async (req, res) => {
try {
const { userAddress } = req.body;
if (!userAddress) {
return res.status(400).json({ error: 'User address is required' });
}
const execution = await workflowService.startWorkflow(req.params.id, userAddress);
res.json(execution);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/compliance/workflows/executions
* Get workflow executions
*/
router.get('/workflows/executions', async (req, res) => {
try {
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
const executions = await prisma.workflowExecution.findMany({
orderBy: { createdAt: 'desc' },
take: 100,
});
res.json(executions);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/compliance/analytics/metrics
* Get compliance metrics
*/
router.get('/analytics/metrics', async (req, res) => {
try {
const metrics = await analyticsService.calculateMetrics();
res.json(metrics);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/compliance/analytics/trends
* Get compliance trends
*/
router.get('/analytics/trends', async (req, res) => {
try {
const startDate = req.query.startDate ? new Date(req.query.startDate as string) : new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const endDate = req.query.endDate ? new Date(req.query.endDate as string) : new Date();
const trends = await analyticsService.getTrends(startDate, endDate);
res.json(trends);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/compliance/analytics/providers
* Get provider performance
*/
router.get('/analytics/providers', async (req, res) => {
try {
const performance = await analyticsService.getProviderPerformance();
res.json(performance);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
export { router as complianceAdvancedRouter };

View File

@@ -0,0 +1,128 @@
import express from 'express';
import { RegulatoryReportingService } from '../services/regulatory-reporting';
import { SARGenerator } from '../services/sar-generator';
import { CTRGenerator } from '../services/ctr-generator';
import { ReportSubmissionService } from '../services/report-submission';
import { ComplianceService } from '../services/compliance';
import { ethers } from 'ethers';
const router = express.Router();
// Initialize services
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL || 'http://localhost:8545');
const diamondAddress = process.env.DIAMOND_ADDRESS || '';
const complianceService = new ComplianceService(provider, diamondAddress);
const reportingService = new RegulatoryReportingService(complianceService);
const sarGenerator = new SARGenerator(reportingService);
const ctrGenerator = new CTRGenerator(reportingService);
const submissionService = new ReportSubmissionService(sarGenerator, ctrGenerator);
/**
* GET /api/compliance/reports/sar
* Get all SAR reports
*/
router.get('/sar', async (req, res) => {
try {
const status = req.query.status as string | undefined;
const sars = await reportingService.getAllSARs(status);
res.json(sars);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* POST /api/compliance/reports/sar
* Generate new SAR
*/
router.post('/sar', async (req, res) => {
try {
const { transactionHash, userAddress, amount, reason, jurisdiction } = req.body;
if (!transactionHash || !userAddress || !amount || !reason) {
return res.status(400).json({ error: 'Missing required fields' });
}
const sar = await reportingService.generateSAR(
transactionHash,
userAddress,
amount,
reason,
jurisdiction || 'US'
);
res.json(sar);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* POST /api/compliance/reports/sar/:id/submit
* Submit SAR
*/
router.post('/sar/:id/submit', async (req, res) => {
try {
const result = await submissionService.submitSAR(req.params.id);
res.json(result);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/compliance/reports/ctr
* Get all CTR reports
*/
router.get('/ctr', async (req, res) => {
try {
const status = req.query.status as string | undefined;
const ctrs = await reportingService.getAllCTRs(status);
res.json(ctrs);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* POST /api/compliance/reports/ctr
* Generate new CTR
*/
router.post('/ctr', async (req, res) => {
try {
const { transactionHash, userAddress, amount, currency, transactionType, jurisdiction } = req.body;
if (!transactionHash || !userAddress || !amount || !currency || !transactionType) {
return res.status(400).json({ error: 'Missing required fields' });
}
const ctr = await reportingService.generateCTR(
transactionHash,
userAddress,
amount,
currency,
transactionType,
jurisdiction || 'US'
);
res.json(ctr);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* POST /api/compliance/reports/ctr/:id/submit
* Submit CTR
*/
router.post('/ctr/:id/submit', async (req, res) => {
try {
const result = await submissionService.submitCTR(req.params.id);
res.json(result);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
export { router as complianceReportsRouter };

View File

@@ -0,0 +1,147 @@
import { Router } from 'express';
import { ethers } from 'ethers';
import { ComplianceService } from '../services/compliance';
import { authenticateToken, optionalAuth, AuthRequest } from '../middleware/auth';
import { strictRateLimiter, apiRateLimiter } from '../middleware/rateLimit';
import { validate, schemas } from '../middleware/validation';
import { z } from 'zod';
const router = Router();
function initProvider() {
const rpcUrl = process.env.RPC_URL || 'http://localhost:8545';
return new ethers.JsonRpcProvider(rpcUrl);
}
const complianceService = new ComplianceService(
initProvider(),
process.env.DIAMOND_ADDRESS || ''
);
const verifyKYCSchema = z.object({
body: z.object({
userAddress: schemas.address,
provider: z.string().optional()
})
});
const verifyAMLSchema = z.object({
body: z.object({
userAddress: schemas.address,
provider: z.string().optional()
})
});
const checkOFACSchema = z.object({
body: z.object({
userAddress: schemas.address
})
});
const travelRuleSchema = z.object({
body: z.object({
from: schemas.address,
to: schemas.address,
amount: z.string(),
asset: z.string()
})
});
const iso20022Schema = z.object({
body: z.object({
messageType: z.string(),
data: z.any()
})
});
const auditSchema = z.object({
body: z.object({
userAddress: schemas.address,
action: z.string(),
details: z.any()
})
});
router.use(apiRateLimiter);
router.post('/kyc/verify', authenticateToken, strictRateLimiter, validate(verifyKYCSchema), async (req: AuthRequest, res) => {
try {
const { userAddress, provider } = req.body;
const result = await complianceService.verifyKYC(userAddress, provider);
res.json({ success: true, result });
} catch (error: any) {
console.error('Error verifying KYC:', error);
res.status(500).json({ error: error.message || 'Failed to verify KYC' });
}
});
router.post('/aml/verify', authenticateToken, strictRateLimiter, validate(verifyAMLSchema), async (req: AuthRequest, res) => {
try {
const { userAddress, provider } = req.body;
const result = await complianceService.verifyAML(userAddress, provider);
res.json({ success: true, result });
} catch (error: any) {
console.error('Error verifying AML:', error);
res.status(500).json({ error: error.message || 'Failed to verify AML' });
}
});
router.post('/ofac/check', authenticateToken, validate(checkOFACSchema), async (req: AuthRequest, res) => {
try {
const { userAddress } = req.body;
const result = await complianceService.checkOFACSanctions(userAddress);
res.json({ success: true, result });
} catch (error: any) {
console.error('Error checking OFAC:', error);
res.status(500).json({ error: error.message || 'Failed to check OFAC' });
}
});
router.get('/record/:userAddress', optionalAuth, async (req, res) => {
try {
const { userAddress } = req.params;
if (!/^0x[a-fA-F0-9]{40}$/.test(userAddress)) {
return res.status(400).json({ error: 'Invalid address format' });
}
const record = await complianceService.getComplianceRecord(userAddress);
res.json({ success: true, record });
} catch (error: any) {
console.error('Error fetching compliance record:', error);
res.status(500).json({ error: error.message || 'Failed to fetch compliance record' });
}
});
router.post('/travel-rule/generate', authenticateToken, validate(travelRuleSchema), async (req: AuthRequest, res) => {
try {
const { from, to, amount, asset } = req.body;
const message = await complianceService.generateTravelRuleMessage(from, to, amount, asset);
res.json({ success: true, message });
} catch (error: any) {
console.error('Error generating travel rule message:', error);
res.status(500).json({ error: error.message || 'Failed to generate travel rule message' });
}
});
router.post('/iso20022/generate', authenticateToken, validate(iso20022Schema), async (req: AuthRequest, res) => {
try {
const { messageType, data } = req.body;
const message = await complianceService.generateISO20022Message(messageType, data);
res.json({ success: true, message });
} catch (error: any) {
console.error('Error generating ISO 20022 message:', error);
res.status(500).json({ error: error.message || 'Failed to generate ISO 20022 message' });
}
});
router.post('/audit', authenticateToken, validate(auditSchema), async (req: AuthRequest, res) => {
try {
const { userAddress, action, details } = req.body;
await complianceService.recordAuditTrail(userAddress, action, details);
res.json({ success: true });
} catch (error: any) {
console.error('Error recording audit trail:', error);
res.status(500).json({ error: error.message || 'Failed to record audit trail' });
}
});
export { router as complianceRouter };

View File

@@ -0,0 +1,92 @@
import { Router } from 'express';
import { CustodialService } from '../services/custodial';
import { authenticateToken, AuthRequest } from '../middleware/auth';
import { strictRateLimiter } from '../middleware/rateLimit';
import { validate, schemas } from '../middleware/validation';
import { z } from 'zod';
const router = Router();
const custodialService = new CustodialService();
const createWalletSchema = z.object({
body: z.object({
provider: z.enum(['fireblocks', 'coinbase', 'bitgo']),
type: z.enum(['hot', 'warm', 'cold'])
})
});
const transferSchema = z.object({
body: z.object({
to: schemas.address,
amount: z.string().regex(/^\d+$/, 'Invalid amount'),
asset: z.string()
})
});
router.use(authenticateToken); // All routes require authentication
router.use(strictRateLimiter); // Strict rate limiting for custodial operations
router.post('/wallets', validate(createWalletSchema), async (req: AuthRequest, res) => {
try {
const { provider, type } = req.body;
const wallet = await custodialService.createCustodialWallet(provider, type);
res.json({ success: true, wallet });
} catch (error: any) {
console.error('Error creating custodial wallet:', error);
res.status(500).json({ error: error.message || 'Failed to create wallet' });
}
});
router.get('/wallets', async (req: AuthRequest, res) => {
try {
const wallets = await custodialService.getAllWallets();
res.json({ success: true, wallets });
} catch (error: any) {
console.error('Error fetching custodial wallets:', error);
res.status(500).json({ error: error.message || 'Failed to fetch wallets' });
}
});
router.get('/wallets/:walletId', async (req: AuthRequest, res) => {
try {
const { walletId } = req.params;
const wallet = await custodialService.getCustodialWallet(walletId);
if (!wallet) {
return res.status(404).json({ error: 'Wallet not found' });
}
res.json({ success: true, wallet });
} catch (error: any) {
console.error('Error fetching custodial wallet:', error);
res.status(500).json({ error: error.message || 'Failed to fetch wallet' });
}
});
router.post('/wallets/:walletId/transfer', validate(transferSchema), async (req: AuthRequest, res) => {
try {
const { walletId } = req.params;
const { to, amount, asset } = req.body;
if (!walletId) {
return res.status(400).json({ error: 'walletId is required' });
}
const txId = await custodialService.initiateTransfer(walletId, to, amount, asset);
res.json({ success: true, transactionId: txId });
} catch (error: any) {
console.error('Error initiating transfer:', error);
res.status(500).json({ error: error.message || 'Failed to initiate transfer' });
}
});
router.get('/wallets/:walletId/mpc', async (req: AuthRequest, res) => {
try {
const { walletId } = req.params;
const mpcInfo = await custodialService.getMPCKeyShares(walletId);
res.json({ success: true, mpcInfo });
} catch (error: any) {
console.error('Error fetching MPC info:', error);
res.status(500).json({ error: error.message || 'Failed to fetch MPC info' });
}
});
export { router as custodialRouter };

View File

@@ -0,0 +1,155 @@
import express from 'express';
import { GovernanceDiscussionService } from '../services/governance-discussion';
import { GovernanceAnalyticsService } from '../services/governance-analytics';
import { DelegationService } from '../services/delegation';
import { ethers } from 'ethers';
const router = express.Router();
// Initialize services
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL || 'http://localhost:8545');
const diamondAddress = process.env.DIAMOND_ADDRESS || '';
const discussionService = new GovernanceDiscussionService();
const analyticsService = new GovernanceAnalyticsService();
const delegationService = new DelegationService(provider, diamondAddress);
/**
* POST /api/governance/discussion/:proposalId/comment
* Add comment to proposal
*/
router.post('/discussion/:proposalId/comment', async (req, res) => {
try {
const { author, content, parentId } = req.body;
if (!author || !content) {
return res.status(400).json({ error: 'Author and content are required' });
}
const comment = await discussionService.addComment(
BigInt(req.params.proposalId),
author,
content,
parentId
);
res.json(comment);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/governance/discussion/:proposalId
* Get discussion thread
*/
router.get('/discussion/:proposalId', async (req, res) => {
try {
const discussion = await discussionService.getDiscussion(BigInt(req.params.proposalId));
res.json(discussion);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* POST /api/governance/discussion/comment/:id/vote
* Vote on comment
*/
router.post('/discussion/comment/:id/vote', async (req, res) => {
try {
const { voter, upvote } = req.body;
if (!voter || upvote === undefined) {
return res.status(400).json({ error: 'Voter and upvote are required' });
}
await discussionService.voteComment(req.params.id, voter, upvote);
res.json({ success: true });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/governance/analytics/metrics
* Get governance metrics
*/
router.get('/analytics/metrics', async (req, res) => {
try {
const metrics = await analyticsService.calculateMetrics();
res.json(metrics);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/governance/analytics/trends
* Get voting trends
*/
router.get('/analytics/trends', async (req, res) => {
try {
const startDate = req.query.startDate ? new Date(req.query.startDate as string) : new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const endDate = req.query.endDate ? new Date(req.query.endDate as string) : new Date();
const trends = await analyticsService.getVotingTrends(startDate, endDate);
res.json(trends);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/governance/analytics/delegates
* Get delegate leaderboard
*/
router.get('/analytics/delegates', async (req, res) => {
try {
const limit = parseInt(req.query.limit as string) || 10;
const leaderboard = await analyticsService.getDelegateLeaderboard(limit);
res.json(leaderboard);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/governance/delegation/:address
* Get delegation for address
*/
router.get('/delegation/:address', async (req, res) => {
try {
const delegation = await delegationService.getDelegation(req.params.address);
res.json(delegation);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/governance/delegation
* Get all delegations
*/
router.get('/delegation', async (req, res) => {
try {
const delegations = await delegationService.getAllDelegations();
res.json(delegations);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/governance/delegation/reputation/:address
* Get delegate reputation
*/
router.get('/delegation/reputation/:address', async (req, res) => {
try {
const reputation = await delegationService.getDelegateReputation(req.params.address);
res.json(reputation);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
export { router as governanceAdvancedRouter };

View File

@@ -0,0 +1,101 @@
import express from 'express';
import { SnapshotService } from '../services/snapshot';
import { SnapshotAPI } from '../integrations/snapshot-api';
const router = express.Router();
const snapshotService = new SnapshotService(process.env.SNAPSHOT_SPACE_ID || 'asle.eth');
const snapshotAPI = new SnapshotAPI();
/**
* GET /api/governance/snapshot/proposals
* Get Snapshot proposals
*/
router.get('/snapshot/proposals', async (req, res) => {
try {
const limit = parseInt(req.query.limit as string) || 20;
const skip = parseInt(req.query.skip as string) || 0;
const proposals = await snapshotService.getProposals(limit, skip);
res.json(proposals);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/governance/snapshot/proposal/:id
* Get Snapshot proposal by ID
*/
router.get('/snapshot/proposal/:id', async (req, res) => {
try {
const proposal = await snapshotService.getProposal(req.params.id);
if (!proposal) {
return res.status(404).json({ error: 'Proposal not found' });
}
res.json(proposal);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/governance/snapshot/proposal/:id/votes
* Get votes for Snapshot proposal
*/
router.get('/snapshot/proposal/:id/votes', async (req, res) => {
try {
const votes = await snapshotService.getVotes(req.params.id);
res.json(votes);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* POST /api/governance/snapshot/proposal/:id/vote
* Vote on Snapshot proposal
*/
router.post('/snapshot/proposal/:id/vote', async (req, res) => {
try {
const { choice, voter, signature } = req.body;
if (choice === undefined || !voter || !signature) {
return res.status(400).json({ error: 'Missing required fields' });
}
const vote = await snapshotService.vote(req.params.id, choice, voter, signature);
res.json(vote);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* POST /api/governance/snapshot/sync/:id
* Sync Snapshot proposal to local governance
*/
router.post('/snapshot/sync/:id', async (req, res) => {
try {
const localProposal = await snapshotService.syncProposalToLocal(req.params.id);
res.json(localProposal);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/governance/snapshot/voting-power/:address
* Get voting power for address
*/
router.get('/snapshot/voting-power/:address', async (req, res) => {
try {
const snapshot = parseInt(req.query.snapshot as string) || Math.floor(Date.now() / 1000);
const vp = await snapshotService.getVotingPower(req.params.address, snapshot);
res.json({ address: req.params.address, votingPower: vp, snapshot });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
export { router as governanceSnapshotRouter };

View File

@@ -0,0 +1,116 @@
import { Router } from 'express';
import { MonitoringService } from '../services/monitoring';
import { authenticateToken, optionalAuth, AuthRequest } from '../middleware/auth';
import { apiRateLimiter } from '../middleware/rateLimit';
import { validate } from '../middleware/validation';
import { z } from 'zod';
const router = Router();
const monitoringService = new MonitoringService();
const createAlertSchema = z.object({
body: z.object({
alertType: z.string(),
severity: z.enum(['low', 'medium', 'high', 'critical']),
message: z.string(),
metadata: z.any().optional()
})
});
const recordMetricSchema = z.object({
body: z.object({
metricType: z.string(),
value: z.string(),
metadata: z.any().optional()
})
});
router.use(apiRateLimiter);
router.get('/health', optionalAuth, async (req, res) => {
try {
const health = await monitoringService.getSystemHealth();
res.json({ success: true, health });
} catch (error: any) {
console.error('Error fetching system health:', error);
res.status(500).json({ error: error.message || 'Failed to fetch health' });
}
});
router.get('/alerts', authenticateToken, async (req: AuthRequest, res) => {
try {
const filters: any = {};
if (req.query.type) filters.type = req.query.type;
if (req.query.severity) filters.severity = req.query.severity;
if (req.query.resolved !== undefined) {
filters.resolved = req.query.resolved === 'true';
}
const alerts = await monitoringService.getAlerts(filters);
res.json({ success: true, alerts });
} catch (error: any) {
console.error('Error fetching alerts:', error);
res.status(500).json({ error: error.message || 'Failed to fetch alerts' });
}
});
router.post('/alerts', authenticateToken, validate(createAlertSchema), async (req: AuthRequest, res) => {
try {
const { alertType, severity, message, metadata } = req.body;
const alertId = await monitoringService.createAlert(alertType, severity, message, metadata);
res.json({ success: true, alertId });
} catch (error: any) {
console.error('Error creating alert:', error);
res.status(500).json({ error: error.message || 'Failed to create alert' });
}
});
router.post('/alerts/:alertId/resolve', authenticateToken, async (req: AuthRequest, res) => {
try {
const { alertId } = req.params;
await monitoringService.resolveAlert(alertId);
res.json({ success: true });
} catch (error: any) {
console.error('Error resolving alert:', error);
res.status(500).json({ error: error.message || 'Failed to resolve alert' });
}
});
router.get('/metrics', optionalAuth, async (req, res) => {
try {
const { name, from, to } = req.query;
const timeRange = from && to ? { from: Number(from), to: Number(to) } : undefined;
const metrics = await monitoringService.getMetrics(name as string, timeRange);
res.json({ success: true, metrics });
} catch (error: any) {
console.error('Error fetching metrics:', error);
res.status(500).json({ error: error.message || 'Failed to fetch metrics' });
}
});
router.post('/metrics', authenticateToken, validate(recordMetricSchema), async (req: AuthRequest, res) => {
try {
const { metricType, value, metadata } = req.body;
await monitoringService.recordMetric(metricType, value, metadata);
res.json({ success: true });
} catch (error: any) {
console.error('Error recording metric:', error);
res.status(500).json({ error: error.message || 'Failed to record metric' });
}
});
router.get('/reports/:period', authenticateToken, async (req: AuthRequest, res) => {
try {
const { period } = req.params;
if (!['daily', 'weekly', 'monthly'].includes(period)) {
return res.status(400).json({ error: 'Invalid period. Must be daily, weekly, or monthly' });
}
const report = await monitoringService.generateReport(period as 'daily' | 'weekly' | 'monthly');
res.json({ success: true, report });
} catch (error: any) {
console.error('Error generating report:', error);
res.status(500).json({ error: error.message || 'Failed to generate report' });
}
});
export { router as monitoringRouter };

View File

@@ -0,0 +1,117 @@
import express from 'express';
import { CrossChainManager } from '../services/cross-chain-manager';
const router = express.Router();
const crossChainManager = new CrossChainManager();
/**
* POST /api/chains/register
* Register a new chain (EVM, Solana, or Cosmos)
*/
router.post('/register', async (req, res) => {
try {
const { chainId, chainType, name, rpcUrl, bridgeConfig } = req.body;
if (!chainId || !chainType || !name || !rpcUrl) {
return res.status(400).json({ error: 'Missing required fields' });
}
await crossChainManager.registerChain({
chainId,
chainType,
name,
rpcUrl,
bridgeConfig,
});
res.json({ success: true, message: 'Chain registered successfully' });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* POST /api/chains/cross-chain/send
* Send cross-chain message
*/
router.post('/cross-chain/send', async (req, res) => {
try {
const { sourceChainId, targetChainId, payload } = req.body;
if (!sourceChainId || !targetChainId || !payload) {
return res.status(400).json({ error: 'Missing required fields' });
}
const messageId = await crossChainManager.sendCrossChainMessage(
sourceChainId,
targetChainId,
payload
);
res.json({ messageId, status: 'pending' });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/chains/:chainId/status
* Get chain status
*/
router.get('/:chainId/status', async (req, res) => {
try {
const status = await crossChainManager.getChainStatus(req.params.chainId);
res.json(status);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* POST /api/chains/bridge/solana
* Bridge to/from Solana
*/
router.post('/bridge/solana', async (req, res) => {
try {
const { direction, chainId, amount, tokenAddress } = req.body;
if (direction === 'to') {
const txHash = await crossChainManager.bridgeToSolana(chainId, BigInt(amount), tokenAddress);
res.json({ txHash, status: 'pending' });
} else {
const txHash = await crossChainManager.bridgeFromSolana(chainId, BigInt(amount), tokenAddress);
res.json({ txHash, status: 'pending' });
}
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* POST /api/chains/bridge/ibc
* Bridge via IBC (Cosmos)
*/
router.post('/bridge/ibc', async (req, res) => {
try {
const { sourceChain, targetChain, channelId, denom, amount } = req.body;
if (!sourceChain || !targetChain || !channelId || !denom || !amount) {
return res.status(400).json({ error: 'Missing required fields' });
}
const txHash = await crossChainManager.bridgeViaIBC(
sourceChain,
targetChain,
channelId,
denom,
BigInt(amount)
);
res.json({ txHash, status: 'pending' });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
export { router as nonEVMChainsRouter };

221
backend/src/api/pools.ts Normal file
View File

@@ -0,0 +1,221 @@
import { Router } from 'express';
import { ethers } from 'ethers';
import { PrismaClient } from '@prisma/client';
import { z } from 'zod';
import { authenticateToken, optionalAuth, AuthRequest } from '../middleware/auth';
import { apiRateLimiter } from '../middleware/rateLimit';
import { validate, schemas } from '../middleware/validation';
const router = Router();
const prisma = new PrismaClient();
// Initialize provider and contract interface
let provider: ethers.Provider;
let diamondContract: ethers.Contract;
function initProvider() {
if (!provider) {
const rpcUrl = process.env.RPC_URL || 'http://localhost:8545';
provider = new ethers.JsonRpcProvider(rpcUrl);
}
return provider;
}
// Validation schemas
const createPoolSchema = z.object({
body: z.object({
baseToken: schemas.address,
quoteToken: schemas.address,
initialBaseReserve: schemas.amount,
initialQuoteReserve: schemas.amount,
virtualBaseReserve: schemas.amount,
virtualQuoteReserve: schemas.amount,
k: z.string().regex(/^\d+$/, 'Invalid k value'),
oraclePrice: schemas.amount,
oracle: schemas.address.optional()
})
});
router.use(apiRateLimiter);
// GET /api/pools - List all pools
router.get('/', optionalAuth, async (req, res) => {
try {
// Get from database
const dbPools = await prisma.pool.findMany({
orderBy: { createdAt: 'desc' },
take: 100,
include: {
_count: {
select: { transactions: true, lpPositions: true }
}
}
});
// Also fetch from blockchain for latest state
const pools = await Promise.all(dbPools.map(async (dbPool) => {
try {
const provider = initProvider();
const diamondAddress = process.env.DIAMOND_ADDRESS;
if (!diamondAddress) {
return {
id: Number(dbPool.poolId),
baseToken: dbPool.baseToken,
quoteToken: dbPool.quoteToken,
baseReserve: dbPool.baseReserve,
quoteReserve: dbPool.quoteReserve,
active: dbPool.active,
createdAt: dbPool.createdAt.toISOString()
};
}
// Fetch from contract (simplified - would need proper ABI)
return {
id: Number(dbPool.poolId),
baseToken: dbPool.baseToken,
quoteToken: dbPool.quoteToken,
baseReserve: dbPool.baseReserve,
quoteReserve: dbPool.quoteReserve,
virtualBaseReserve: dbPool.virtualBaseReserve,
virtualQuoteReserve: dbPool.virtualQuoteReserve,
k: dbPool.k,
oraclePrice: dbPool.oraclePrice,
active: dbPool.active,
transactionCount: dbPool._count.transactions,
lpCount: dbPool._count.lpPositions,
createdAt: dbPool.createdAt.toISOString(),
updatedAt: dbPool.updatedAt.toISOString()
};
} catch (error) {
console.error(`Error fetching pool ${dbPool.poolId}:`, error);
return {
id: Number(dbPool.poolId),
baseToken: dbPool.baseToken,
quoteToken: dbPool.quoteToken,
active: dbPool.active
};
}
}));
res.json({ pools });
} catch (error) {
console.error('Error fetching pools:', error);
res.status(500).json({ error: 'Failed to fetch pools' });
}
});
// GET /api/pools/:poolId - Get pool details
router.get('/:poolId', optionalAuth, async (req, res) => {
try {
const poolId = BigInt(req.params.poolId);
const dbPool = await prisma.pool.findUnique({
where: { poolId },
include: {
transactions: {
orderBy: { timestamp: 'desc' },
take: 10
},
lpPositions: true
}
});
if (!dbPool) {
return res.status(404).json({ error: 'Pool not found' });
}
// Fetch latest state from blockchain
const provider = initProvider();
const diamondAddress = process.env.DIAMOND_ADDRESS;
// In production, fetch from LiquidityFacet
const pool = {
id: Number(dbPool.poolId),
baseToken: dbPool.baseToken,
quoteToken: dbPool.quoteToken,
baseReserve: dbPool.baseReserve,
quoteReserve: dbPool.quoteReserve,
virtualBaseReserve: dbPool.virtualBaseReserve,
virtualQuoteReserve: dbPool.virtualQuoteReserve,
k: dbPool.k,
oraclePrice: dbPool.oraclePrice,
active: dbPool.active,
recentTransactions: dbPool.transactions.map(tx => ({
id: tx.id,
txHash: tx.txHash,
user: tx.user,
tokenIn: tx.tokenIn,
tokenOut: tx.tokenOut,
amountIn: tx.amountIn,
amountOut: tx.amountOut,
timestamp: tx.timestamp.toISOString()
})),
lpPositions: dbPool.lpPositions.length,
createdAt: dbPool.createdAt.toISOString()
};
res.json({ pool });
} catch (error) {
console.error('Error fetching pool:', error);
res.status(500).json({ error: 'Failed to fetch pool' });
}
});
// POST /api/pools - Create new pool (requires authentication)
router.post('/', authenticateToken, validate(createPoolSchema), async (req: AuthRequest, res) => {
try {
const {
baseToken,
quoteToken,
initialBaseReserve,
initialQuoteReserve,
virtualBaseReserve,
virtualQuoteReserve,
k,
oraclePrice,
oracle
} = req.body;
const provider = initProvider();
const diamondAddress = process.env.DIAMOND_ADDRESS;
if (!diamondAddress) {
return res.status(500).json({ error: 'Diamond address not configured' });
}
// In production, this would:
// 1. Create transaction to LiquidityFacet.createPool()
// 2. Wait for confirmation
// 3. Store in database
// For now, create mock pool in database
const pool = await prisma.pool.create({
data: {
poolId: BigInt(Date.now()), // In production, get from contract
baseToken,
quoteToken,
baseReserve: initialBaseReserve || '0',
quoteReserve: initialQuoteReserve || '0',
virtualBaseReserve: virtualBaseReserve || '0',
virtualQuoteReserve: virtualQuoteReserve || '0',
k: k || '0',
oraclePrice: oraclePrice || '0',
active: true
}
});
res.status(201).json({
pool: {
id: Number(pool.poolId),
baseToken: pool.baseToken,
quoteToken: pool.quoteToken,
active: pool.active
}
});
} catch (error) {
console.error('Error creating pool:', error);
res.status(500).json({ error: 'Failed to create pool' });
}
});
export { router as poolsRouter };

138
backend/src/api/vaults.ts Normal file
View File

@@ -0,0 +1,138 @@
import { Router } from 'express';
import { PrismaClient } from '@prisma/client';
import { authenticateToken, optionalAuth, AuthRequest } from '../middleware/auth';
import { apiRateLimiter } from '../middleware/rateLimit';
import { validate, schemas } from '../middleware/validation';
import { z } from 'zod';
const router = Router();
const prisma = new PrismaClient();
const createVaultSchema = z.object({
body: z.object({
asset: schemas.address.optional(),
isMultiAsset: z.boolean().default(false)
})
});
router.use(apiRateLimiter);
router.get('/', optionalAuth, async (req, res) => {
try {
const vaults = await prisma.vault.findMany({
orderBy: { createdAt: 'desc' },
take: 100,
include: {
_count: {
select: { deposits: true, withdrawals: true }
}
}
});
res.json({
vaults: vaults.map(v => ({
id: Number(v.vaultId),
asset: v.asset,
isMultiAsset: v.isMultiAsset,
totalAssets: v.totalAssets,
totalSupply: v.totalSupply,
active: v.active,
depositCount: v._count.deposits,
withdrawalCount: v._count.withdrawals,
createdAt: v.createdAt.toISOString()
}))
});
} catch (error) {
console.error('Error fetching vaults:', error);
res.status(500).json({ error: 'Failed to fetch vaults' });
}
});
router.get('/:vaultId', optionalAuth, async (req, res) => {
try {
const vaultId = BigInt(req.params.vaultId);
const vault = await prisma.vault.findUnique({
where: { vaultId },
include: {
deposits: {
orderBy: { timestamp: 'desc' },
take: 10
},
withdrawals: {
orderBy: { timestamp: 'desc' },
take: 10
}
}
});
if (!vault) {
return res.status(404).json({ error: 'Vault not found' });
}
res.json({
vault: {
id: Number(vault.vaultId),
asset: vault.asset,
isMultiAsset: vault.isMultiAsset,
totalAssets: vault.totalAssets,
totalSupply: vault.totalSupply,
active: vault.active,
recentDeposits: vault.deposits.map(d => ({
id: d.id,
user: d.user,
assets: d.assets,
shares: d.shares,
txHash: d.txHash,
timestamp: d.timestamp.toISOString()
})),
recentWithdrawals: vault.withdrawals.map(w => ({
id: w.id,
user: w.user,
assets: w.assets,
shares: w.shares,
txHash: w.txHash,
timestamp: w.timestamp.toISOString()
})),
createdAt: vault.createdAt.toISOString()
}
});
} catch (error) {
console.error('Error fetching vault:', error);
res.status(500).json({ error: 'Failed to fetch vault' });
}
});
router.post('/', authenticateToken, validate(createVaultSchema), async (req: AuthRequest, res) => {
try {
const { asset, isMultiAsset } = req.body;
if (!isMultiAsset && !asset) {
return res.status(400).json({ error: 'Asset address required for ERC-4626 vaults' });
}
const vault = await prisma.vault.create({
data: {
vaultId: BigInt(Date.now()),
asset: asset || null,
isMultiAsset: isMultiAsset || false,
totalAssets: '0',
totalSupply: '0',
active: true
}
});
res.status(201).json({
vault: {
id: Number(vault.vaultId),
asset: vault.asset,
isMultiAsset: vault.isMultiAsset,
active: vault.active
}
});
} catch (error) {
console.error('Error creating vault:', error);
res.status(500).json({ error: 'Failed to create vault' });
}
});
export { router as vaultsRouter };

View File

@@ -0,0 +1,21 @@
import { Router } from 'express';
import { WhiteLabelService } from '../services/white-label';
const router = Router();
const whiteLabelService = new WhiteLabelService();
// Public endpoint to get config by domain
router.get('/:domain', async (req, res) => {
try {
const config = await whiteLabelService.getConfigByDomain(req.params.domain);
if (!config || !config.active) {
return res.status(404).json({ error: 'Config not found' });
}
res.json(config);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
export default router;

View File

@@ -0,0 +1,206 @@
import { PrismaClient } from '@prisma/client';
import { AnalyticsService } from '../services/analytics';
const prisma = new PrismaClient();
const analyticsService = new AnalyticsService();
export const resolvers = {
Query: {
pools: async () => {
const dbPools = await prisma.pool.findMany({
orderBy: { createdAt: 'desc' },
take: 100
});
return dbPools.map(p => ({
id: p.poolId.toString(),
baseToken: p.baseToken,
quoteToken: p.quoteToken,
baseReserve: p.baseReserve,
quoteReserve: p.quoteReserve,
virtualBaseReserve: p.virtualBaseReserve,
virtualQuoteReserve: p.virtualQuoteReserve,
k: p.k,
oraclePrice: p.oraclePrice,
active: p.active
}));
},
pool: async (_: any, { id }: { id: string }) => {
const dbPool = await prisma.pool.findUnique({
where: { poolId: BigInt(id) }
});
if (!dbPool) return null;
return {
id: dbPool.poolId.toString(),
baseToken: dbPool.baseToken,
quoteToken: dbPool.quoteToken,
baseReserve: dbPool.baseReserve,
quoteReserve: dbPool.quoteReserve,
virtualBaseReserve: dbPool.virtualBaseReserve,
virtualQuoteReserve: dbPool.virtualQuoteReserve,
k: dbPool.k,
oraclePrice: dbPool.oraclePrice,
active: dbPool.active
};
},
vaults: async () => {
const dbVaults = await prisma.vault.findMany({
orderBy: { createdAt: 'desc' },
take: 100
});
return dbVaults.map(v => ({
id: v.vaultId.toString(),
asset: v.asset,
totalAssets: v.totalAssets,
totalSupply: v.totalSupply,
isMultiAsset: v.isMultiAsset,
active: v.active
}));
},
vault: async (_: any, { id }: { id: string }) => {
const dbVault = await prisma.vault.findUnique({
where: { vaultId: BigInt(id) }
});
if (!dbVault) return null;
return {
id: dbVault.vaultId.toString(),
asset: dbVault.asset,
totalAssets: dbVault.totalAssets,
totalSupply: dbVault.totalSupply,
isMultiAsset: dbVault.isMultiAsset,
active: dbVault.active
};
},
poolAnalytics: async (_: any, { poolId, startDate, endDate }: { poolId: string; startDate?: string; endDate?: string }) => {
const analytics = await analyticsService.getPoolAnalytics(
BigInt(poolId),
startDate ? new Date(startDate) : undefined,
endDate ? new Date(endDate) : undefined
);
return analytics.map(a => ({
poolId: a.poolId.toString(),
tvl: a.tvl,
volume24h: a.volume24h,
volume7d: a.volume7d,
volume30d: a.volume30d,
fees24h: a.fees24h,
fees7d: a.fees7d,
fees30d: a.fees30d,
utilizationRate: a.utilizationRate,
timestamp: a.timestamp.toISOString(),
}));
},
portfolio: async (_: any, { address, startDate, endDate }: { address: string; startDate?: string; endDate?: string }) => {
if (startDate || endDate) {
const history = await analyticsService.getUserPortfolioHistory(
address,
startDate ? new Date(startDate) : undefined,
endDate ? new Date(endDate) : undefined
);
if (history.length === 0) return null;
const latest = history[0];
return {
userAddress: latest.userAddress,
totalValue: latest.totalValue,
poolPositions: latest.poolPositions,
vaultPositions: latest.vaultPositions,
timestamp: latest.timestamp.toISOString(),
};
} else {
const portfolio = await analyticsService.calculateUserPortfolio(address);
return {
userAddress: portfolio.userAddress,
totalValue: portfolio.totalValue,
poolPositions: portfolio.poolPositions,
vaultPositions: portfolio.vaultPositions,
timestamp: portfolio.timestamp.toISOString(),
};
}
},
systemMetrics: async () => {
const metrics = await analyticsService.calculateSystemMetrics();
return {
totalTVL: metrics.totalTVL,
totalVolume24h: metrics.totalVolume24h,
totalFees24h: metrics.totalFees24h,
activePools: metrics.activePools,
activeUsers: metrics.activeUsers,
transactionCount24h: metrics.transactionCount24h,
};
},
transactionAnalytics: async (_: any, { poolId, startDate, endDate }: { poolId?: string; startDate?: string; endDate?: string }) => {
const analytics = await analyticsService.getTransactionAnalytics(
poolId ? BigInt(poolId) : undefined,
startDate ? new Date(startDate) : undefined,
endDate ? new Date(endDate) : undefined
);
return analytics;
},
},
Mutation: {
createPool: async (_: any, args: any) => {
const pool = await prisma.pool.create({
data: {
poolId: BigInt(Date.now()),
baseToken: args.baseToken,
quoteToken: args.quoteToken,
baseReserve: args.initialBaseReserve,
quoteReserve: args.initialQuoteReserve,
virtualBaseReserve: args.virtualBaseReserve,
virtualQuoteReserve: args.virtualQuoteReserve,
k: args.k,
oraclePrice: args.oraclePrice,
active: true
}
});
return {
id: pool.poolId.toString(),
baseToken: pool.baseToken,
quoteToken: pool.quoteToken,
baseReserve: pool.baseReserve,
quoteReserve: pool.quoteReserve,
virtualBaseReserve: pool.virtualBaseReserve,
virtualQuoteReserve: pool.virtualQuoteReserve,
k: pool.k,
oraclePrice: pool.oraclePrice,
active: pool.active
};
},
createVault: async (_: any, args: any) => {
const vault = await prisma.vault.create({
data: {
vaultId: BigInt(Date.now()),
asset: args.asset || null,
isMultiAsset: args.isMultiAsset,
totalAssets: '0',
totalSupply: '0',
active: true
}
});
return {
id: vault.vaultId.toString(),
asset: vault.asset,
totalAssets: vault.totalAssets,
totalSupply: vault.totalSupply,
isMultiAsset: vault.isMultiAsset,
active: vault.active
};
},
},
};

View File

@@ -0,0 +1,85 @@
export const typeDefs = `#graphql
type Pool {
id: ID!
baseToken: String!
quoteToken: String!
baseReserve: String!
quoteReserve: String!
virtualBaseReserve: String
virtualQuoteReserve: String
k: String
oraclePrice: String
active: Boolean!
}
type Vault {
id: ID!
asset: String
totalAssets: String!
totalSupply: String!
isMultiAsset: Boolean!
active: Boolean!
}
type PoolMetrics {
poolId: String!
tvl: String!
volume24h: String!
volume7d: String!
volume30d: String!
fees24h: String!
fees7d: String!
fees30d: String!
utilizationRate: Float!
timestamp: String!
}
type Portfolio {
userAddress: String!
totalValue: String!
poolPositions: JSON!
vaultPositions: JSON!
timestamp: String!
}
type SystemMetrics {
totalTVL: String!
totalVolume24h: String!
totalFees24h: String!
activePools: Int!
activeUsers: Int!
transactionCount24h: Int!
}
scalar JSON
type Query {
pools: [Pool!]!
pool(id: ID!): Pool
vaults: [Vault!]!
vault(id: ID!): Vault
poolAnalytics(poolId: String!, startDate: String, endDate: String): [PoolMetrics!]!
portfolio(address: String!, startDate: String, endDate: String): Portfolio
systemMetrics: SystemMetrics!
transactionAnalytics(poolId: String, startDate: String, endDate: String): [JSON!]!
}
type Mutation {
createPool(
baseToken: String!
quoteToken: String!
initialBaseReserve: String!
initialQuoteReserve: String!
virtualBaseReserve: String!
virtualQuoteReserve: String!
k: String!
oraclePrice: String!
): Pool!
createVault(
asset: String
isMultiAsset: Boolean!
): Vault!
}
`;

View File

@@ -0,0 +1,22 @@
export interface Pool {
id: string;
baseToken: string;
quoteToken: string;
baseReserve: string;
quoteReserve: string;
virtualBaseReserve?: string;
virtualQuoteReserve?: string;
k?: string;
oraclePrice?: string;
active: boolean;
}
export interface Vault {
id: string;
asset: string | null;
totalAssets: string;
totalSupply: string;
isMultiAsset: boolean;
active: boolean;
}

171
backend/src/index.ts Normal file
View File

@@ -0,0 +1,171 @@
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { typeDefs } from './graphql/schema';
import { resolvers } from './graphql/resolvers';
import { poolsRouter } from './api/pools';
import { vaultsRouter } from './api/vaults';
import { complianceRouter } from './api/compliance';
import { ccipRouter } from './api/ccip';
import { custodialRouter } from './api/custodial';
import { bankRouter } from './api/bank';
import { monitoringRouter } from './api/monitoring';
import { analyticsRouter } from './api/analytics';
import { complianceReportsRouter } from './api/compliance-reports';
import { complianceAdvancedRouter } from './api/compliance-advanced';
import { governanceSnapshotRouter } from './api/governance-snapshot';
import { governanceAdvancedRouter } from './api/governance-advanced';
import { mobileRouter } from './routes/mobile';
import { nonEVMChainsRouter } from './api/non-evm-chains';
import adminRouter from './api/admin';
import whiteLabelRouter from './api/white-label';
import { WebSocketServerManager } from './websocket/server';
import { apiRateLimiter, securityHeaders, corsConfig, sanitizeInput } from './middleware/security';
import { MonitoringService } from './services/monitoring';
import winston from 'winston';
// Initialize logger
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
})
]
});
// Initialize monitoring
const monitoringService = new MonitoringService();
const app = express();
const PORT = process.env.PORT || 4000;
// Security middleware
app.use(securityHeaders);
app.use(cors(corsConfig));
app.use(sanitizeInput);
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// Global rate limiting
app.use(apiRateLimiter);
// Health check endpoint
app.get('/health', async (req, res) => {
try {
const health = await monitoringService.getSystemHealth();
res.json({
status: health.status,
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
} catch (error) {
res.status(503).json({ status: 'down', error: 'Health check failed' });
}
});
// REST API routes
app.use('/api/pools', poolsRouter);
app.use('/api/vaults', vaultsRouter);
app.use('/api/compliance', complianceRouter);
app.use('/api/ccip', ccipRouter);
app.use('/api/custodial', custodialRouter);
app.use('/api/bank', bankRouter);
app.use('/api/monitoring', monitoringRouter);
app.use('/api/analytics', analyticsRouter);
app.use('/api/compliance/reports', complianceReportsRouter);
app.use('/api/compliance', complianceAdvancedRouter);
app.use('/api/governance', governanceSnapshotRouter);
app.use('/api/governance', governanceAdvancedRouter);
app.use('/api/mobile', mobileRouter);
app.use('/api/chains', nonEVMChainsRouter);
app.use('/api/admin', adminRouter);
app.use('/api/white-label', whiteLabelRouter);
// GraphQL server
const server = new ApolloServer({
typeDefs,
resolvers,
formatError: (err) => {
logger.error('GraphQL Error:', err);
return {
message: err.message,
extensions: {
code: err.extensions?.code,
timestamp: new Date().toISOString()
}
};
}
});
// Error handling middleware
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.error('Unhandled error:', err);
res.status(err.status || 500).json({
error: process.env.NODE_ENV === 'production' ? 'Internal server error' : err.message,
timestamp: new Date().toISOString()
});
});
async function startServer() {
try {
await server.start();
app.use(
'/graphql',
expressMiddleware(server, {
context: async ({ req }) => {
// Add authentication context if needed
return {
user: (req as any).user,
logger
};
}
})
);
const httpServer = app.listen(PORT, () => {
logger.info(`ASLE Backend Server running on http://localhost:${PORT}`);
logger.info(`GraphQL endpoint: http://localhost:${PORT}/graphql`);
logger.info(`Health check: http://localhost:${PORT}/health`);
// Record startup metric
monitoringService.recordMetric('server.startup', '1').catch(console.error);
});
// Initialize WebSocket server
const wsManager = new WebSocketServerManager(httpServer);
logger.info(`WebSocket server running on ws://localhost:${PORT}/ws`);
} catch (error) {
logger.error('Failed to start server:', error);
process.exit(1);
}
}
// Graceful shutdown
process.on('SIGTERM', async () => {
logger.info('SIGTERM received, shutting down gracefully');
await server.stop();
process.exit(0);
});
process.on('SIGINT', async () => {
logger.info('SIGINT received, shutting down gracefully');
await server.stop();
process.exit(0);
});
startServer().catch((error) => {
logger.error('Fatal error starting server:', error);
process.exit(1);
});

View File

@@ -0,0 +1,185 @@
import axios from 'axios';
import { SnapshotService, SnapshotProposal, SnapshotVote } from '../services/snapshot';
const SNAPSHOT_API_URL = 'https://hub.snapshot.org/api';
const SNAPSHOT_GRAPHQL_URL = 'https://hub.snapshot.org/graphql';
export class SnapshotAPI {
/**
* GraphQL query to Snapshot
*/
async query(query: string, variables: any = {}): Promise<any> {
try {
const response = await axios.post(SNAPSHOT_GRAPHQL_URL, {
query,
variables,
});
return response.data.data;
} catch (error: any) {
throw new Error(`Snapshot GraphQL error: ${error.message}`);
}
}
/**
* Get space information
*/
async getSpace(spaceId: string): Promise<any> {
const query = `
query Space($id: String!) {
space(id: $id) {
id
name
about
network
symbol
strategies {
name
network
params
}
admins
moderators
members
filters {
minScore
onlyMembers
}
}
}
`;
return await this.query(query, { id: spaceId });
}
/**
* Get proposals with GraphQL
*/
async getProposals(spaceId: string, first: number = 20, skip: number = 0): Promise<any> {
const query = `
query Proposals($space: String!, $first: Int!, $skip: Int!) {
proposals(
first: $first
skip: $skip
where: { space: $space }
orderBy: "created"
orderDirection: desc
) {
id
title
body
choices
start
end
snapshot
state
author
created
scores
scores_by_strategy
scores_total
scores_updated
plugins
network
type
strategies {
name
network
params
}
}
}
`;
return await this.query(query, { space: spaceId, first, skip });
}
/**
* Get proposal by ID
*/
async getProposal(proposalId: string): Promise<any> {
const query = `
query Proposal($id: String!) {
proposal(id: $id) {
id
title
body
choices
start
end
snapshot
state
author
created
scores
scores_by_strategy
scores_total
scores_updated
plugins
network
type
strategies {
name
network
params
}
space {
id
name
}
}
}
`;
return await this.query(query, { id: proposalId });
}
/**
* Get votes for proposal
*/
async getVotes(proposalId: string, first: number = 1000): Promise<any> {
const query = `
query Votes($proposal: String!, $first: Int!) {
votes(
first: $first
where: { proposal: $proposal }
orderBy: "vp"
orderDirection: desc
) {
id
voter
vp
vp_by_strategy
choice
created
proposal {
id
}
}
}
`;
return await this.query(query, { proposal: proposalId, first });
}
/**
* Get voting power
*/
async getVotingPower(
address: string,
spaceId: string,
snapshot: number
): Promise<number> {
try {
const response = await axios.post(`${SNAPSHOT_API_URL}/scoring`, {
address,
space: spaceId,
snapshot,
});
return response.data?.vp || 0;
} catch (error: any) {
console.error('Error getting voting power:', error);
return 0;
}
}
}

View File

@@ -0,0 +1,50 @@
/**
* Job to clean up old audit logs based on retention policy
* Run periodically (e.g., daily via cron)
*/
import { PrismaClient } from '@prisma/client';
import { SystemConfigService } from '../services/system-config';
const prisma = new PrismaClient();
const configService = new SystemConfigService();
export async function cleanupAuditLogs() {
try {
// Get retention period from config (default: 90 days)
const retentionDays = await configService.getConfig('audit_log_retention_days') || 90;
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
// Delete old audit logs
const result = await prisma.adminAuditLog.deleteMany({
where: {
timestamp: {
lt: cutoffDate,
},
},
});
console.log(`Cleaned up ${result.count} audit logs older than ${retentionDays} days`);
return result.count;
} catch (error: any) {
console.error('Error cleaning up audit logs:', error);
throw error;
} finally {
await prisma.$disconnect();
}
}
// Run if called directly
if (require.main === module) {
cleanupAuditLogs()
.then(() => {
console.log('Audit log cleanup completed');
process.exit(0);
})
.catch((error) => {
console.error('Audit log cleanup failed:', error);
process.exit(1);
});
}

View File

@@ -0,0 +1,58 @@
import { AnalyticsService } from '../services/analytics';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
const analyticsService = new AnalyticsService();
/**
* Scheduled job to calculate and store metrics
* Should be run every hour or as configured
*/
export async function calculateMetricsJob() {
console.log('Starting metrics calculation job...');
try {
// Calculate pool metrics for all active pools
const pools = await prisma.pool.findMany({
where: { active: true },
});
for (const pool of pools) {
try {
await analyticsService.calculatePoolMetrics(pool.poolId);
console.log(`Calculated metrics for pool ${pool.poolId}`);
} catch (error) {
console.error(`Error calculating metrics for pool ${pool.poolId}:`, error);
}
}
// Calculate system metrics
const systemMetrics = await analyticsService.calculateSystemMetrics();
console.log('System metrics:', systemMetrics);
// Store system metrics
await prisma.metric.create({
data: {
metricType: 'system',
value: JSON.stringify(systemMetrics),
timestamp: new Date(),
},
});
console.log('Metrics calculation job completed');
} catch (error) {
console.error('Error in metrics calculation job:', error);
throw error;
}
}
// Run if called directly
if (require.main === module) {
calculateMetricsJob()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
}

View File

@@ -0,0 +1,77 @@
import { RealTimeScreeningService } from '../services/real-time-screening';
import { ComplianceService } from '../services/compliance';
import { SARGenerator } from '../services/sar-generator';
import { CTRGenerator } from '../services/ctr-generator';
import { ethers } from 'ethers';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
/**
* Scheduled job to monitor and screen transactions
*/
export async function screeningMonitorJob() {
console.log('Starting screening monitor job...');
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL || 'http://localhost:8545');
const diamondAddress = process.env.DIAMOND_ADDRESS || '';
const complianceService = new ComplianceService(provider, diamondAddress);
const sarGenerator = new SARGenerator(
// Would need regulatory reporting service
{} as any
);
const ctrGenerator = new CTRGenerator(
// Would need regulatory reporting service
{} as any
);
const screeningService = new RealTimeScreeningService(
complianceService,
sarGenerator,
ctrGenerator
);
try {
// Get recent transactions that need screening
const recentTransactions = await prisma.transaction.findMany({
where: {
timestamp: {
gte: new Date(Date.now() - 60 * 60 * 1000), // Last hour
},
status: 'completed',
},
take: 100,
});
for (const tx of recentTransactions) {
try {
// Screen transaction
await screeningService.screenTransaction(
tx.txHash,
tx.user,
tx.user, // Simplified - would need from/to addresses
tx.amountIn || '0',
'ETH' // Simplified
);
} catch (error) {
console.error(`Error screening transaction ${tx.txHash}:`, error);
}
}
console.log(`Screened ${recentTransactions.length} transactions`);
} catch (error) {
console.error('Error in screening monitor job:', error);
throw error;
}
}
// Run if called directly
if (require.main === module) {
screeningMonitorJob()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
}

View File

@@ -0,0 +1,50 @@
/**
* Job to rotate secrets (API keys, tokens, etc.)
* Run periodically (e.g., monthly via cron)
*/
import { SecretManager } from '../services/secret-manager';
export async function rotateSecrets() {
try {
console.log('Starting secret rotation...');
// List of secrets that should be rotated
const secretsToRotate = [
'JWT_SECRET',
'FIREBASE_SERVICE_ACCOUNT',
// Add other secrets that need rotation
];
for (const secretKey of secretsToRotate) {
try {
await SecretManager.rotateSecret(secretKey);
console.log(`Rotated secret: ${secretKey}`);
} catch (error: any) {
console.error(`Failed to rotate secret ${secretKey}:`, error.message);
}
}
// Clear cache after rotation
SecretManager.clearCache();
console.log('Secret rotation completed');
} catch (error: any) {
console.error('Error during secret rotation:', error);
throw error;
}
}
// Run if called directly
if (require.main === module) {
rotateSecrets()
.then(() => {
console.log('Secret rotation job completed');
process.exit(0);
})
.catch((error) => {
console.error('Secret rotation job failed:', error);
process.exit(1);
});
}

View File

@@ -0,0 +1,79 @@
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
/**
* Authentication middleware for admin routes
*/
export interface AuthRequest extends Request {
admin?: {
id: string;
email: string;
role: string;
permissions: string[];
};
}
export const authenticateAdmin = async (
req: AuthRequest,
res: Response,
next: NextFunction
) => {
try {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'admin-secret-key') as any;
// In production, verify token against database session
req.admin = {
id: decoded.userId,
email: decoded.email,
role: decoded.role,
permissions: decoded.permissions || [],
};
next();
} catch (error: any) {
if (error.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token expired' });
}
return res.status(401).json({ error: 'Invalid token' });
}
};
/**
* Role-based access control middleware
*/
export const requireRole = (...roles: string[]) => {
return (req: AuthRequest, res: Response, next: NextFunction) => {
if (!req.admin) {
return res.status(401).json({ error: 'Unauthorized' });
}
if (!roles.includes(req.admin.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
};
/**
* Permission-based access control middleware
*/
export const requirePermission = (permission: string) => {
return (req: AuthRequest, res: Response, next: NextFunction) => {
if (!req.admin) {
return res.status(401).json({ error: 'Unauthorized' });
}
if (!req.admin.permissions.includes(permission) && req.admin.role !== 'super_admin') {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
};

View File

@@ -0,0 +1,23 @@
import rateLimit from 'express-rate-limit';
export const apiRateLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.',
standardHeaders: true,
legacyHeaders: false,
});
export const strictRateLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 10, // Strict limit for sensitive endpoints
message: 'Too many requests, please try again later.',
});
export const authRateLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // Very strict for auth endpoints
message: 'Too many authentication attempts, please try again later.',
skipSuccessfulRequests: true,
});

View File

@@ -0,0 +1,119 @@
import { Request, Response, NextFunction } from 'express';
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';
/**
* Security middleware configurations
*/
// Rate limiting configurations
export const createRateLimiter = (windowMs: number, max: number) => {
return rateLimit({
windowMs,
max,
message: 'Too many requests from this IP, please try again later.',
standardHeaders: true,
legacyHeaders: false,
});
};
// Specific rate limiters
export const authRateLimiter = createRateLimiter(15 * 60 * 1000, 5); // 5 requests per 15 minutes
export const apiRateLimiter = createRateLimiter(60 * 1000, 100); // 100 requests per minute
export const strictRateLimiter = createRateLimiter(60 * 1000, 10); // 10 requests per minute
/**
* Enhanced security headers
*/
export const securityHeaders = helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
frameguard: {
action: 'deny',
},
noSniff: true,
xssFilter: true,
});
/**
* CORS configuration based on environment
*/
export const corsConfig = {
origin: (origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) => {
const allowedOrigins = process.env.CORS_ORIGINS?.split(',') || [process.env.CORS_ORIGIN || '*'];
if (process.env.NODE_ENV === 'production') {
if (!origin || allowedOrigins.includes(origin) || allowedOrigins.includes('*')) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
} else {
callback(null, true);
}
},
credentials: true,
optionsSuccessStatus: 200,
};
/**
* Input validation middleware
*/
export const validateInput = (schema: any) => {
return (req: Request, res: Response, next: NextFunction) => {
try {
schema.parse(req.body);
next();
} catch (error: any) {
res.status(400).json({
error: 'Validation error',
details: error.errors,
});
}
};
};
/**
* Sanitize input to prevent XSS
*/
export const sanitizeInput = (req: Request, res: Response, next: NextFunction) => {
if (req.body && typeof req.body === 'object') {
const sanitize = (obj: any): any => {
if (typeof obj === 'string') {
// Remove potentially dangerous characters
return obj.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
}
if (Array.isArray(obj)) {
return obj.map(sanitize);
}
if (obj && typeof obj === 'object') {
const sanitized: any = {};
for (const key in obj) {
sanitized[key] = sanitize(obj[key]);
}
return sanitized;
}
return obj;
};
req.body = sanitize(req.body);
}
next();
};

View File

@@ -0,0 +1,32 @@
import { Request, Response, NextFunction } from 'express';
import { z, ZodSchema } from 'zod';
export function validate(schema: ZodSchema) {
return (req: Request, res: Response, next: NextFunction) => {
try {
schema.parse({
body: req.body,
query: req.query,
params: req.params
});
next();
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({
error: 'Validation failed',
details: error.errors
});
}
next(error);
}
};
}
// Common validation schemas
export const schemas = {
address: z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address'),
poolId: z.string().transform(val => BigInt(val)),
vaultId: z.string().transform(val => BigInt(val)),
amount: z.string().regex(/^\d+$/, 'Invalid amount'),
};

View File

@@ -0,0 +1,51 @@
import express from 'express';
import { AnalyticsService } from '../services/analytics';
const router = express.Router();
const analyticsService = new AnalyticsService();
/**
* GET /api/mobile/portfolio/:address
* Get optimized portfolio for mobile
*/
router.get('/portfolio/:address', async (req, res) => {
try {
const portfolio = await analyticsService.calculateUserPortfolio(req.params.address);
// Return simplified version for mobile
res.json({
totalValue: portfolio.totalValue,
poolCount: Object.keys(portfolio.poolPositions).length,
vaultCount: Object.keys(portfolio.vaultPositions).length,
});
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/mobile/pools
* Get pools (optimized for mobile)
*/
router.get('/pools', async (req, res) => {
try {
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
const pools = await prisma.pool.findMany({
where: { active: true },
take: 20,
select: {
poolId: true,
baseToken: true,
quoteToken: true,
baseReserve: true,
quoteReserve: true,
},
});
res.json(pools);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
export { router as mobileRouter };

View File

@@ -0,0 +1,270 @@
import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
const prisma = new PrismaClient();
export interface AdminUser {
id: string;
email: string;
role: string;
permissions: string[];
active: boolean;
}
export interface AdminLoginCredentials {
email: string;
password: string;
}
export interface CreateAdminUserData {
email: string;
password: string;
role?: string;
permissions?: string[];
}
export class AdminService {
private readonly JWT_SECRET = process.env.JWT_SECRET || 'admin-secret-key';
private readonly JWT_EXPIRY = '7d';
/**
* Authenticate admin user
*/
async login(credentials: AdminLoginCredentials, ipAddress?: string, userAgent?: string): Promise<{ user: AdminUser; token: string }> {
const admin = await prisma.adminUser.findUnique({
where: { email: credentials.email },
});
if (!admin || !admin.active) {
throw new Error('Invalid credentials');
}
const isValid = await bcrypt.compare(credentials.password, admin.passwordHash);
if (!isValid) {
throw new Error('Invalid credentials');
}
// Update last login
await prisma.adminUser.update({
where: { id: admin.id },
data: { lastLogin: new Date() },
});
// Create session
const token = jwt.sign(
{ userId: admin.id, email: admin.email, role: admin.role },
this.JWT_SECRET,
{ expiresIn: this.JWT_EXPIRY }
);
await prisma.adminSession.create({
data: {
adminUserId: admin.id,
token,
ipAddress,
userAgent,
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
},
});
// Log audit
await this.logAudit(admin.id, 'login', 'auth', null, { ipAddress, userAgent });
return {
user: {
id: admin.id,
email: admin.email,
role: admin.role,
permissions: admin.permissions,
active: admin.active,
},
token,
};
}
/**
* Verify admin token
*/
async verifyToken(token: string): Promise<AdminUser> {
try {
const decoded = jwt.verify(token, this.JWT_SECRET) as any;
const session = await prisma.adminSession.findUnique({
where: { token },
include: { adminUser: true },
});
if (!session || session.expiresAt < new Date() || !session.adminUser.active) {
throw new Error('Invalid or expired session');
}
return {
id: session.adminUser.id,
email: session.adminUser.email,
role: session.adminUser.role,
permissions: session.adminUser.permissions,
active: session.adminUser.active,
};
} catch (error) {
throw new Error('Invalid token');
}
}
/**
* Create admin user
*/
async createAdmin(data: CreateAdminUserData, createdBy: string): Promise<AdminUser> {
const existing = await prisma.adminUser.findUnique({
where: { email: data.email },
});
if (existing) {
throw new Error('Admin user already exists');
}
const passwordHash = await bcrypt.hash(data.password, 10);
const admin = await prisma.adminUser.create({
data: {
email: data.email,
passwordHash,
role: data.role || 'admin',
permissions: data.permissions || [],
},
});
await this.logAudit(createdBy, 'create_admin', 'admin_user', admin.id, { email: data.email, role: data.role });
return {
id: admin.id,
email: admin.email,
role: admin.role,
permissions: admin.permissions,
active: admin.active,
};
}
/**
* Get all admin users
*/
async getAdmins(): Promise<AdminUser[]> {
const admins = await prisma.adminUser.findMany({
orderBy: { createdAt: 'desc' },
});
return admins.map(admin => ({
id: admin.id,
email: admin.email,
role: admin.role,
permissions: admin.permissions,
active: admin.active,
}));
}
/**
* Update admin user
*/
async updateAdmin(id: string, data: Partial<CreateAdminUserData>, updatedBy: string): Promise<AdminUser> {
const updateData: any = {};
if (data.email) updateData.email = data.email;
if (data.role) updateData.role = data.role;
if (data.permissions) updateData.permissions = data.permissions;
if (data.password) {
updateData.passwordHash = await bcrypt.hash(data.password, 10);
}
const admin = await prisma.adminUser.update({
where: { id },
data: updateData,
});
await this.logAudit(updatedBy, 'update_admin', 'admin_user', id, updateData);
return {
id: admin.id,
email: admin.email,
role: admin.role,
permissions: admin.permissions,
active: admin.active,
};
}
/**
* Delete admin user
*/
async deleteAdmin(id: string, deletedBy: string): Promise<void> {
await this.logAudit(deletedBy, 'delete_admin', 'admin_user', id);
await prisma.adminUser.delete({ where: { id } });
}
/**
* Log audit event
*/
async logAudit(
adminUserId: string,
action: string,
resource?: string,
resourceId?: string | null,
details?: any,
ipAddress?: string
): Promise<void> {
await prisma.adminAuditLog.create({
data: {
adminUserId,
action,
resource,
resourceId,
details: details || {},
ipAddress,
},
});
}
/**
* Get audit logs
*/
async getAuditLogs(filters?: {
adminUserId?: string;
action?: string;
startDate?: Date;
endDate?: Date;
limit?: number;
}) {
const where: any = {};
if (filters?.adminUserId) where.adminUserId = filters.adminUserId;
if (filters?.action) where.action = filters.action;
if (filters?.startDate || filters?.endDate) {
where.timestamp = {};
if (filters.startDate) where.timestamp.gte = filters.startDate;
if (filters.endDate) where.timestamp.lte = filters.endDate;
}
return prisma.adminAuditLog.findMany({
where,
include: {
adminUser: {
select: {
id: true,
email: true,
role: true,
},
},
},
orderBy: { timestamp: 'desc' },
take: filters?.limit || 100,
});
}
/**
* Logout (invalidate session)
*/
async logout(token: string): Promise<void> {
await prisma.adminSession.deleteMany({
where: { token },
});
}
}

View File

@@ -0,0 +1,133 @@
import { AMLResult } from '../compliance';
export interface IAMLProvider {
name: string;
apiKey?: string;
apiUrl?: string;
enabled: boolean;
screen(address: string): Promise<AMLResult>;
checkTransaction(txHash: string, chainId: number): Promise<AMLResult>;
}
export abstract class BaseAMLProvider implements IAMLProvider {
name: string;
apiKey?: string;
apiUrl?: string;
enabled: boolean;
constructor(name: string, apiKey?: string, apiUrl?: string) {
this.name = name;
this.apiKey = apiKey;
this.apiUrl = apiUrl;
this.enabled = !!apiKey;
}
abstract screen(address: string): Promise<AMLResult>;
abstract checkTransaction(txHash: string, chainId: number): Promise<AMLResult>;
protected async makeRequest(endpoint: string, options: RequestInit = {}): Promise<any> {
if (!this.apiKey || !this.apiUrl) {
throw new Error(`${this.name} provider not configured`);
}
const response = await fetch(`${this.apiUrl}${endpoint}`, {
...options,
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
...options.headers,
},
});
if (!response.ok) {
throw new Error(`${this.name} API error: ${response.statusText}`);
}
return response.json();
}
}
export class CipherTraceProvider extends BaseAMLProvider implements IAMLProvider {
constructor(apiKey?: string, apiUrl?: string) {
super('CipherTrace', apiKey, apiUrl || 'https://api.ciphertrace.com');
}
async screen(address: string): Promise<AMLResult> {
if (!this.enabled) {
return this.mockScreen(address);
}
try {
const response = await this.makeRequest('/v1/wallet', {
method: 'POST',
body: JSON.stringify({
address,
}),
});
return {
passed: !response.sanctions && response.riskScore < 70,
riskScore: response.riskScore || 0,
sanctions: response.sanctions || false,
provider: this.name,
timestamp: Date.now(),
riskLevel: this.getRiskLevel(response.riskScore),
};
} catch (error: any) {
console.error('CipherTrace screening error:', error);
throw new Error(`CipherTrace screening failed: ${error.message}`);
}
}
async checkTransaction(txHash: string, chainId: number): Promise<AMLResult> {
if (!this.enabled) {
return {
passed: true,
riskScore: 0,
sanctions: false,
provider: this.name,
timestamp: Date.now(),
};
}
try {
const response = await this.makeRequest('/v1/transaction', {
method: 'POST',
body: JSON.stringify({
txHash,
chainId,
}),
});
return {
passed: !response.sanctions && response.riskScore < 70,
riskScore: response.riskScore || 0,
sanctions: response.sanctions || false,
provider: this.name,
timestamp: Date.now(),
riskLevel: this.getRiskLevel(response.riskScore),
};
} catch (error: any) {
throw new Error(`CipherTrace transaction check failed: ${error.message}`);
}
}
private getRiskLevel(riskScore: number): string {
if (riskScore < 30) return 'low';
if (riskScore < 70) return 'medium';
return 'high';
}
private mockScreen(address: string): AMLResult {
return {
passed: true,
riskScore: 10,
sanctions: false,
provider: this.name,
timestamp: Date.now(),
riskLevel: 'low',
};
}
}

View File

@@ -0,0 +1,88 @@
import { BaseAMLProvider, IAMLProvider } from './ciphertrace';
import { AMLResult } from '../compliance';
export class TRMProvider extends BaseAMLProvider implements IAMLProvider {
constructor(apiKey?: string, apiUrl?: string) {
super('TRM Labs', apiKey, apiUrl || 'https://api.trmlabs.com');
}
async screen(address: string): Promise<AMLResult> {
if (!this.enabled) {
return this.mockScreen(address);
}
try {
const response = await this.makeRequest('/v1/addresses', {
method: 'POST',
body: JSON.stringify({
address,
}),
});
const riskScore = response.riskScore || 0;
return {
passed: !response.isSanctioned && riskScore < 70,
riskScore,
sanctions: response.isSanctioned || false,
provider: this.name,
timestamp: Date.now(),
riskLevel: this.getRiskLevel(riskScore),
};
} catch (error: any) {
console.error('TRM Labs screening error:', error);
throw new Error(`TRM Labs screening failed: ${error.message}`);
}
}
async checkTransaction(txHash: string, chainId: number): Promise<AMLResult> {
if (!this.enabled) {
return {
passed: true,
riskScore: 0,
sanctions: false,
provider: this.name,
timestamp: Date.now(),
};
}
try {
const response = await this.makeRequest('/v1/transactions', {
method: 'POST',
body: JSON.stringify({
txHash,
chainId,
}),
});
const riskScore = response.riskScore || 0;
return {
passed: !response.isSanctioned && riskScore < 70,
riskScore,
sanctions: response.isSanctioned || false,
provider: this.name,
timestamp: Date.now(),
riskLevel: this.getRiskLevel(riskScore),
};
} catch (error: any) {
throw new Error(`TRM Labs transaction check failed: ${error.message}`);
}
}
private getRiskLevel(riskScore: number): string {
if (riskScore < 30) return 'low';
if (riskScore < 70) return 'medium';
return 'high';
}
private mockScreen(address: string): AMLResult {
return {
passed: true,
riskScore: 10,
sanctions: false,
provider: this.name,
timestamp: Date.now(),
riskLevel: 'low',
};
}
}

View File

@@ -0,0 +1,349 @@
import { PrismaClient } from '@prisma/client';
import { ethers } from 'ethers';
const prisma = new PrismaClient();
export interface PoolAnalytics {
poolId: bigint;
tvl: string;
volume24h: string;
volume7d: string;
volume30d: string;
fees24h: string;
fees7d: string;
fees30d: string;
utilizationRate: number;
timestamp: Date;
}
export interface PortfolioData {
userAddress: string;
totalValue: string;
poolPositions: Record<string, any>;
vaultPositions: Record<string, any>;
timestamp: Date;
}
export interface SystemMetrics {
totalTVL: string;
totalVolume24h: string;
totalFees24h: string;
activePools: number;
activeUsers: number;
transactionCount24h: number;
}
export class AnalyticsService {
/**
* Calculate and store pool metrics
*/
async calculatePoolMetrics(poolId: bigint): Promise<PoolAnalytics> {
const pool = await prisma.pool.findUnique({
where: { poolId },
include: {
transactions: {
where: {
timestamp: {
gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), // Last 30 days
},
},
},
},
});
if (!pool) {
throw new Error(`Pool ${poolId} not found`);
}
const now = new Date();
const day24h = new Date(now.getTime() - 24 * 60 * 60 * 1000);
const day7d = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
const day30d = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
const transactions24h = pool.transactions.filter(
(tx) => tx.timestamp >= day24h && tx.status === 'completed'
);
const transactions7d = pool.transactions.filter(
(tx) => tx.timestamp >= day7d && tx.status === 'completed'
);
const transactions30d = pool.transactions.filter(
(tx) => tx.timestamp >= day30d && tx.status === 'completed'
);
const volume24h = transactions24h.reduce(
(sum, tx) => sum + BigInt(tx.amountIn || '0'),
BigInt(0)
).toString();
const volume7d = transactions7d.reduce(
(sum, tx) => sum + BigInt(tx.amountIn || '0'),
BigInt(0)
).toString();
const volume30d = transactions30d.reduce(
(sum, tx) => sum + BigInt(tx.amountIn || '0'),
BigInt(0)
).toString();
// Calculate fees (assuming 0.3% fee)
const feeRate = 0.003;
const fees24h = (BigInt(volume24h) * BigInt(Math.floor(feeRate * 10000))) / BigInt(10000);
const fees7d = (BigInt(volume7d) * BigInt(Math.floor(feeRate * 10000))) / BigInt(10000);
const fees30d = (BigInt(volume30d) * BigInt(Math.floor(feeRate * 10000))) / BigInt(10000);
// Calculate TVL (base + quote reserves)
const tvl = (
BigInt(pool.baseReserve || '0') + BigInt(pool.quoteReserve || '0')
).toString();
// Calculate utilization rate (simplified)
const totalReserves = BigInt(pool.baseReserve || '0') + BigInt(pool.quoteReserve || '0');
const virtualReserves =
BigInt(pool.virtualBaseReserve || '0') + BigInt(pool.virtualQuoteReserve || '0');
const utilizationRate =
totalReserves > 0
? Number((totalReserves * BigInt(10000)) / virtualReserves) / 10000
: 0;
const metrics: PoolAnalytics = {
poolId,
tvl,
volume24h,
volume7d,
volume30d,
fees24h: fees24h.toString(),
fees7d: fees7d.toString(),
fees30d: fees30d.toString(),
utilizationRate,
timestamp: now,
};
// Store in database
await prisma.poolMetrics.create({
data: {
poolId,
tvl,
volume24h,
volume7d,
volume30d,
fees24h: fees24h.toString(),
fees7d: fees7d.toString(),
fees30d: fees30d.toString(),
utilizationRate,
timestamp: now,
},
});
return metrics;
}
/**
* Get pool analytics for a specific pool
*/
async getPoolAnalytics(
poolId: bigint,
startDate?: Date,
endDate?: Date
): Promise<PoolAnalytics[]> {
const where: any = { poolId };
if (startDate || endDate) {
where.timestamp = {};
if (startDate) where.timestamp.gte = startDate;
if (endDate) where.timestamp.lte = endDate;
}
const metrics = await prisma.poolMetrics.findMany({
where,
orderBy: { timestamp: 'desc' },
take: 1000,
});
return metrics.map((m) => ({
poolId: m.poolId,
tvl: m.tvl,
volume24h: m.volume24h,
volume7d: m.volume7d,
volume30d: m.volume30d,
fees24h: m.fees24h,
fees7d: m.fees7d,
fees30d: m.fees30d,
utilizationRate: m.utilizationRate,
timestamp: m.timestamp,
}));
}
/**
* Calculate user portfolio
*/
async calculateUserPortfolio(userAddress: string): Promise<PortfolioData> {
// Get pool positions
const lpPositions = await prisma.lPPosition.findMany({
where: { user: userAddress },
include: { pool: true },
});
// Get vault positions
const deposits = await prisma.deposit.findMany({
where: { user: userAddress },
include: { vault: true },
});
const poolPositions: Record<string, any> = {};
let totalPoolValue = BigInt(0);
for (const position of lpPositions) {
const pool = position.pool;
const poolValue =
(BigInt(position.lpShares) * BigInt(pool.baseReserve || '0')) /
BigInt(pool.totalSupply || '1');
totalPoolValue += poolValue;
poolPositions[pool.poolId.toString()] = {
poolId: pool.poolId.toString(),
lpShares: position.lpShares,
value: poolValue.toString(),
};
}
const vaultPositions: Record<string, any> = {};
let totalVaultValue = BigInt(0);
for (const deposit of deposits) {
const vault = deposit.vault;
const vaultValue = BigInt(deposit.shares);
totalVaultValue += vaultValue;
vaultPositions[vault.vaultId.toString()] = {
vaultId: vault.vaultId.toString(),
shares: deposit.shares,
value: vaultValue.toString(),
};
}
const totalValue = (totalPoolValue + totalVaultValue).toString();
const portfolio: PortfolioData = {
userAddress,
totalValue,
poolPositions,
vaultPositions,
timestamp: new Date(),
};
// Store in database
await prisma.userPortfolio.create({
data: {
userAddress,
totalValue,
poolPositions: poolPositions as any,
vaultPositions: vaultPositions as any,
timestamp: new Date(),
},
});
return portfolio;
}
/**
* Get user portfolio history
*/
async getUserPortfolioHistory(
userAddress: string,
startDate?: Date,
endDate?: Date
): Promise<PortfolioData[]> {
const where: any = { userAddress };
if (startDate || endDate) {
where.timestamp = {};
if (startDate) where.timestamp.gte = startDate;
if (endDate) where.timestamp.lte = endDate;
}
const portfolios = await prisma.userPortfolio.findMany({
where,
orderBy: { timestamp: 'desc' },
take: 1000,
});
return portfolios.map((p) => ({
userAddress: p.userAddress,
totalValue: p.totalValue,
poolPositions: p.poolPositions as any,
vaultPositions: p.vaultPositions as any,
timestamp: p.timestamp,
}));
}
/**
* Calculate system-wide metrics
*/
async calculateSystemMetrics(): Promise<SystemMetrics> {
const now = new Date();
const day24h = new Date(now.getTime() - 24 * 60 * 60 * 1000);
// Get all pools
const pools = await prisma.pool.findMany({
where: { active: true },
});
// Calculate total TVL
const totalTVL = pools.reduce(
(sum, pool) =>
sum + BigInt(pool.baseReserve || '0') + BigInt(pool.quoteReserve || '0'),
BigInt(0)
).toString();
// Get transactions in last 24h
const transactions24h = await prisma.transaction.findMany({
where: {
timestamp: { gte: day24h },
status: 'completed',
},
});
const totalVolume24h = transactions24h.reduce(
(sum, tx) => sum + BigInt(tx.amountIn || '0'),
BigInt(0)
).toString();
const feeRate = 0.003;
const totalFees24h = (
(BigInt(totalVolume24h) * BigInt(Math.floor(feeRate * 10000))) /
BigInt(10000)
).toString();
// Get active users (users with transactions in last 24h)
const activeUsers = new Set(transactions24h.map((tx) => tx.user)).size;
return {
totalTVL,
totalVolume24h,
totalFees24h,
activePools: pools.length,
activeUsers,
transactionCount24h: transactions24h.length,
};
}
/**
* Get transaction analytics
*/
async getTransactionAnalytics(
poolId?: bigint,
startDate?: Date,
endDate?: Date
): Promise<any[]> {
const where: any = {};
if (poolId) where.poolId = poolId;
if (startDate || endDate) {
where.timestamp = {};
if (startDate) where.timestamp.gte = startDate;
if (endDate) where.timestamp.lte = endDate;
}
const analytics = await prisma.transactionAnalytics.findMany({
where,
orderBy: { timestamp: 'desc' },
take: 1000,
});
return analytics;
}
}

View File

@@ -0,0 +1,152 @@
export interface BankIntegration {
name: string;
type: 'swift' | 'iso20022' | 'api';
endpoint: string;
apiKey?: string;
}
export interface SWIFTMessage {
messageType: string;
senderBIC: string;
receiverBIC: string;
amount: string;
currency: string;
reference: string;
details: any;
}
export interface ISO20022Message {
messageType: string; // pacs.008, camt.053, etc.
sender: string;
receiver: string;
document: any;
}
export class BankService {
private integrations: Map<string, BankIntegration> = new Map();
constructor() {
this.initializeIntegrations();
}
private initializeIntegrations() {
// SWIFT integration - production-ready structure
this.integrations.set('swift', {
name: 'SWIFT Network',
type: 'swift',
endpoint: process.env.SWIFT_ENDPOINT || 'https://swift.com/api',
apiKey: process.env.SWIFT_API_KEY,
enabled: !!process.env.SWIFT_API_KEY
});
// ISO 20022 integration - production-ready structure
this.integrations.set('iso20022', {
name: 'ISO 20022 Messaging Bridge',
type: 'iso20022',
endpoint: process.env.ISO20022_ENDPOINT || 'https://iso20022.example.com/api',
apiKey: process.env.ISO20022_API_KEY,
enabled: !!process.env.ISO20022_API_KEY
});
// Bank API integration structure
this.integrations.set('bankapi', {
name: 'Bank API Direct',
type: 'api',
endpoint: process.env.BANK_API_ENDPOINT || '',
apiKey: process.env.BANK_API_KEY,
enabled: !!process.env.BANK_API_KEY
});
}
async sendSWIFTMessage(message: SWIFTMessage): Promise<string> {
const integration = this.integrations.get('swift');
if (!integration) {
throw new Error('SWIFT integration not configured');
}
if (integration.enabled && integration.apiKey) {
// Real SWIFT API call would go here
return await this._sendSWIFT(integration, message);
}
// Mock implementation
const messageRef = `SWIFT-${Date.now()}`;
console.log('SWIFT Message:', message);
return messageRef;
}
private async _sendSWIFT(integration: BankIntegration, message: SWIFTMessage): Promise<string> {
// Production implementation:
/*
const response = await fetch(`${integration.endpoint}/messages`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${integration.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(message)
});
const data = await response.json();
return data.messageRef;
*/
return `SWIFT-${Date.now()}`;
}
async sendISO20022Message(message: ISO20022Message): Promise<string> {
const integration = this.integrations.get('iso20022');
if (!integration) {
throw new Error('ISO 20022 integration not configured');
}
if (integration.enabled && integration.apiKey) {
return await this._sendISO20022(integration, message);
}
const messageId = `ISO20022-${Date.now()}`;
return messageId;
}
private async _sendISO20022(integration: BankIntegration, message: ISO20022Message): Promise<string> {
// Production implementation would send ISO 20022 formatted message
return `ISO20022-${Date.now()}`;
}
async convertToSWIFT(iso20022Message: ISO20022Message): Promise<SWIFTMessage> {
// Convert ISO 20022 message to SWIFT format
return {
messageType: 'MT103',
senderBIC: iso20022Message.sender,
receiverBIC: iso20022Message.receiver,
amount: iso20022Message.document.amount || '0',
currency: iso20022Message.document.currency || 'USD',
reference: `REF-${Date.now()}`,
details: iso20022Message.document,
};
}
async getBankStatement(accountId: string, dateFrom: string, dateTo: string): Promise<any> {
// Production implementation would fetch from bank API
return {
accountId,
period: { from: dateFrom, to: dateTo },
transactions: [],
};
}
async processPayment(amount: string, currency: string, recipientBIC: string, details: any): Promise<string> {
// Process payment via bank integration
const isoMessage: ISO20022Message = {
messageType: 'pacs.008',
sender: process.env.BANK_BIC || 'ASLEGB22XXX',
receiver: recipientBIC,
document: {
amount,
currency,
...details
}
};
return await this.sendISO20022Message(isoMessage);
}
}

View File

@@ -0,0 +1,159 @@
/**
* Base interface for bridge adapters
* Supports different blockchain architectures (EVM, Solana, Cosmos)
*/
export interface BridgeAdapter {
chainId: string;
chainType: 'evm' | 'solana' | 'cosmos';
name: string;
/**
* Send cross-chain message
*/
sendMessage(targetChain: string, message: any): Promise<string>;
/**
* Receive cross-chain message
*/
receiveMessage(messageId: string): Promise<any>;
/**
* Get bridge status
*/
getStatus(): Promise<BridgeStatus>;
}
export interface BridgeStatus {
connected: boolean;
lastBlock: number;
pendingMessages: number;
error?: string;
}
export interface CrossChainMessage {
id: string;
sourceChain: string;
targetChain: string;
payload: any;
timestamp: number;
status: 'pending' | 'confirmed' | 'failed';
}
/**
* Bridge adapter factory
*/
export class BridgeAdapterFactory {
static createAdapter(chainId: string, chainType: 'evm' | 'solana' | 'cosmos'): BridgeAdapter {
switch (chainType) {
case 'evm':
return new EVMBridgeAdapter(chainId);
case 'solana':
return new SolanaBridgeAdapter(chainId);
case 'cosmos':
return new CosmosBridgeAdapter(chainId);
default:
throw new Error(`Unsupported chain type: ${chainType}`);
}
}
}
/**
* EVM Bridge Adapter (uses CCIP)
*/
class EVMBridgeAdapter implements BridgeAdapter {
chainId: string;
chainType: 'evm' = 'evm';
name: string;
constructor(chainId: string) {
this.chainId = chainId;
this.name = `EVM-${chainId}`;
}
async sendMessage(targetChain: string, message: any): Promise<string> {
// Use CCIP for EVM chains
// Implementation would use CCIPFacet
return `evm_message_${Date.now()}`;
}
async receiveMessage(messageId: string): Promise<any> {
// Receive via CCIP
return {};
}
async getStatus(): Promise<BridgeStatus> {
return {
connected: true,
lastBlock: 0,
pendingMessages: 0,
};
}
}
/**
* Solana Bridge Adapter (uses Wormhole or similar)
*/
class SolanaBridgeAdapter implements BridgeAdapter {
chainId: string;
chainType: 'solana' = 'solana';
name: string;
constructor(chainId: string) {
this.chainId = chainId;
this.name = `Solana-${chainId}`;
}
async sendMessage(targetChain: string, message: any): Promise<string> {
// Use Wormhole or similar bridge for Solana
// Implementation would interact with Solana program
return `solana_message_${Date.now()}`;
}
async receiveMessage(messageId: string): Promise<any> {
// Receive via Wormhole
return {};
}
async getStatus(): Promise<BridgeStatus> {
return {
connected: true,
lastBlock: 0,
pendingMessages: 0,
};
}
}
/**
* Cosmos Bridge Adapter (uses IBC)
*/
class CosmosBridgeAdapter implements BridgeAdapter {
chainId: string;
chainType: 'cosmos' = 'cosmos';
name: string;
constructor(chainId: string) {
this.chainId = chainId;
this.name = `Cosmos-${chainId}`;
}
async sendMessage(targetChain: string, message: any): Promise<string> {
// Use IBC (Inter-Blockchain Communication) for Cosmos chains
// Implementation would use Cosmos SDK IBC module
return `cosmos_message_${Date.now()}`;
}
async receiveMessage(messageId: string): Promise<any> {
// Receive via IBC
return {};
}
async getStatus(): Promise<BridgeStatus> {
return {
connected: true,
lastBlock: 0,
pendingMessages: 0,
};
}
}

View File

@@ -0,0 +1,191 @@
import { ethers } from 'ethers';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export interface CCIPMessage {
messageId: string;
sourceChainId: number;
targetChainId: number;
messageType: string;
payload: any;
timestamp: number;
status: 'pending' | 'delivered' | 'failed';
}
// Chain selector mappings for CCIP
const CHAIN_SELECTORS: Record<number, bigint> = {
1: BigInt('5009297550715157269'), // Ethereum Mainnet
137: BigInt('4051577828743386545'), // Polygon
42161: BigInt('4949039107694359620'), // Arbitrum
10: BigInt('3734403246176062136'), // Optimism
56: BigInt('11344663589394136015'), // BSC
43114: BigInt('6433500567565415381'), // Avalanche
8453: BigInt('15971525489660198786'), // Base
11155111: BigInt('16015286601757825753'), // Sepolia
};
export class CCIPService {
private provider: ethers.Provider;
private diamondAddress: string;
private chainProviders: Map<number, ethers.Provider> = new Map();
constructor(provider: ethers.Provider, diamondAddress: string) {
this.provider = provider;
this.diamondAddress = diamondAddress;
this.initializeChainProviders();
}
private initializeChainProviders(): void {
// Initialize RPC providers for all supported chains
const rpcUrls: Record<number, string> = {
1: process.env.ETHEREUM_RPC_URL || 'https://eth.llamarpc.com',
137: process.env.POLYGON_RPC_URL || 'https://polygon.llamarpc.com',
42161: process.env.ARBITRUM_RPC_URL || 'https://arb1.arbitrum.io/rpc',
10: process.env.OPTIMISM_RPC_URL || 'https://mainnet.optimism.io',
56: process.env.BSC_RPC_URL || 'https://bsc-dataseed1.binance.org',
43114: process.env.AVALANCHE_RPC_URL || 'https://api.avax.network/ext/bc/C/rpc',
8453: process.env.BASE_RPC_URL || 'https://mainnet.base.org',
11155111: process.env.SEPOLIA_RPC_URL || 'https://rpc.sepolia.org',
};
for (const [chainId, rpcUrl] of Object.entries(rpcUrls)) {
try {
this.chainProviders.set(Number(chainId), new ethers.JsonRpcProvider(rpcUrl));
} catch (error) {
console.error(`Failed to initialize provider for chain ${chainId}:`, error);
}
}
}
getChainSelector(chainId: number): bigint | null {
return CHAIN_SELECTORS[chainId] || null;
}
getProvider(chainId: number): ethers.Provider | null {
return this.chainProviders.get(chainId) || null;
}
async trackMessage(
messageId: string,
sourceChainId: number,
targetChainId: number,
messageType: string,
payload: any
): Promise<void> {
// Store in database
await prisma.ccipMessage.create({
data: {
messageId,
sourceChainId: BigInt(sourceChainId),
targetChainId: BigInt(targetChainId),
messageType,
payload: payload as any,
status: 'pending',
timestamp: new Date()
}
});
}
async updateMessageStatus(messageId: string, status: 'delivered' | 'failed', error?: string): Promise<void> {
await prisma.ccipMessage.update({
where: { messageId },
data: {
status,
deliveredAt: status === 'delivered' ? new Date() : undefined,
error
}
});
}
async getMessage(messageId: string): Promise<CCIPMessage | null> {
const msg = await prisma.ccipMessage.findUnique({
where: { messageId }
});
if (!msg) return null;
return {
messageId: msg.messageId,
sourceChainId: Number(msg.sourceChainId),
targetChainId: Number(msg.targetChainId),
messageType: msg.messageType,
payload: msg.payload as any,
timestamp: msg.timestamp.getTime(),
status: msg.status as any
};
}
async getAllMessages(): Promise<CCIPMessage[]> {
const messages = await prisma.ccipMessage.findMany({
orderBy: { timestamp: 'desc' },
take: 100
});
return messages.map(msg => ({
messageId: msg.messageId,
sourceChainId: Number(msg.sourceChainId),
targetChainId: Number(msg.targetChainId),
messageType: msg.messageType,
payload: msg.payload as any,
timestamp: msg.timestamp.getTime(),
status: msg.status as any
}));
}
async getMessagesByChain(chainId: number): Promise<CCIPMessage[]> {
const messages = await prisma.ccipMessage.findMany({
where: {
OR: [
{ sourceChainId: BigInt(chainId) },
{ targetChainId: BigInt(chainId) }
]
},
orderBy: { timestamp: 'desc' }
});
return messages.map(msg => ({
messageId: msg.messageId,
sourceChainId: Number(msg.sourceChainId),
targetChainId: Number(msg.targetChainId),
messageType: msg.messageType,
payload: msg.payload as any,
timestamp: msg.timestamp.getTime(),
status: msg.status as any
}));
}
async monitorCrossChainState(): Promise<void> {
// Monitor pending messages
const pendingMessages = await prisma.cCIPMessage.findMany({
where: {
status: 'pending',
timestamp: {
lt: new Date(Date.now() - 5 * 60 * 1000) // Older than 5 minutes
}
}
});
for (const msg of pendingMessages) {
// In production, check Chainlink CCIP explorer or contract events
// For now, mark as failed if pending too long
if (Date.now() - msg.timestamp.getTime() > 30 * 60 * 1000) {
await this.updateMessageStatus(msg.messageId, 'failed', 'Message timeout');
}
}
}
async syncLiquidityState(poolId: number, chainId: number): Promise<void> {
// In production, this would:
// 1. Fetch pool state from source chain
// 2. Send sync message to target chains
// 3. Update local database
console.log(`Syncing liquidity state for pool ${poolId} on chain ${chainId}`);
}
async syncVaultBalance(vaultId: number, chainId: number): Promise<void> {
// Similar to liquidity sync but for vaults
console.log(`Syncing vault balance for vault ${vaultId} on chain ${chainId}`);
}
}

View File

@@ -0,0 +1,195 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export interface ComplianceMetrics {
totalKYCVerified: number;
totalAMLVerified: number;
totalSanctionsDetected: number;
totalSARsGenerated: number;
totalCTRsGenerated: number;
averageRiskScore: number;
complianceRate: number;
screeningVolume24h: number;
}
export interface ComplianceTrends {
date: string;
kycVerified: number;
amlVerified: number;
sanctionsDetected: number;
sarsGenerated: number;
ctrsGenerated: number;
}
export class ComplianceAnalyticsService {
/**
* Calculate compliance metrics
*/
async calculateMetrics(): Promise<ComplianceMetrics> {
const now = new Date();
const day24h = new Date(now.getTime() - 24 * 60 * 60 * 1000);
const [
totalKYCVerified,
totalAMLVerified,
totalSanctionsDetected,
totalSARs,
totalCTRs,
screeningResults24h,
avgRiskScore,
] = await Promise.all([
prisma.complianceRecord.count({
where: { kycVerified: true },
}),
prisma.complianceRecord.count({
where: { amlVerified: true },
}),
prisma.screeningResult.count({
where: { sanctions: true },
}),
prisma.sARReport.count(),
prisma.cTRReport.count(),
prisma.screeningResult.findMany({
where: {
timestamp: { gte: day24h },
},
}),
prisma.screeningResult.aggregate({
_avg: { riskScore: true },
}),
]);
const totalUsers = await prisma.complianceRecord.count();
const complianceRate =
totalUsers > 0
? ((totalKYCVerified + totalAMLVerified) / (totalUsers * 2)) * 100
: 0;
return {
totalKYCVerified,
totalAMLVerified,
totalSanctionsDetected,
totalSARsGenerated: totalSARs,
totalCTRsGenerated: totalCTRs,
averageRiskScore: avgRiskScore._avg.riskScore || 0,
complianceRate,
screeningVolume24h: screeningResults24h.length,
};
}
/**
* Get compliance trends over time
*/
async getTrends(
startDate: Date,
endDate: Date
): Promise<ComplianceTrends[]> {
const trends: ComplianceTrends[] = [];
const currentDate = new Date(startDate);
while (currentDate <= endDate) {
const dayStart = new Date(currentDate);
dayStart.setHours(0, 0, 0, 0);
const dayEnd = new Date(currentDate);
dayEnd.setHours(23, 59, 59, 999);
const [kycVerified, amlVerified, sanctionsDetected, sarsGenerated, ctrsGenerated] =
await Promise.all([
prisma.complianceRecord.count({
where: {
kycVerified: true,
lastKYCUpdate: {
gte: dayStart,
lte: dayEnd,
},
},
}),
prisma.complianceRecord.count({
where: {
amlVerified: true,
lastAMLUpdate: {
gte: dayStart,
lte: dayEnd,
},
},
}),
prisma.screeningResult.count({
where: {
sanctions: true,
timestamp: {
gte: dayStart,
lte: dayEnd,
},
},
}),
prisma.sARReport.count({
where: {
createdAt: {
gte: dayStart,
lte: dayEnd,
},
},
}),
prisma.cTRReport.count({
where: {
createdAt: {
gte: dayStart,
lte: dayEnd,
},
},
}),
]);
trends.push({
date: dayStart.toISOString().split('T')[0],
kycVerified,
amlVerified,
sanctionsDetected,
sarsGenerated,
ctrsGenerated,
});
currentDate.setDate(currentDate.getDate() + 1);
}
return trends;
}
/**
* Get provider performance
*/
async getProviderPerformance() {
const kycProviders = await prisma.complianceRecord.groupBy({
by: ['kycProvider'],
_count: {
kycProvider: true,
},
where: {
kycProvider: { not: null },
},
});
const amlProviders = await prisma.complianceRecord.groupBy({
by: ['amlProvider'],
_count: {
amlProvider: true,
},
where: {
amlProvider: { not: null },
},
});
return {
kycProviders: kycProviders.map((p) => ({
provider: p.kycProvider,
count: p._count.kycProvider,
})),
amlProviders: amlProviders.map((p) => ({
provider: p.amlProvider,
count: p._count.amlProvider,
})),
};
}
}

View File

@@ -0,0 +1,231 @@
import { PrismaClient } from '@prisma/client';
import { ComplianceService } from './compliance';
const prisma = new PrismaClient();
export interface WorkflowStep {
id: string;
name: string;
type: 'kyc' | 'aml' | 'sanctions' | 'approval' | 'notification';
config: any;
required: boolean;
order: number;
}
export interface Workflow {
id: string;
name: string;
description: string;
steps: WorkflowStep[];
active: boolean;
createdAt: Date;
}
export interface WorkflowExecution {
id: string;
workflowId: string;
userAddress: string;
currentStep: number;
status: 'pending' | 'in_progress' | 'completed' | 'failed' | 'rejected';
results: Record<string, any>;
createdAt: Date;
updatedAt: Date;
}
export class ComplianceWorkflowService {
private complianceService: ComplianceService;
constructor(complianceService: ComplianceService) {
this.complianceService = complianceService;
}
/**
* Create workflow template
*/
async createWorkflow(
name: string,
description: string,
steps: Omit<WorkflowStep, 'id'>[]
): Promise<Workflow> {
const workflow = await prisma.complianceWorkflow.create({
data: {
name,
description,
steps: steps.map((step, index) => ({
...step,
id: `step_${index}`,
order: step.order || index,
})) as any,
active: true,
},
});
return {
id: workflow.id,
name: workflow.name,
description: workflow.description,
steps: (workflow.steps as any[]).map((s) => ({
id: s.id,
name: s.name,
type: s.type,
config: s.config,
required: s.required,
order: s.order,
})),
active: workflow.active,
createdAt: workflow.createdAt,
};
}
/**
* Start workflow execution
*/
async startWorkflow(
workflowId: string,
userAddress: string
): Promise<WorkflowExecution> {
const workflow = await prisma.complianceWorkflow.findUnique({
where: { id: workflowId },
});
if (!workflow) {
throw new Error('Workflow not found');
}
if (!workflow.active) {
throw new Error('Workflow is not active');
}
const execution = await prisma.workflowExecution.create({
data: {
workflowId,
userAddress,
currentStep: 0,
status: 'in_progress',
results: {},
},
});
// Start first step
await this.executeStep(execution.id);
return {
id: execution.id,
workflowId: execution.workflowId,
userAddress: execution.userAddress,
currentStep: execution.currentStep,
status: execution.status as any,
results: execution.results as any,
createdAt: execution.createdAt,
updatedAt: execution.updatedAt,
};
}
/**
* Execute workflow step
*/
private async executeStep(executionId: string): Promise<void> {
const execution = await prisma.workflowExecution.findUnique({
where: { id: executionId },
include: { workflow: true },
});
if (!execution) {
throw new Error('Execution not found');
}
const workflow = execution.workflow;
const steps = workflow.steps as any[];
const currentStepData = steps[execution.currentStep];
if (!currentStepData) {
// Workflow completed
await prisma.workflowExecution.update({
where: { id: executionId },
data: {
status: 'completed',
},
});
return;
}
try {
let result: any;
switch (currentStepData.type) {
case 'kyc':
result = await this.complianceService.verifyKYC(
execution.userAddress,
currentStepData.config?.provider || 'default'
);
break;
case 'aml':
result = await this.complianceService.verifyAML(
execution.userAddress,
currentStepData.config?.provider || 'default'
);
break;
case 'sanctions':
// Sanctions check would go here
result = { passed: true };
break;
case 'approval':
// Manual approval step - wait for admin
result = { pending: true };
break;
case 'notification':
// Send notification
result = { sent: true };
break;
}
// Update execution with step result
const results = execution.results as any;
results[currentStepData.id] = result;
await prisma.workflowExecution.update({
where: { id: executionId },
data: {
results,
currentStep: execution.currentStep + 1,
},
});
// Continue to next step if not approval
if (currentStepData.type !== 'approval') {
await this.executeStep(executionId);
}
} catch (error: any) {
await prisma.workflowExecution.update({
where: { id: executionId },
data: {
status: 'failed',
},
});
throw error;
}
}
/**
* Get workflow execution status
*/
async getExecution(executionId: string): Promise<WorkflowExecution | null> {
const execution = await prisma.workflowExecution.findUnique({
where: { id: executionId },
});
if (!execution) return null;
return {
id: execution.id,
workflowId: execution.workflowId,
userAddress: execution.userAddress,
currentStep: execution.currentStep,
status: execution.status as any,
results: execution.results as any,
createdAt: execution.createdAt,
updatedAt: execution.updatedAt,
};
}
}

View File

@@ -0,0 +1,509 @@
import { ethers } from 'ethers';
import { PrismaClient } from '@prisma/client';
import { JumioProvider } from './kyc-providers/jumio';
import { VeriffProvider } from './kyc-providers/veriff';
import { PersonaProvider } from './kyc-providers/persona';
import { CipherTraceProvider } from './aml-providers/ciphertrace';
import { TRMProvider } from './aml-providers/trm';
const prisma = new PrismaClient();
export interface KYCResult {
verified: boolean;
tier: number;
provider: string;
timestamp: number;
kycId?: string;
}
export interface AMLResult {
passed: boolean;
riskScore: number;
sanctions: boolean;
provider: string;
timestamp: number;
riskLevel?: string;
}
export interface OFACCheck {
sanctioned: boolean;
listType: string;
timestamp: number;
details?: string;
}
export interface KYCProvider {
name: string;
apiKey?: string;
apiUrl?: string;
enabled: boolean;
}
export interface AMLProvider {
name: string;
apiKey?: string;
apiUrl?: string;
enabled: boolean;
}
export class ComplianceService {
private provider: ethers.Provider;
private diamondAddress: string;
private kycProviders: Map<string, KYCProvider> = new Map();
private amlProviders: Map<string, AMLProvider> = new Map();
private ofacCache: Map<string, { result: OFACCheck; timestamp: number }> = new Map();
private readonly OFAC_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
constructor(provider: ethers.Provider, diamondAddress: string) {
this.provider = provider;
this.diamondAddress = diamondAddress;
// Initialize default providers (mock-ready structure)
this.kycProviders.set('sumsub', {
name: 'Sumsub',
apiKey: process.env.SUMSUB_API_KEY,
apiUrl: process.env.SUMSUB_API_URL || 'https://api.sumsub.com',
enabled: !!process.env.SUMSUB_API_KEY
});
this.kycProviders.set('onfido', {
name: 'Onfido',
apiKey: process.env.ONFIDO_API_KEY,
apiUrl: process.env.ONFIDO_API_URL || 'https://api.onfido.com',
enabled: !!process.env.ONFIDO_API_KEY
});
this.kycProviders.set('jumio', {
name: 'Jumio',
apiKey: process.env.JUMIO_API_KEY,
apiUrl: process.env.JUMIO_API_URL || 'https://netverify.com/api/v4',
enabled: !!process.env.JUMIO_API_KEY
});
this.kycProviders.set('veriff', {
name: 'Veriff',
apiKey: process.env.VERIFF_API_KEY,
apiUrl: process.env.VERIFF_API_URL || 'https://station.veriff.com',
enabled: !!process.env.VERIFF_API_KEY
});
this.kycProviders.set('persona', {
name: 'Persona',
apiKey: process.env.PERSONA_API_KEY,
apiUrl: process.env.PERSONA_API_URL || 'https://withpersona.com/api/v1',
enabled: !!process.env.PERSONA_API_KEY
});
this.amlProviders.set('chainalysis', {
name: 'Chainalysis',
apiKey: process.env.CHAINALYSIS_API_KEY,
apiUrl: process.env.CHAINALYSIS_API_URL || 'https://api.chainalysis.com',
enabled: !!process.env.CHAINALYSIS_API_KEY
});
this.amlProviders.set('elliptic', {
name: 'Elliptic',
apiKey: process.env.ELLIPTIC_API_KEY,
apiUrl: process.env.ELLIPTIC_API_URL || 'https://api.elliptic.co',
enabled: !!process.env.ELLIPTIC_API_KEY
});
this.amlProviders.set('ciphertrace', {
name: 'CipherTrace',
apiKey: process.env.CIPHERTRACE_API_KEY,
apiUrl: process.env.CIPHERTRACE_API_URL || 'https://api.ciphertrace.com',
enabled: !!process.env.CIPHERTRACE_API_KEY
});
this.amlProviders.set('trm', {
name: 'TRM Labs',
apiKey: process.env.TRM_API_KEY,
apiUrl: process.env.TRM_API_URL || 'https://api.trmlabs.com',
enabled: !!process.env.TRM_API_KEY
});
}
async verifyKYC(userAddress: string, providerName: string = 'default'): Promise<KYCResult> {
// Check database first
const existing = await prisma.complianceRecord.findUnique({
where: { userAddress }
});
if (existing?.kycVerified && existing.lastKYCUpdate) {
const age = Date.now() - existing.lastKYCUpdate.getTime();
// Return cached if less than 90 days old
if (age < 90 * 24 * 60 * 60 * 1000) {
return {
verified: existing.kycVerified,
tier: 1,
provider: existing.kycProvider || 'cached',
timestamp: existing.lastKYCUpdate.getTime()
};
}
}
const kycProvider = this.kycProviders.get(providerName);
let result: KYCResult;
if (kycProvider?.enabled && kycProvider.apiKey) {
// Real integration would call provider API here
// For now, using production-ready mock structure
result = await this._callKYCProvider(kycProvider, userAddress);
} else {
// Mock implementation for development
result = {
verified: true,
tier: 1,
provider: providerName || 'mock',
timestamp: Date.now(),
};
}
// Update database
await prisma.complianceRecord.upsert({
where: { userAddress },
update: {
kycVerified: result.verified,
kycProvider: result.provider,
lastKYCUpdate: new Date(result.timestamp)
},
create: {
userAddress,
complianceMode: 'Regulated',
kycVerified: result.verified,
kycProvider: result.provider,
lastKYCUpdate: new Date(result.timestamp)
}
});
return result;
}
private async _callKYCProvider(provider: KYCProvider, userAddress: string): Promise<KYCResult> {
// Use provider-specific implementations
let providerInstance;
switch (provider.name.toLowerCase()) {
case 'jumio':
providerInstance = new JumioProvider(provider.apiKey, provider.apiUrl);
break;
case 'veriff':
providerInstance = new VeriffProvider(provider.apiKey, provider.apiUrl);
break;
case 'persona':
providerInstance = new PersonaProvider(provider.apiKey, provider.apiUrl);
break;
case 'sumsub':
case 'onfido':
default:
// For Sumsub and Onfido, use generic implementation
// Production implementation would make HTTP request to provider API
const response = await fetch(`${provider.apiUrl}/v1/verify`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${provider.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ address: userAddress })
});
const data = await response.json();
return {
verified: data.status === 'approved',
tier: data.tier || 1,
provider: provider.name,
timestamp: Date.now(),
kycId: data.kycId
};
}
if (providerInstance) {
return await providerInstance.verify(userAddress);
}
// Fallback mock response
return {
verified: true,
tier: 1,
provider: provider.name,
timestamp: Date.now(),
kycId: `KYC-${Date.now()}`
};
}
async verifyAML(userAddress: string, providerName: string = 'default'): Promise<AMLResult> {
// Check database first
const existing = await prisma.complianceRecord.findUnique({
where: { userAddress }
});
if (existing?.amlVerified && existing.lastAMLUpdate) {
const age = Date.now() - existing.lastAMLUpdate.getTime();
// Return cached if less than 30 days old
if (age < 30 * 24 * 60 * 60 * 1000) {
return {
passed: existing.amlVerified,
riskScore: 0.1,
sanctions: false,
provider: existing.amlProvider || 'cached',
timestamp: existing.lastAMLUpdate.getTime()
};
}
}
const amlProvider = this.amlProviders.get(providerName);
let result: AMLResult;
if (amlProvider?.enabled && amlProvider.apiKey) {
result = await this._callAMLProvider(amlProvider, userAddress);
} else {
// Mock implementation
result = {
passed: true,
riskScore: 0.1,
sanctions: false,
provider: providerName || 'mock',
timestamp: Date.now(),
riskLevel: 'low'
};
}
// Update database
await prisma.complianceRecord.upsert({
where: { userAddress },
update: {
amlVerified: result.passed,
amlProvider: result.provider,
lastAMLUpdate: new Date(result.timestamp)
},
create: {
userAddress,
complianceMode: 'Regulated',
amlVerified: result.passed,
amlProvider: result.provider,
lastAMLUpdate: new Date(result.timestamp)
}
});
return result;
}
private async _callAMLProvider(provider: AMLProvider, userAddress: string): Promise<AMLResult> {
// Use provider-specific implementations
let providerInstance;
switch (provider.name.toLowerCase()) {
case 'ciphertrace':
providerInstance = new CipherTraceProvider(provider.apiKey, provider.apiUrl);
break;
case 'trm labs':
case 'trm':
providerInstance = new TRMProvider(provider.apiKey, provider.apiUrl);
break;
case 'chainalysis':
case 'elliptic':
default:
// For Chainalysis and Elliptic, use generic implementation
// Production implementation would make HTTP request
const response = await fetch(`${provider.apiUrl}/v1/screen`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${provider.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ address: userAddress })
});
const data = await response.json();
return {
passed: !data.sanctions && (data.riskScore || 0) < 70,
riskScore: data.riskScore || 0,
sanctions: data.sanctions || false,
provider: provider.name,
timestamp: Date.now(),
riskLevel: (data.riskScore || 0) < 30 ? 'low' : (data.riskScore || 0) < 70 ? 'medium' : 'high'
};
}
if (providerInstance) {
return await providerInstance.screen(userAddress);
}
// Fallback mock response
return {
passed: true,
riskScore: 0.1,
sanctions: false,
provider: provider.name,
timestamp: Date.now(),
riskLevel: 'low'
};
}
async checkOFACSanctions(userAddress: string): Promise<OFACCheck> {
// Check cache first
const cached = this.ofacCache.get(userAddress);
if (cached && (Date.now() - cached.timestamp) < this.OFAC_CACHE_TTL) {
return cached.result;
}
// In production, this would query OFAC API or database
// Example: https://ofac-api.com or local database
let result: OFACCheck;
if (process.env.OFAC_API_KEY) {
// Real OFAC API call would go here
result = await this._checkOFACAPI(userAddress);
} else {
// Mock implementation with database lookup structure
result = {
sanctioned: false,
listType: 'SDN',
timestamp: Date.now(),
details: 'Address not found in sanctions lists'
};
}
// Cache result
this.ofacCache.set(userAddress, { result, timestamp: Date.now() });
return result;
}
private async _checkOFACAPI(userAddress: string): Promise<OFACCheck> {
// Production implementation would query OFAC API
/*
const response = await fetch(`https://ofac-api.com/v1/check`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.OFAC_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ address: userAddress })
});
const data = await response.json();
return {
sanctioned: data.sanctioned || false,
listType: data.listType || 'SDN',
timestamp: Date.now(),
details: data.details
};
*/
return {
sanctioned: false,
listType: 'SDN',
timestamp: Date.now()
};
}
async generateTravelRuleMessage(
from: string,
to: string,
amount: string,
asset: string
): Promise<string> {
// Generate FATF Travel Rule compliant message (ISO 20022 compatible)
const message = {
version: '1.0',
messageType: 'pacs.008', // Payment message
originator: {
address: from,
kyc: await this.isKYCVerified(from),
aml: await this.isAMLVerified(from)
},
beneficiary: {
address: to,
kyc: await this.isKYCVerified(to),
aml: await this.isAMLVerified(to)
},
transaction: {
amount,
asset,
timestamp: new Date().toISOString(),
txId: `TR-${Date.now()}`
},
compliance: {
travelRule: true,
version: 'FATF-16'
}
};
return JSON.stringify(message);
}
async generateISO20022Message(
messageType: string,
data: any
): Promise<string> {
// Generate ISO 20022 compliant financial message
const message = {
AppHdr: {
Fr: {
FIId: {
FinInstnId: {
BICFI: data.fromBIC || 'ASLEGB22XXX'
}
}
},
To: {
FIId: {
FinInstnId: {
BICFI: data.toBIC || 'ASLEGB22XXX'
}
}
},
BizMsgIdr: `ASLE-${messageType}-${Date.now()}`,
MsgDefIdr: messageType, // pacs.008, camt.053, etc.
CreDt: new Date().toISOString(),
Fr: {
OrgId: {
Othr: {
Id: 'ASLE'
}
}
}
},
Document: data.document || {}
};
return JSON.stringify(message);
}
async recordAuditTrail(
userAddress: string,
action: string,
details: any
): Promise<void> {
// Record compliance audit trail in database
await prisma.auditTrail.create({
data: {
userAddress,
action,
details: details as any,
complianceMode: 'Regulated',
timestamp: new Date()
}
});
}
async isKYCVerified(userAddress: string): Promise<boolean> {
const record = await prisma.complianceRecord.findUnique({
where: { userAddress }
});
return record?.kycVerified || false;
}
async isAMLVerified(userAddress: string): Promise<boolean> {
const record = await prisma.complianceRecord.findUnique({
where: { userAddress }
});
return record?.amlVerified || false;
}
async getComplianceRecord(userAddress: string) {
return prisma.complianceRecord.findUnique({
where: { userAddress }
});
}
}

View File

@@ -0,0 +1,95 @@
/**
* Cosmos-specific adapter for ASLE operations
* Integrates with Cosmos SDK and IBC
*/
export interface CosmosConfig {
rpcUrl: string;
chainId: string;
denom: string; // Native denomination (e.g., 'uatom')
ibcChannel?: string;
}
export interface CosmosTransaction {
txHash: string;
height: number;
status: 'success' | 'failed';
}
export class CosmosAdapter {
private config: CosmosConfig;
private client: any; // Would be Cosmos SDK client
constructor(config: CosmosConfig) {
this.config = config;
// Initialize Cosmos SDK client
// this.client = new CosmosClient(config.rpcUrl);
}
/**
* Create liquidity pool on Cosmos chain
*/
async createPool(baseDenom: string, quoteDenom: string, initialLiquidity: bigint): Promise<string> {
// Use Cosmos SDK x/pool module or similar
// Would send IBC transaction
return `cosmos_pool_${Date.now()}`;
}
/**
* Add liquidity to Cosmos pool
*/
async addLiquidity(poolId: string, amounts: { denom: string; amount: bigint }[]): Promise<CosmosTransaction> {
// Execute Cosmos transaction via IBC
return {
txHash: `cosmos_tx_${Date.now()}`,
height: 0,
status: 'success',
};
}
/**
* Bridge assets via IBC
*/
async bridgeViaIBC(
targetChain: string,
channelId: string,
denom: string,
amount: bigint
): Promise<string> {
// Use IBC to transfer tokens
// 1. Create IBC transfer message
// 2. Sign and broadcast transaction
return `ibc_tx_${Date.now()}`;
}
/**
* Get Cosmos account balance
*/
async getBalance(address: string, denom?: string): Promise<bigint> {
// Query Cosmos account balance
return BigInt(0);
}
/**
* Get pool reserves
*/
async getPoolReserves(poolId: string): Promise<{ base: bigint; quote: bigint }> {
// Query Cosmos pool state
return {
base: BigInt(0),
quote: BigInt(0),
};
}
/**
* Query IBC channel status
*/
async getIBCChannelStatus(channelId: string): Promise<{ state: string; counterparty: string }> {
// Query IBC channel
return {
state: 'OPEN',
counterparty: '',
};
}
}

View File

@@ -0,0 +1,170 @@
import { BridgeAdapter, BridgeAdapterFactory, CrossChainMessage } from './bridge-adapter';
import { SolanaAdapter } from './solana-adapter';
import { CosmosAdapter } from './cosmos-adapter';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export interface ChainConfig {
chainId: string;
chainType: 'evm' | 'solana' | 'cosmos';
name: string;
rpcUrl: string;
bridgeConfig?: any;
}
export class CrossChainManager {
private adapters: Map<string, BridgeAdapter> = new Map();
private solanaAdapter?: SolanaAdapter;
private cosmosAdapter?: CosmosAdapter;
/**
* Register chain
*/
async registerChain(config: ChainConfig): Promise<void> {
const adapter = BridgeAdapterFactory.createAdapter(config.chainId, config.chainType);
this.adapters.set(config.chainId, adapter);
// Initialize specific adapters if needed
if (config.chainType === 'solana' && config.bridgeConfig) {
this.solanaAdapter = new SolanaAdapter({
rpcUrl: config.rpcUrl,
programId: config.bridgeConfig.programId || '',
wormholeBridge: config.bridgeConfig.wormholeBridge,
});
} else if (config.chainType === 'cosmos' && config.bridgeConfig) {
this.cosmosAdapter = new CosmosAdapter({
rpcUrl: config.rpcUrl,
chainId: config.chainId,
denom: config.bridgeConfig.denom || 'uatom',
ibcChannel: config.bridgeConfig.ibcChannel,
});
}
}
/**
* Send cross-chain message
*/
async sendCrossChainMessage(
sourceChainId: string,
targetChainId: string,
payload: any
): Promise<string> {
const sourceAdapter = this.adapters.get(sourceChainId);
if (!sourceAdapter) {
throw new Error(`Source chain ${sourceChainId} not registered`);
}
const messageId = await sourceAdapter.sendMessage(targetChainId, payload);
// Store message in database
await prisma.crossChainMessage.create({
data: {
messageId,
sourceChain: sourceChainId,
targetChain: targetChainId,
payload: payload as any,
status: 'pending',
timestamp: new Date(),
},
});
return messageId;
}
/**
* Receive cross-chain message
*/
async receiveCrossChainMessage(messageId: string, chainId: string): Promise<any> {
const adapter = this.adapters.get(chainId);
if (!adapter) {
throw new Error(`Chain ${chainId} not registered`);
}
const payload = await adapter.receiveMessage(messageId);
// Update message status
await prisma.crossChainMessage.update({
where: { messageId },
data: {
status: 'confirmed',
receivedAt: new Date(),
},
});
return payload;
}
/**
* Get chain status
*/
async getChainStatus(chainId: string): Promise<any> {
const adapter = this.adapters.get(chainId);
if (!adapter) {
throw new Error(`Chain ${chainId} not registered`);
}
return await adapter.getStatus();
}
/**
* Bridge liquidity from EVM to Solana
*/
async bridgeToSolana(
evmChainId: string,
amount: bigint,
tokenAddress: string
): Promise<string> {
if (!this.solanaAdapter) {
throw new Error('Solana adapter not initialized');
}
return await this.solanaAdapter.bridgeFromEVM(
parseInt(evmChainId),
amount,
tokenAddress
);
}
/**
* Bridge liquidity from Solana to EVM
*/
async bridgeFromSolana(
targetChainId: string,
amount: bigint,
tokenAddress: string
): Promise<string> {
if (!this.solanaAdapter) {
throw new Error('Solana adapter not initialized');
}
return await this.solanaAdapter.bridgeToEVM(
parseInt(targetChainId),
amount,
tokenAddress
);
}
/**
* Bridge liquidity via IBC (Cosmos)
*/
async bridgeViaIBC(
sourceChain: string,
targetChain: string,
channelId: string,
denom: string,
amount: bigint
): Promise<string> {
if (!this.cosmosAdapter) {
throw new Error('Cosmos adapter not initialized');
}
return await this.cosmosAdapter.bridgeViaIBC(
targetChain,
channelId,
denom,
amount
);
}
}

View File

@@ -0,0 +1,93 @@
import { RegulatoryReportingService } from './regulatory-reporting';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export class CTRGenerator {
private reportingService: RegulatoryReportingService;
constructor(reportingService: RegulatoryReportingService) {
this.reportingService = reportingService;
}
/**
* Generate CTR from transaction
*/
async generateFromTransaction(
transactionHash: string,
userAddress: string,
amount: string,
currency: string,
transactionType: string
): Promise<string> {
const ctr = await this.reportingService.generateCTR(
transactionHash,
userAddress,
amount,
currency,
transactionType
);
return ctr.id;
}
/**
* Check and auto-generate CTR if threshold exceeded
*/
async checkAndGenerate(
transactionHash: string,
userAddress: string,
amount: string,
currency: string,
transactionType: string
): Promise<string | null> {
const requiresCTR = await this.reportingService.checkCTRThreshold(amount, currency);
if (!requiresCTR) {
return null;
}
return await this.generateFromTransaction(
transactionHash,
userAddress,
amount,
currency,
transactionType
);
}
/**
* Format CTR for submission (FinCEN format)
*/
async formatCTRForSubmission(ctrId: string): Promise<any> {
const ctr = await prisma.cTRReport.findUnique({
where: { id: ctrId },
});
if (!ctr) {
throw new Error('CTR not found');
}
// Format according to FinCEN CTR requirements
return {
reportType: 'CTR',
reportId: ctr.reportId,
filerInfo: {
name: process.env.COMPANY_NAME || 'ASLE Platform',
ein: process.env.COMPANY_EIN || '',
},
transactionInfo: {
transactionHash: ctr.transactionHash,
amount: ctr.amount,
currency: ctr.currency,
type: ctr.transactionType,
date: ctr.createdAt.toISOString(),
},
subjectInfo: {
address: ctr.userAddress,
},
jurisdiction: ctr.jurisdiction,
};
}
}

View File

@@ -0,0 +1,136 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export interface CustodialProvider {
name: string;
type: 'fireblocks' | 'coinbase' | 'bitgo' | 'custom';
apiKey?: string;
apiUrl: string;
}
export interface CustodialWallet {
walletId: string;
address: string;
provider: string;
type: 'hot' | 'warm' | 'cold';
mpcEnabled: boolean;
}
export class CustodialService {
private providers: Map<string, CustodialProvider> = new Map();
constructor() {
this.initializeProviders();
}
private initializeProviders() {
// Fireblocks - production-ready structure
this.providers.set('fireblocks', {
name: 'Fireblocks',
type: 'fireblocks',
apiKey: process.env.FIREBLOCKS_API_KEY,
apiUrl: process.env.FIREBLOCKS_API_URL || 'https://api.fireblocks.io/v1',
enabled: !!process.env.FIREBLOCKS_API_KEY
});
// Coinbase Prime - production-ready structure
this.providers.set('coinbase', {
name: 'Coinbase Prime',
type: 'coinbase',
apiKey: process.env.COINBASE_API_KEY,
apiUrl: process.env.COINBASE_API_URL || 'https://api.coinbase.com/api/v3',
enabled: !!process.env.COINBASE_API_KEY
});
// BitGo - production-ready structure
this.providers.set('bitgo', {
name: 'BitGo',
type: 'bitgo',
apiKey: process.env.BITGO_API_KEY,
apiUrl: process.env.BITGO_API_URL || 'https://app.bitgo.com/api',
enabled: !!process.env.BITGO_API_KEY
});
}
async createCustodialWallet(providerName: string, walletType: 'hot' | 'warm' | 'cold'): Promise<CustodialWallet> {
const provider = this.providers.get(providerName);
if (!provider) {
throw new Error(`Provider ${providerName} not found`);
}
let wallet: CustodialWallet;
if (provider.enabled && provider.apiKey) {
// Real integration would call provider API
wallet = await this._createWalletWithProvider(provider, walletType);
} else {
// Mock implementation with production structure
wallet = {
walletId: `wallet-${Date.now()}`,
address: `0x${Math.random().toString(16).substr(2, 40)}`,
provider: providerName,
type: walletType,
mpcEnabled: true,
};
}
return wallet;
}
private async _createWalletWithProvider(provider: CustodialProvider, walletType: string): Promise<CustodialWallet> {
// Production implementation would make API calls:
/*
const response = await fetch(`${provider.apiUrl}/wallets`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${provider.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ type: walletType })
});
const data = await response.json();
return {
walletId: data.id,
address: data.address,
provider: provider.name,
type: walletType,
mpcEnabled: data.mpcEnabled || false
};
*/
// Mock with real structure
return {
walletId: `${provider.type}-${Date.now()}`,
address: `0x${Math.random().toString(16).substr(2, 40)}`,
provider: provider.name,
type: walletType as any,
mpcEnabled: true
};
}
async getCustodialWallet(walletId: string): Promise<CustodialWallet | null> {
// In production, fetch from provider API
return null;
}
async initiateTransfer(
walletId: string,
to: string,
amount: string,
asset: string
): Promise<string> {
// Production implementation would:
// 1. Create transaction request with custodial provider
// 2. Require multi-sig approval
// 3. Execute transaction
// 4. Return transaction hash
return `tx-${Date.now()}`;
}
async getMPCKeyShares(walletId: string): Promise<{ shares: number; threshold: number }> {
// In production, retrieve MPC key share information
return { shares: 3, threshold: 2 };
}
}

View File

@@ -0,0 +1,136 @@
import { PrismaClient } from '@prisma/client';
import { ethers } from 'ethers';
const prisma = new PrismaClient();
export interface Delegation {
delegator: string;
delegatee: string;
votingPower: string;
timestamp: Date;
}
export interface DelegateReputation {
delegatee: string;
totalDelegated: string;
proposalsVoted: number;
proposalsWon: number;
winRate: number;
averageVoteWeight: string;
}
export class DelegationService {
private provider: ethers.Provider;
private diamondAddress: string;
constructor(provider: ethers.Provider, diamondAddress: string) {
this.provider = provider;
this.diamondAddress = diamondAddress;
}
async getDelegation(delegator: string): Promise<Delegation | null> {
// Get from contract
const governanceFacet = new ethers.Contract(
this.diamondAddress,
[
'function delegates(address delegator) external view returns (address)',
'function getCurrentVotes(address account) external view returns (uint256)',
],
this.provider
);
try {
const delegatee = await governanceFacet.delegates(delegator);
const votingPower = await governanceFacet.getCurrentVotes(delegator);
if (delegatee.toLowerCase() === delegator.toLowerCase()) {
return null; // Not delegated
}
return {
delegator,
delegatee,
votingPower: votingPower.toString(),
timestamp: new Date(),
};
} catch (error) {
console.error('Error getting delegation:', error);
return null;
}
}
async getAllDelegations(): Promise<Delegation[]> {
// In production, would index events or query contract
const delegations = await prisma.delegation.findMany({
orderBy: { timestamp: 'desc' },
});
return delegations.map((d) => ({
delegator: d.delegator,
delegatee: d.delegatee,
votingPower: d.votingPower,
timestamp: d.timestamp,
}));
}
async getDelegateReputation(delegatee: string): Promise<DelegateReputation> {
const delegations = await prisma.delegation.findMany({
where: { delegatee },
});
const votes = await prisma.vote.findMany({
where: { voter: delegatee },
include: { proposal: true },
});
const totalDelegated = delegations.reduce(
(sum, d) => sum + BigInt(d.votingPower),
BigInt(0)
).toString();
const proposalsWon = votes.filter(
(v) => v.proposal.status === 'passed' && v.support
).length;
const averageVoteWeight =
votes.length > 0
? (
votes.reduce(
(sum, v) => sum + BigInt(v.votingPower),
BigInt(0)
) / BigInt(votes.length)
).toString()
: '0';
return {
delegatee,
totalDelegated,
proposalsVoted: votes.length,
proposalsWon,
winRate: votes.length > 0 ? (proposalsWon / votes.length) * 100 : 0,
averageVoteWeight,
};
}
async trackDelegation(
delegator: string,
delegatee: string,
votingPower: string
): Promise<void> {
await prisma.delegation.upsert({
where: { delegator },
update: {
delegatee,
votingPower,
timestamp: new Date(),
},
create: {
delegator,
delegatee,
votingPower,
timestamp: new Date(),
},
});
}
}

View File

@@ -0,0 +1,109 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export interface DeploymentData {
name: string;
environment: string;
version: string;
config: any;
}
export interface DeploymentLogData {
deploymentId: string;
level: string;
message: string;
metadata?: any;
}
export class DeploymentService {
/**
* Create deployment
*/
async createDeployment(data: DeploymentData, deployedBy?: string): Promise<any> {
return prisma.deployment.create({
data: {
name: data.name,
environment: data.environment,
version: data.version,
config: data.config,
status: 'pending',
deployedBy,
},
});
}
/**
* Update deployment status
*/
async updateDeploymentStatus(
id: string,
status: 'pending' | 'deploying' | 'success' | 'failed',
deployedAt?: Date
): Promise<void> {
await prisma.deployment.update({
where: { id },
data: {
status,
deployedAt: deployedAt || (status === 'success' ? new Date() : undefined),
},
});
}
/**
* Add deployment log
*/
async addLog(data: DeploymentLogData): Promise<void> {
await prisma.deploymentLog.create({
data: {
deploymentId: data.deploymentId,
level: data.level,
message: data.message,
metadata: data.metadata || {},
},
});
}
/**
* Get deployment
*/
async getDeployment(id: string) {
return prisma.deployment.findUnique({
where: { id },
include: {
logs: {
orderBy: { timestamp: 'desc' },
take: 100,
},
},
});
}
/**
* Get deployments by environment
*/
async getDeployments(environment?: string) {
const where: any = {};
if (environment) where.environment = environment;
return prisma.deployment.findMany({
where,
orderBy: { createdAt: 'desc' },
take: 50,
});
}
/**
* Rollback deployment
*/
async rollbackDeployment(id: string, rollbackVersion: string): Promise<void> {
await prisma.deployment.update({
where: { id },
data: {
rollbackVersion,
status: 'pending',
},
});
}
}

View File

@@ -0,0 +1,92 @@
import { PushNotificationService } from './push-notifications';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export class FCMService extends PushNotificationService {
/**
* Register device token
*/
async registerDevice(userAddress: string, deviceToken: string, platform: 'ios' | 'android'): Promise<void> {
await prisma.deviceToken.upsert({
where: {
userAddress_deviceToken: {
userAddress,
deviceToken,
},
},
update: {
platform,
updatedAt: new Date(),
},
create: {
userAddress,
deviceToken,
platform,
},
});
}
/**
* Send transaction notification
*/
async sendTransactionNotification(
userAddress: string,
transactionHash: string,
status: 'pending' | 'completed' | 'failed'
): Promise<void> {
const devices = await prisma.deviceToken.findMany({
where: { userAddress },
});
const title = status === 'completed' ? 'Transaction Completed' :
status === 'failed' ? 'Transaction Failed' :
'Transaction Pending';
const body = `Transaction ${transactionHash.slice(0, 10)}... is ${status}`;
const notifications = devices.map((device) => ({
token: device.deviceToken,
title,
body,
data: {
type: 'transaction',
transactionHash,
status,
},
}));
await this.sendBatchNotifications(notifications);
}
/**
* Send proposal notification
*/
async sendProposalNotification(
userAddress: string,
proposalId: string,
type: 'created' | 'voting_ended' | 'executed'
): Promise<void> {
const devices = await prisma.deviceToken.findMany({
where: { userAddress },
});
const title = type === 'created' ? 'New Proposal' :
type === 'voting_ended' ? 'Voting Ended' :
'Proposal Executed';
const body = `Proposal ${proposalId} ${type}`;
const notifications = devices.map((device) => ({
token: device.deviceToken,
title,
body,
data: {
type: 'proposal',
proposalId,
eventType: type,
},
}));
await this.sendBatchNotifications(notifications);
}
}

View File

@@ -0,0 +1,197 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export interface GovernanceMetrics {
totalProposals: number;
activeProposals: number;
passedProposals: number;
rejectedProposals: number;
totalVotes: number;
uniqueVoters: number;
participationRate: number;
averageVotingPower: string;
totalDelegations: number;
}
export interface VotingTrends {
date: string;
proposalsCreated: number;
votesCast: number;
proposalsPassed: number;
}
export interface DelegateStats {
delegatee: string;
totalDelegated: string;
proposalsVoted: number;
winRate: number;
averageVoteWeight: string;
}
export class GovernanceAnalyticsService {
/**
* Calculate governance metrics
*/
async calculateMetrics(): Promise<GovernanceMetrics> {
const [
totalProposals,
activeProposals,
passedProposals,
rejectedProposals,
totalVotes,
uniqueVoters,
delegations,
] = await Promise.all([
prisma.proposal.count(),
prisma.proposal.count({ where: { status: 'active' } }),
prisma.proposal.count({ where: { status: 'passed' } }),
prisma.proposal.count({ where: { status: 'rejected' } }),
prisma.vote.count(),
prisma.vote.groupBy({
by: ['voter'],
_count: { voter: true },
}),
prisma.delegation.count(),
]);
const totalVotingPower = await prisma.vote.aggregate({
_sum: { votingPower: true },
});
const averageVotingPower =
totalVotes > 0
? (BigInt(totalVotingPower._sum.votingPower || '0') / BigInt(totalVotes)).toString()
: '0';
const participationRate =
uniqueVoters.length > 0
? (totalVotes / (uniqueVoters.length * totalProposals)) * 100
: 0;
return {
totalProposals,
activeProposals,
passedProposals,
rejectedProposals,
totalVotes,
uniqueVoters: uniqueVoters.length,
participationRate,
averageVotingPower,
totalDelegations: delegations,
};
}
/**
* Get voting trends
*/
async getVotingTrends(startDate: Date, endDate: Date): Promise<VotingTrends[]> {
const trends: VotingTrends[] = [];
const currentDate = new Date(startDate);
while (currentDate <= endDate) {
const dayStart = new Date(currentDate);
dayStart.setHours(0, 0, 0, 0);
const dayEnd = new Date(currentDate);
dayEnd.setHours(23, 59, 59, 999);
const [proposalsCreated, votesCast, proposalsPassed] = await Promise.all([
prisma.proposal.count({
where: {
createdAt: {
gte: dayStart,
lte: dayEnd,
},
},
}),
prisma.vote.count({
where: {
timestamp: {
gte: dayStart,
lte: dayEnd,
},
},
}),
prisma.proposal.count({
where: {
status: 'passed',
updatedAt: {
gte: dayStart,
lte: dayEnd,
},
},
}),
]);
trends.push({
date: dayStart.toISOString().split('T')[0],
proposalsCreated,
votesCast,
proposalsPassed,
});
currentDate.setDate(currentDate.getDate() + 1);
}
return trends;
}
/**
* Get delegate leaderboard
*/
async getDelegateLeaderboard(limit: number = 10): Promise<DelegateStats[]> {
const delegations = await prisma.delegation.findMany({
include: {
// Would need to join with votes
},
});
// Group by delegatee and calculate stats
const delegateMap = new Map<string, { totalDelegated: bigint; votes: any[] }>();
for (const delegation of delegations) {
const existing = delegateMap.get(delegation.delegatee) || {
totalDelegated: BigInt(0),
votes: [],
};
existing.totalDelegated += BigInt(delegation.votingPower);
delegateMap.set(delegation.delegatee, existing);
}
// Get votes for each delegatee
for (const [delegatee] of delegateMap) {
const votes = await prisma.vote.findMany({
where: { voter: delegatee },
include: { proposal: true },
});
delegateMap.get(delegatee)!.votes = votes;
}
const stats: DelegateStats[] = Array.from(delegateMap.entries()).map(([delegatee, data]) => {
const proposalsWon = data.votes.filter(
(v) => v.proposal.status === 'passed' && v.support
).length;
const averageVoteWeight =
data.votes.length > 0
? (
data.votes.reduce(
(sum, v) => sum + BigInt(v.votingPower),
BigInt(0)
) / BigInt(data.votes.length)
).toString()
: '0';
return {
delegatee,
totalDelegated: data.totalDelegated.toString(),
proposalsVoted: data.votes.length,
winRate: data.votes.length > 0 ? (proposalsWon / data.votes.length) * 100 : 0,
averageVoteWeight,
};
});
return stats.sort((a, b) => parseFloat(b.totalDelegated) - parseFloat(a.totalDelegated)).slice(0, limit);
}
}

View File

@@ -0,0 +1,165 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export interface Comment {
id: string;
proposalId: bigint;
author: string;
content: string;
parentId?: string;
upvotes: number;
downvotes: number;
createdAt: Date;
updatedAt: Date;
}
export interface DiscussionThread {
proposalId: bigint;
comments: Comment[];
totalComments: number;
}
export class GovernanceDiscussionService {
/**
* Add comment to proposal
*/
async addComment(
proposalId: bigint,
author: string,
content: string,
parentId?: string
): Promise<Comment> {
const comment = await prisma.comment.create({
data: {
proposalId,
author,
content,
parentId,
upvotes: 0,
downvotes: 0,
},
});
return {
id: comment.id,
proposalId: comment.proposalId,
author: comment.author,
content: comment.content,
parentId: comment.parentId || undefined,
upvotes: comment.upvotes,
downvotes: comment.downvotes,
createdAt: comment.createdAt,
updatedAt: comment.updatedAt,
};
}
/**
* Get discussion thread for proposal
*/
async getDiscussion(proposalId: bigint): Promise<DiscussionThread> {
const comments = await prisma.comment.findMany({
where: { proposalId },
orderBy: { createdAt: 'asc' },
});
return {
proposalId,
comments: comments.map((c) => ({
id: c.id,
proposalId: c.proposalId,
author: c.author,
content: c.content,
parentId: c.parentId || undefined,
upvotes: c.upvotes,
downvotes: c.downvotes,
createdAt: c.createdAt,
updatedAt: c.updatedAt,
})),
totalComments: comments.length,
};
}
/**
* Vote on comment
*/
async voteComment(commentId: string, voter: string, upvote: boolean): Promise<void> {
const existingVote = await prisma.commentVote.findUnique({
where: {
commentId_voter: {
commentId,
voter,
},
},
});
if (existingVote) {
// Update existing vote
if (existingVote.upvote !== upvote) {
await prisma.commentVote.update({
where: { id: existingVote.id },
data: { upvote },
});
// Update comment vote counts
if (upvote) {
await prisma.comment.update({
where: { id: commentId },
data: {
upvotes: { increment: 1 },
downvotes: { decrement: 1 },
},
});
} else {
await prisma.comment.update({
where: { id: commentId },
data: {
upvotes: { decrement: 1 },
downvotes: { increment: 1 },
},
});
}
}
} else {
// Create new vote
await prisma.commentVote.create({
data: {
commentId,
voter,
upvote,
},
});
// Update comment vote counts
await prisma.comment.update({
where: { id: commentId },
data: {
upvotes: upvote ? { increment: 1 } : undefined,
downvotes: upvote ? undefined : { increment: 1 },
},
});
}
}
/**
* Delete comment
*/
async deleteComment(commentId: string, author: string): Promise<void> {
const comment = await prisma.comment.findUnique({
where: { id: commentId },
});
if (!comment) {
throw new Error('Comment not found');
}
if (comment.author.toLowerCase() !== author.toLowerCase()) {
throw new Error('Not authorized to delete this comment');
}
await prisma.comment.delete({
where: { id: commentId },
});
}
}

View File

@@ -0,0 +1,50 @@
import { KYCResult } from '../compliance';
export interface IKYCProvider {
name: string;
apiKey?: string;
apiUrl?: string;
enabled: boolean;
verify(userAddress: string, userData?: any): Promise<KYCResult>;
checkStatus(kycId: string): Promise<KYCResult>;
}
export abstract class BaseKYCProvider implements IKYCProvider {
name: string;
apiKey?: string;
apiUrl?: string;
enabled: boolean;
constructor(name: string, apiKey?: string, apiUrl?: string) {
this.name = name;
this.apiKey = apiKey;
this.apiUrl = apiUrl;
this.enabled = !!apiKey;
}
abstract verify(userAddress: string, userData?: any): Promise<KYCResult>;
abstract checkStatus(kycId: string): Promise<KYCResult>;
protected async makeRequest(endpoint: string, options: RequestInit = {}): Promise<any> {
if (!this.apiKey || !this.apiUrl) {
throw new Error(`${this.name} provider not configured`);
}
const response = await fetch(`${this.apiUrl}${endpoint}`, {
...options,
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
...options.headers,
},
});
if (!response.ok) {
throw new Error(`${this.name} API error: ${response.statusText}`);
}
return response.json();
}
}

View File

@@ -0,0 +1,76 @@
import { BaseKYCProvider, IKYCProvider } from './base';
import { KYCResult } from '../compliance';
export class JumioProvider extends BaseKYCProvider implements IKYCProvider {
constructor(apiKey?: string, apiUrl?: string) {
super('Jumio', apiKey, apiUrl || 'https://netverify.com/api/v4');
}
async verify(userAddress: string, userData?: any): Promise<KYCResult> {
if (!this.enabled) {
return this.mockVerify(userAddress);
}
try {
// Jumio API integration
const response = await this.makeRequest('/performNetverify', {
method: 'POST',
body: JSON.stringify({
customerInternalReference: userAddress,
userReference: userAddress,
callbackUrl: `${process.env.API_URL}/api/compliance/jumio/callback`,
...userData,
}),
});
return {
verified: response.verificationStatus === 'APPROVED_VERIFIED',
tier: response.verificationLevel || 1,
provider: this.name,
timestamp: Date.now(),
kycId: response.transactionReference,
};
} catch (error: any) {
console.error('Jumio verification error:', error);
throw new Error(`Jumio verification failed: ${error.message}`);
}
}
async checkStatus(kycId: string): Promise<KYCResult> {
if (!this.enabled) {
return {
verified: true,
tier: 1,
provider: this.name,
timestamp: Date.now(),
kycId,
};
}
try {
const response = await this.makeRequest(`/netverify/${kycId}`, {
method: 'GET',
});
return {
verified: response.verificationStatus === 'APPROVED_VERIFIED',
tier: response.verificationLevel || 1,
provider: this.name,
timestamp: Date.now(),
kycId,
};
} catch (error: any) {
throw new Error(`Jumio status check failed: ${error.message}`);
}
}
private mockVerify(userAddress: string): KYCResult {
return {
verified: true,
tier: 1,
provider: this.name,
timestamp: Date.now(),
};
}
}

View File

@@ -0,0 +1,81 @@
import { BaseKYCProvider, IKYCProvider } from './base';
import { KYCResult } from '../compliance';
export class PersonaProvider extends BaseKYCProvider implements IKYCProvider {
constructor(apiKey?: string, apiUrl?: string) {
super('Persona', apiKey, apiUrl || 'https://withpersona.com/api/v1');
}
async verify(userAddress: string, userData?: any): Promise<KYCResult> {
if (!this.enabled) {
return this.mockVerify(userAddress);
}
try {
// Persona API integration
const response = await this.makeRequest('/inquiries', {
method: 'POST',
body: JSON.stringify({
data: {
type: 'inquiry',
attributes: {
reference_id: userAddress,
template_id: process.env.PERSONA_TEMPLATE_ID || 'tmpl_default',
redirect_url: `${process.env.API_URL}/api/compliance/persona/callback`,
},
},
}),
});
return {
verified: false, // Will be updated via webhook
tier: 1,
provider: this.name,
timestamp: Date.now(),
kycId: response.data.id,
};
} catch (error: any) {
console.error('Persona verification error:', error);
throw new Error(`Persona verification failed: ${error.message}`);
}
}
async checkStatus(kycId: string): Promise<KYCResult> {
if (!this.enabled) {
return {
verified: true,
tier: 1,
provider: this.name,
timestamp: Date.now(),
kycId,
};
}
try {
const response = await this.makeRequest(`/inquiries/${kycId}`, {
method: 'GET',
});
const status = response.data.attributes.status;
return {
verified: status === 'completed' && response.data.attributes.state === 'approved',
tier: response.data.attributes.state === 'approved' ? 2 : 1,
provider: this.name,
timestamp: Date.now(),
kycId,
};
} catch (error: any) {
throw new Error(`Persona status check failed: ${error.message}`);
}
}
private mockVerify(userAddress: string): KYCResult {
return {
verified: true,
tier: 1,
provider: this.name,
timestamp: Date.now(),
};
}
}

View File

@@ -0,0 +1,83 @@
import { BaseKYCProvider, IKYCProvider } from './base';
import { KYCResult } from '../compliance';
export class VeriffProvider extends BaseKYCProvider implements IKYCProvider {
constructor(apiKey?: string, apiUrl?: string) {
super('Veriff', apiKey, apiUrl || 'https://station.veriff.com');
}
async verify(userAddress: string, userData?: any): Promise<KYCResult> {
if (!this.enabled) {
return this.mockVerify(userAddress);
}
try {
// Veriff API integration
const response = await this.makeRequest('/v1/sessions', {
method: 'POST',
body: JSON.stringify({
verification: {
callback: `${process.env.API_URL}/api/compliance/veriff/callback`,
person: {
firstName: userData?.firstName,
lastName: userData?.lastName,
},
document: {
type: 'PASSPORT',
},
},
vendorData: userAddress,
}),
});
return {
verified: false, // Will be updated via webhook
tier: 1,
provider: this.name,
timestamp: Date.now(),
kycId: response.verification.id,
};
} catch (error: any) {
console.error('Veriff verification error:', error);
throw new Error(`Veriff verification failed: ${error.message}`);
}
}
async checkStatus(kycId: string): Promise<KYCResult> {
if (!this.enabled) {
return {
verified: true,
tier: 1,
provider: this.name,
timestamp: Date.now(),
kycId,
};
}
try {
const response = await this.makeRequest(`/v1/sessions/${kycId}`, {
method: 'GET',
});
return {
verified: response.verification.status === 'success',
tier: response.verification.code === 9001 ? 2 : 1,
provider: this.name,
timestamp: Date.now(),
kycId,
};
} catch (error: any) {
throw new Error(`Veriff status check failed: ${error.message}`);
}
}
private mockVerify(userAddress: string): KYCResult {
return {
verified: true,
tier: 1,
provider: this.name,
timestamp: Date.now(),
};
}
}

View File

@@ -0,0 +1,214 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export interface Alert {
id: string;
type: 'error' | 'warning' | 'info' | 'critical';
severity: 'low' | 'medium' | 'high' | 'critical';
title: string;
message: string;
timestamp: number;
source: string;
resolved: boolean;
}
export interface Metric {
name: string;
value: number;
unit: string;
timestamp: number;
tags: { [key: string]: string };
}
export interface SystemHealth {
status: 'healthy' | 'degraded' | 'down';
components: {
[component: string]: {
status: 'up' | 'down' | 'degraded';
lastCheck: number;
};
};
metrics: Metric[];
alerts: Alert[];
}
export class MonitoringService {
constructor() {}
async recordMetric(metricType: string, value: string, metadata?: any): Promise<void> {
await prisma.metric.create({
data: {
metricType,
value,
metadata: metadata as any,
timestamp: new Date()
}
});
}
async createAlert(
alertType: string,
severity: 'low' | 'medium' | 'high' | 'critical',
message: string,
metadata?: any
): Promise<string> {
const alert = await prisma.systemAlert.create({
data: {
alertType,
severity,
message,
metadata: metadata as any,
resolved: false,
createdAt: new Date()
}
});
// In production, send webhook notifications for critical alerts
if (severity === 'critical') {
await this._sendCriticalAlert(alert.id, message);
}
return alert.id;
}
private async _sendCriticalAlert(alertId: string, message: string): Promise<void> {
// Send webhook or email notification
const webhookUrl = process.env.ALERT_WEBHOOK_URL;
if (webhookUrl) {
// Production would send HTTP request
console.log(`Critical alert ${alertId}: ${message}`);
}
}
async resolveAlert(alertId: string): Promise<void> {
await prisma.systemAlert.update({
where: { id: alertId },
data: {
resolved: true,
resolvedAt: new Date()
}
});
}
async getSystemHealth(): Promise<SystemHealth> {
const unresolvedAlerts = await prisma.systemAlert.findMany({
where: { resolved: false },
orderBy: { createdAt: 'desc' },
take: 10
});
const recentMetrics = await prisma.metric.findMany({
orderBy: { timestamp: 'desc' },
take: 100
});
// Determine overall health status
let status: 'healthy' | 'degraded' | 'down' = 'healthy';
const criticalAlerts = unresolvedAlerts.filter(a => a.severity === 'critical');
if (criticalAlerts.length > 0) {
status = 'down';
} else if (unresolvedAlerts.filter(a => a.severity === 'high').length > 0) {
status = 'degraded';
}
return {
status,
components: {
contracts: { status: 'up', lastCheck: Date.now() },
frontend: { status: 'up', lastCheck: Date.now() },
backend: { status: 'up', lastCheck: Date.now() },
database: { status: 'up', lastCheck: Date.now() },
ccip: { status: 'up', lastCheck: Date.now() },
compliance: { status: 'up', lastCheck: Date.now() },
},
metrics: recentMetrics.map(m => ({
name: m.metricType,
value: parseFloat(m.value),
unit: 'count',
timestamp: m.timestamp.getTime(),
tags: (m.metadata as any) || {}
})),
alerts: unresolvedAlerts.map(a => ({
id: a.id,
type: a.alertType as any,
severity: a.severity as any,
title: a.alertType,
message: a.message,
timestamp: a.createdAt.getTime(),
source: 'system',
resolved: a.resolved
}))
};
}
async getAlerts(filters?: { type?: string; severity?: string; resolved?: boolean }): Promise<Alert[]> {
const alerts = await prisma.systemAlert.findMany({
where: {
...(filters?.type && { alertType: filters.type }),
...(filters?.severity && { severity: filters.severity }),
...(filters?.resolved !== undefined && { resolved: filters.resolved })
},
orderBy: { createdAt: 'desc' },
take: 100
});
return alerts.map(a => ({
id: a.id,
type: a.alertType as any,
severity: a.severity as any,
title: a.alertType,
message: a.message,
timestamp: a.createdAt.getTime(),
source: 'system',
resolved: a.resolved
}));
}
async getMetrics(metricType?: string, timeRange?: { from: number; to: number }): Promise<Metric[]> {
const metrics = await prisma.metric.findMany({
where: {
...(metricType && { metricType }),
...(timeRange && {
timestamp: {
gte: new Date(timeRange.from),
lte: new Date(timeRange.to)
}
})
},
orderBy: { timestamp: 'desc' },
take: 1000
});
return metrics.map(m => ({
name: m.metricType,
value: parseFloat(m.value),
unit: 'count',
timestamp: m.timestamp.getTime(),
tags: (m.metadata as any) || {}
}));
}
async generateReport(period: 'daily' | 'weekly' | 'monthly'): Promise<any> {
const now = Date.now();
const periodMs = {
daily: 24 * 60 * 60 * 1000,
weekly: 7 * 24 * 60 * 60 * 1000,
monthly: 30 * 24 * 60 * 60 * 1000,
};
const from = now - periodMs[period];
const metrics = await this.getMetrics(undefined, { from, to: now });
const alerts = await this.getAlerts({ resolved: false });
const health = await this.getSystemHealth();
return {
period,
from: new Date(from).toISOString(),
to: new Date(now).toISOString(),
metrics: metrics.length,
alerts: alerts.length,
systemHealth: health,
};
}
}

View File

@@ -0,0 +1,188 @@
export interface JurisdictionConfig {
name: string;
code: string;
regulations: string[];
kycRequired: boolean;
amlRequired: boolean;
travelRuleRequired: boolean;
minKycTier: number;
thresholds: {
kycTier1: number; // Transaction amount threshold for tier 1
kycTier2: number;
kycTier3: number;
travelRule: number; // Amount threshold for Travel Rule
};
}
export interface ComplianceCheck {
allowed: boolean;
requirements: string[];
missingRequirements: string[];
}
export class MultiJurisdictionService {
private jurisdictions: Map<string, JurisdictionConfig> = new Map();
constructor() {
this.initializeJurisdictions();
}
private initializeJurisdictions() {
// MiCA (EU) - Markets in Crypto-Assets Regulation
this.jurisdictions.set('EU', {
name: 'European Union',
code: 'EU',
regulations: ['MiCA', 'GDPR', '5AMLD', '6AMLD'],
kycRequired: true,
amlRequired: true,
travelRuleRequired: true,
minKycTier: 2,
thresholds: {
kycTier1: 1000, // EUR
kycTier2: 10000,
kycTier3: 100000,
travelRule: 1000
}
});
// SEC (US) - Securities and Exchange Commission
this.jurisdictions.set('US', {
name: 'United States',
code: 'US',
regulations: ['SEC', 'FinCEN', 'OFAC', 'BSA'],
kycRequired: true,
amlRequired: true,
travelRuleRequired: true,
minKycTier: 3,
thresholds: {
kycTier1: 3000, // USD
kycTier2: 15000,
kycTier3: 50000,
travelRule: 3000
}
});
// FINMA (Switzerland) - Financial Market Supervisory Authority
this.jurisdictions.set('CH', {
name: 'Switzerland',
code: 'CH',
regulations: ['FINMA', 'AMLA', 'AMLO'],
kycRequired: true,
amlRequired: true,
travelRuleRequired: true,
minKycTier: 2,
thresholds: {
kycTier1: 1000, // CHF
kycTier2: 5000,
kycTier3: 25000,
travelRule: 1000
}
});
// FCA (UK) - Financial Conduct Authority
this.jurisdictions.set('GB', {
name: 'United Kingdom',
code: 'GB',
regulations: ['FCA', 'MLR 2017', 'POCA'],
kycRequired: true,
amlRequired: true,
travelRuleRequired: true,
minKycTier: 2,
thresholds: {
kycTier1: 1000, // GBP
kycTier2: 10000,
kycTier3: 50000,
travelRule: 1000
}
});
// Singapore (MAS)
this.jurisdictions.set('SG', {
name: 'Singapore',
code: 'SG',
regulations: ['MAS', 'PSA'],
kycRequired: true,
amlRequired: true,
travelRuleRequired: true,
minKycTier: 2,
thresholds: {
kycTier1: 1500, // SGD
kycTier2: 15000,
kycTier3: 75000,
travelRule: 1500
}
});
}
getJurisdictionConfig(code: string): JurisdictionConfig | undefined {
return this.jurisdictions.get(code);
}
getAllJurisdictions(): JurisdictionConfig[] {
return Array.from(this.jurisdictions.values());
}
validateCompliance(
userJurisdiction: string,
userKycTier: number,
transactionAmount: number,
transactionType: string
): ComplianceCheck {
const config = this.jurisdictions.get(userJurisdiction);
if (!config) {
return {
allowed: false,
requirements: ['Unknown jurisdiction'],
missingRequirements: ['Valid jurisdiction required']
};
}
const requirements: string[] = [];
const missingRequirements: string[] = [];
let allowed = true;
// Check KYC requirements based on transaction amount
if (config.kycRequired) {
let requiredTier = 1;
if (transactionAmount >= config.thresholds.kycTier3) {
requiredTier = 3;
} else if (transactionAmount >= config.thresholds.kycTier2) {
requiredTier = 2;
}
if (userKycTier < requiredTier) {
allowed = false;
missingRequirements.push(`KYC Tier ${requiredTier} required for transaction amount`);
}
requirements.push(`KYC Tier ${requiredTier} required`);
}
if (config.amlRequired) {
requirements.push('AML verification required');
}
if (config.travelRuleRequired && transactionAmount >= config.thresholds.travelRule) {
if (transactionType === 'transfer') {
requirements.push('FATF Travel Rule compliance required');
}
}
return { allowed, requirements, missingRequirements };
}
getRegulatoryRequirements(jurisdictionCode: string): string[] {
const config = this.jurisdictions.get(jurisdictionCode);
return config ? config.regulations : [];
}
getTravelRuleThreshold(jurisdictionCode: string): number {
const config = this.jurisdictions.get(jurisdictionCode);
return config ? config.thresholds.travelRule : 1000;
}
requiresTravelRule(jurisdictionCode: string, amount: number): boolean {
const config = this.jurisdictions.get(jurisdictionCode);
if (!config || !config.travelRuleRequired) return false;
return amount >= config.thresholds.travelRule;
}
}

View File

@@ -0,0 +1,86 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export interface ProposalTemplate {
id: string;
name: string;
description: string;
proposalType: string;
templateData: any;
active: boolean;
createdAt: Date;
}
export class ProposalTemplateService {
async createTemplate(
name: string,
description: string,
proposalType: string,
templateData: any
): Promise<ProposalTemplate> {
const template = await prisma.proposalTemplate.create({
data: {
name,
description,
proposalType,
templateData: templateData as any,
active: true,
},
});
return {
id: template.id,
name: template.name,
description: template.description,
proposalType: template.proposalType,
templateData: template.templateData as any,
active: template.active,
createdAt: template.createdAt,
};
}
async getTemplate(templateId: string): Promise<ProposalTemplate | null> {
const template = await prisma.proposalTemplate.findUnique({
where: { id: templateId },
});
if (!template) return null;
return {
id: template.id,
name: template.name,
description: template.description,
proposalType: template.proposalType,
templateData: template.templateData as any,
active: template.active,
createdAt: template.createdAt,
};
}
async getAllTemplates(activeOnly: boolean = false): Promise<ProposalTemplate[]> {
const where = activeOnly ? { active: true } : {};
const templates = await prisma.proposalTemplate.findMany({
where,
orderBy: { createdAt: 'desc' },
});
return templates.map((t) => ({
id: t.id,
name: t.name,
description: t.description,
proposalType: t.proposalType,
templateData: t.templateData as any,
active: t.active,
createdAt: t.createdAt,
}));
}
async setTemplateActive(templateId: string, active: boolean): Promise<void> {
await prisma.proposalTemplate.update({
where: { id: templateId },
data: { active },
});
}
}

View File

@@ -0,0 +1,78 @@
import admin from 'firebase-admin';
export interface PushNotification {
token: string;
title: string;
body: string;
data?: any;
}
export class PushNotificationService {
private fcm: admin.messaging.Messaging | null = null;
constructor() {
// Initialize Firebase Admin if credentials are available
if (process.env.FIREBASE_SERVICE_ACCOUNT) {
try {
const serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT);
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
this.fcm = admin.messaging();
} catch (error) {
console.error('Failed to initialize Firebase Admin:', error);
}
}
}
/**
* Send push notification
*/
async sendNotification(notification: PushNotification): Promise<void> {
if (!this.fcm) {
console.warn('FCM not initialized, skipping notification');
return;
}
try {
await this.fcm.send({
token: notification.token,
notification: {
title: notification.title,
body: notification.body,
},
data: notification.data || {},
});
} catch (error: any) {
console.error('Error sending push notification:', error);
throw error;
}
}
/**
* Send notification to multiple devices
*/
async sendBatchNotifications(notifications: PushNotification[]): Promise<void> {
if (!this.fcm) {
console.warn('FCM not initialized, skipping notifications');
return;
}
const messages = notifications.map((n) => ({
token: n.token,
notification: {
title: n.title,
body: n.body,
},
data: n.data || {},
}));
try {
await this.fcm.sendAll(messages);
} catch (error: any) {
console.error('Error sending batch notifications:', error);
throw error;
}
}
}

View File

@@ -0,0 +1,96 @@
import { SNSClient, PublishCommand } from '@aws-sdk/client-sns';
import { BasePushProvider, PushNotification, PushNotificationResult } from './base';
export class AWSSNSProvider extends BasePushProvider {
private sns: SNSClient;
private iosPlatformArn: string;
private androidPlatformArn: string;
private region: string;
constructor(
region?: string,
iosPlatformArn?: string,
androidPlatformArn?: string
) {
super('AWS SNS', !!region && !!iosPlatformArn && !!androidPlatformArn);
this.region = region || process.env.AWS_REGION || 'us-east-1';
this.iosPlatformArn = iosPlatformArn || process.env.AWS_SNS_IOS_ARN || '';
this.androidPlatformArn = androidPlatformArn || process.env.AWS_SNS_ANDROID_ARN || '';
this.sns = new SNSClient({
region: this.region,
credentials: process.env.AWS_ACCESS_KEY_ID ? {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || '',
} : undefined,
});
}
async sendNotification(notification: PushNotification): Promise<PushNotificationResult> {
if (!this.enabled) {
return {
success: false,
error: 'AWS SNS not configured',
};
}
try {
const platformArn = notification.platform === 'ios'
? this.iosPlatformArn
: this.androidPlatformArn;
const message = JSON.stringify({
default: notification.body,
APNS: JSON.stringify({
aps: {
alert: {
title: notification.title,
body: notification.body,
},
badge: 1,
sound: 'default',
},
...notification.data,
}),
GCM: JSON.stringify({
notification: {
title: notification.title,
body: notification.body,
sound: 'default',
},
data: notification.data || {},
}),
});
const command = new PublishCommand({
TargetArn: platformArn,
Message: message,
MessageStructure: 'json',
MessageAttributes: {
'AWS.SNS.MOBILE.APNS.TOPIC': {
DataType: 'String',
StringValue: notification.token,
},
'AWS.SNS.MOBILE.GCM.TOPIC': {
DataType: 'String',
StringValue: notification.token,
},
},
});
const response = await this.sns.send(command);
return {
success: true,
messageId: response.MessageId,
};
} catch (error: any) {
console.error('AWS SNS error:', error);
return {
success: false,
error: error.message,
};
}
}
}

View File

@@ -0,0 +1,53 @@
/**
* Base interface for push notification providers
*/
export interface PushNotification {
token: string;
title: string;
body: string;
data?: Record<string, any>;
platform?: 'ios' | 'android' | 'web';
}
export interface PushNotificationResult {
success: boolean;
messageId?: string;
error?: string;
}
export interface IPushNotificationProvider {
name: string;
enabled: boolean;
sendNotification(notification: PushNotification): Promise<PushNotificationResult>;
sendBatchNotifications(notifications: PushNotification[]): Promise<PushNotificationResult[]>;
}
export abstract class BasePushProvider implements IPushNotificationProvider {
name: string;
enabled: boolean;
constructor(name: string, enabled: boolean = true) {
this.name = name;
this.enabled = enabled;
}
abstract sendNotification(notification: PushNotification): Promise<PushNotificationResult>;
async sendBatchNotifications(notifications: PushNotification[]): Promise<PushNotificationResult[]> {
const results: PushNotificationResult[] = [];
for (const notification of notifications) {
try {
const result = await this.sendNotification(notification);
results.push(result);
} catch (error: any) {
results.push({
success: false,
error: error.message,
});
}
}
return results;
}
}

View File

@@ -0,0 +1,104 @@
import { IPushNotificationProvider } from './base';
import { OneSignalProvider } from './onesignal';
import { AWSSNSProvider } from './aws-sns';
import { NativePushProvider } from './native';
import { PusherBeamsProvider } from './pusher';
import { PushNotificationService } from '../push-notifications';
export type PushProviderType = 'firebase' | 'onesignal' | 'aws-sns' | 'native' | 'pusher';
export class PushProviderFactory {
static createProvider(type: PushProviderType): IPushNotificationProvider {
switch (type) {
case 'onesignal':
return new OneSignalProvider();
case 'aws-sns':
return new AWSSNSProvider();
case 'native':
return new NativePushProvider();
case 'pusher':
return new PusherBeamsProvider();
case 'firebase':
default:
// Return Firebase as default (wrapped in adapter)
return new FirebaseAdapter();
}
}
static getAvailableProviders(): PushProviderType[] {
const providers: PushProviderType[] = [];
if (process.env.ONESIGNAL_APP_ID && process.env.ONESIGNAL_API_KEY) {
providers.push('onesignal');
}
if (process.env.AWS_SNS_IOS_ARN && process.env.AWS_SNS_ANDROID_ARN) {
providers.push('aws-sns');
}
if (process.env.FCM_SERVER_KEY || process.env.APNS_KEY_ID) {
providers.push('native');
}
if (process.env.PUSHER_BEAMS_INSTANCE_ID && process.env.PUSHER_BEAMS_SECRET_KEY) {
providers.push('pusher');
}
if (process.env.FIREBASE_SERVICE_ACCOUNT) {
providers.push('firebase');
}
return providers;
}
}
/**
* Adapter to wrap existing Firebase service as IPushNotificationProvider
*/
class FirebaseAdapter implements IPushNotificationProvider {
name = 'Firebase';
enabled: boolean;
private service: PushNotificationService;
constructor() {
this.service = new PushNotificationService();
this.enabled = !!process.env.FIREBASE_SERVICE_ACCOUNT;
}
async sendNotification(notification: any): Promise<any> {
if (!this.enabled) {
return {
success: false,
error: 'Firebase not configured',
};
}
try {
await this.service.sendNotification(notification);
return {
success: true,
};
} catch (error: any) {
return {
success: false,
error: error.message,
};
}
}
async sendBatchNotifications(notifications: any[]): Promise<any[]> {
if (!this.enabled) {
return notifications.map(() => ({
success: false,
error: 'Firebase not configured',
}));
}
try {
await this.service.sendBatchNotifications(notifications);
return notifications.map(() => ({ success: true }));
} catch (error: any) {
return notifications.map(() => ({
success: false,
error: error.message,
}));
}
}
}

View File

@@ -0,0 +1,146 @@
import apn from 'apn';
import axios from 'axios';
import { BasePushProvider, PushNotification, PushNotificationResult } from './base';
export class NativePushProvider extends BasePushProvider {
private apnProvider: apn.Provider | null = null;
private fcmServerKey: string;
private apnsBundleId: string;
private apnsKeyId?: string;
private apnsTeamId?: string;
private apnsKeyPath?: string;
constructor() {
const fcmConfigured = !!process.env.FCM_SERVER_KEY;
const apnsConfigured = !!(
process.env.APNS_KEY_ID &&
process.env.APNS_TEAM_ID &&
process.env.APNS_KEY_PATH &&
process.env.APNS_BUNDLE_ID
);
super('Native Push', fcmConfigured || apnsConfigured);
this.fcmServerKey = process.env.FCM_SERVER_KEY || '';
this.apnsBundleId = process.env.APNS_BUNDLE_ID || '';
if (apnsConfigured) {
this.apnsKeyId = process.env.APNS_KEY_ID;
this.apnsTeamId = process.env.APNS_TEAM_ID;
this.apnsKeyPath = process.env.APNS_KEY_PATH;
try {
this.apnProvider = new apn.Provider({
token: {
key: this.apnsKeyPath!,
keyId: this.apnsKeyId!,
teamId: this.apnsTeamId!,
},
production: process.env.NODE_ENV === 'production',
});
} catch (error) {
console.error('Failed to initialize APNs:', error);
}
}
}
async sendNotification(notification: PushNotification): Promise<PushNotificationResult> {
if (!this.enabled) {
return {
success: false,
error: 'Native push not configured',
};
}
const platform = notification.platform || 'android';
if (platform === 'ios') {
return this.sendToIOS(notification);
} else {
return this.sendToAndroid(notification);
}
}
private async sendToIOS(notification: PushNotification): Promise<PushNotificationResult> {
if (!this.apnProvider) {
return {
success: false,
error: 'APNs not configured',
};
}
try {
const apnNotification = new apn.Notification();
apnNotification.alert = {
title: notification.title,
body: notification.body,
};
apnNotification.topic = this.apnsBundleId;
apnNotification.payload = notification.data || {};
apnNotification.sound = 'default';
apnNotification.badge = 1;
const result = await this.apnProvider.send(apnNotification, notification.token);
if (result.failed.length > 0) {
return {
success: false,
error: result.failed[0].response?.reason || 'APNs delivery failed',
};
}
return {
success: true,
messageId: result.sent[0]?.device || notification.token,
};
} catch (error: any) {
console.error('APNs error:', error);
return {
success: false,
error: error.message,
};
}
}
private async sendToAndroid(notification: PushNotification): Promise<PushNotificationResult> {
if (!this.fcmServerKey) {
return {
success: false,
error: 'FCM server key not configured',
};
}
try {
const response = await axios.post(
'https://fcm.googleapis.com/fcm/send',
{
to: notification.token,
notification: {
title: notification.title,
body: notification.body,
sound: 'default',
},
data: notification.data || {},
},
{
headers: {
'Content-Type': 'application/json',
Authorization: `key=${this.fcmServerKey}`,
},
}
);
return {
success: true,
messageId: response.data.message_id || response.data.multicast_id?.toString(),
};
} catch (error: any) {
console.error('FCM error:', error.response?.data || error.message);
return {
success: false,
error: error.response?.data?.error || error.message,
};
}
}
}

View File

@@ -0,0 +1,100 @@
import axios from 'axios';
import { BasePushProvider, PushNotification, PushNotificationResult } from './base';
export class OneSignalProvider extends BasePushProvider {
private appId: string;
private apiKey: string;
private baseUrl = 'https://onesignal.com/api/v1';
constructor(appId?: string, apiKey?: string) {
super('OneSignal', !!appId && !!apiKey);
this.appId = appId || process.env.ONESIGNAL_APP_ID || '';
this.apiKey = apiKey || process.env.ONESIGNAL_API_KEY || '';
}
async sendNotification(notification: PushNotification): Promise<PushNotificationResult> {
if (!this.enabled) {
return {
success: false,
error: 'OneSignal not configured',
};
}
try {
const response = await axios.post(
`${this.baseUrl}/notifications`,
{
app_id: this.appId,
include_player_ids: [notification.token],
headings: { en: notification.title },
contents: { en: notification.body },
data: notification.data || {},
...(notification.platform === 'ios' && {
ios_badgeType: 'Increase',
ios_badgeCount: 1,
}),
},
{
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.apiKey}`,
},
}
);
return {
success: true,
messageId: response.data.id,
};
} catch (error: any) {
console.error('OneSignal error:', error.response?.data || error.message);
return {
success: false,
error: error.response?.data?.errors?.[0] || error.message,
};
}
}
async sendBatchNotifications(notifications: PushNotification[]): Promise<PushNotificationResult[]> {
if (!this.enabled) {
return notifications.map(() => ({
success: false,
error: 'OneSignal not configured',
}));
}
try {
// OneSignal supports batch via segments or multiple player IDs
const playerIds = notifications.map(n => n.token);
const response = await axios.post(
`${this.baseUrl}/notifications`,
{
app_id: this.appId,
include_player_ids: playerIds,
headings: { en: notifications[0]?.title || 'Notification' },
contents: { en: notifications[0]?.body || '' },
data: notifications[0]?.data || {},
},
{
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.apiKey}`,
},
}
);
return notifications.map(() => ({
success: true,
messageId: response.data.id,
}));
} catch (error: any) {
console.error('OneSignal batch error:', error.response?.data || error.message);
return notifications.map(() => ({
success: false,
error: error.response?.data?.errors?.[0] || error.message,
}));
}
}
}

View File

@@ -0,0 +1,76 @@
import axios from 'axios';
import { BasePushProvider, PushNotification, PushNotificationResult } from './base';
export class PusherBeamsProvider extends BasePushProvider {
private instanceId: string;
private secretKey: string;
private baseUrl: string;
constructor(instanceId?: string, secretKey?: string) {
super('Pusher Beams', !!instanceId && !!secretKey);
this.instanceId = instanceId || process.env.PUSHER_BEAMS_INSTANCE_ID || '';
this.secretKey = secretKey || process.env.PUSHER_BEAMS_SECRET_KEY || '';
this.baseUrl = `https://${this.instanceId}.pushnotifications.pusher.com`;
}
async sendNotification(notification: PushNotification): Promise<PushNotificationResult> {
if (!this.enabled) {
return {
success: false,
error: 'Pusher Beams not configured',
};
}
try {
const response = await axios.post(
`${this.baseUrl}/publish_api/v1/instances/${this.instanceId}/publishes`,
{
interests: [notification.token], // Using token as interest for simplicity
web: {
notification: {
title: notification.title,
body: notification.body,
},
data: notification.data || {},
},
fcm: {
notification: {
title: notification.title,
body: notification.body,
},
data: notification.data || {},
},
apns: {
aps: {
alert: {
title: notification.title,
body: notification.body,
},
sound: 'default',
badge: 1,
},
data: notification.data || {},
},
},
{
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.secretKey}`,
},
}
);
return {
success: true,
messageId: response.data.publishId,
};
} catch (error: any) {
console.error('Pusher Beams error:', error.response?.data || error.message);
return {
success: false,
error: error.response?.data?.error || error.message,
};
}
}
}

View File

@@ -0,0 +1,250 @@
import { ComplianceService, AMLResult } from './compliance';
import { PrismaClient } from '@prisma/client';
import { SARGenerator } from './sar-generator';
import { CTRGenerator } from './ctr-generator';
const prisma = new PrismaClient();
export interface ScreeningResult {
address: string;
riskScore: number;
sanctions: boolean;
passed: boolean;
providers: string[];
timestamp: Date;
action: 'allow' | 'block' | 'review';
}
export interface ScreeningQueueItem {
id: string;
address: string;
transactionHash?: string;
amount?: string;
priority: 'low' | 'medium' | 'high';
status: 'pending' | 'processing' | 'completed' | 'failed';
createdAt: Date;
}
export class RealTimeScreeningService {
private complianceService: ComplianceService;
private sarGenerator: SARGenerator;
private ctrGenerator: CTRGenerator;
private screeningQueue: ScreeningQueueItem[] = [];
private isProcessing: boolean = false;
constructor(
complianceService: ComplianceService,
sarGenerator: SARGenerator,
ctrGenerator: CTRGenerator
) {
this.complianceService = complianceService;
this.sarGenerator = sarGenerator;
this.ctrGenerator = ctrGenerator;
}
/**
* Screen address in real-time
*/
async screenAddress(address: string): Promise<ScreeningResult> {
// Check all AML providers
const providers = ['chainalysis', 'elliptic', 'ciphertrace', 'trm'];
const results: AMLResult[] = [];
for (const providerName of providers) {
try {
const result = await this.complianceService.verifyAML(address, providerName);
results.push(result);
} catch (error) {
console.error(`Error screening with ${providerName}:`, error);
}
}
// Aggregate results
const maxRiskScore = Math.max(...results.map((r) => r.riskScore));
const hasSanctions = results.some((r) => r.sanctions);
const allPassed = results.every((r) => r.passed);
// Determine action
let action: 'allow' | 'block' | 'review' = 'allow';
if (hasSanctions || maxRiskScore >= 90) {
action = 'block';
} else if (maxRiskScore >= 70) {
action = 'review';
}
const screeningResult: ScreeningResult = {
address,
riskScore: maxRiskScore,
sanctions: hasSanctions,
passed: allPassed && !hasSanctions,
providers: results.map((r) => r.provider),
timestamp: new Date(),
action,
};
// Store result
await prisma.screeningResult.create({
data: {
address,
riskScore: maxRiskScore,
sanctions: hasSanctions,
passed: allPassed && !hasSanctions,
providers: results.map((r) => r.provider),
action,
timestamp: new Date(),
},
});
// Auto-block if sanctions detected
if (hasSanctions) {
await this.handleSanctionsDetected(address, screeningResult);
}
return screeningResult;
}
/**
* Screen transaction
*/
async screenTransaction(
transactionHash: string,
fromAddress: string,
toAddress: string,
amount: string,
currency: string
): Promise<{ from: ScreeningResult; to: ScreeningResult; requiresCTR: boolean }> {
const [fromResult, toResult] = await Promise.all([
this.screenAddress(fromAddress),
this.screenAddress(toAddress),
]);
// Check CTR threshold
const requiresCTR = await this.ctrGenerator.checkAndGenerate(
transactionHash,
fromAddress,
amount,
currency,
'transfer'
);
// Auto-generate SAR if high risk
if (fromResult.riskScore >= 70 || toResult.riskScore >= 70) {
await this.sarGenerator.autoGenerateForHighRisk(
transactionHash,
fromAddress,
amount,
Math.max(fromResult.riskScore, toResult.riskScore)
);
}
return {
from: fromResult,
to: toResult,
requiresCTR: !!requiresCTR,
};
}
/**
* Add to screening queue
*/
async queueScreening(
address: string,
transactionHash?: string,
amount?: string,
priority: 'low' | 'medium' | 'high' = 'medium'
): Promise<string> {
const item: ScreeningQueueItem = {
id: `screening_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
address,
transactionHash,
amount,
priority,
status: 'pending',
createdAt: new Date(),
};
this.screeningQueue.push(item);
this.processQueue();
return item.id;
}
/**
* Process screening queue
*/
private async processQueue() {
if (this.isProcessing) return;
this.isProcessing = true;
while (this.screeningQueue.length > 0) {
// Sort by priority
this.screeningQueue.sort((a, b) => {
const priorityOrder = { high: 3, medium: 2, low: 1 };
return priorityOrder[b.priority] - priorityOrder[a.priority];
});
const item = this.screeningQueue.shift();
if (!item) break;
try {
item.status = 'processing';
await this.screenAddress(item.address);
item.status = 'completed';
} catch (error) {
console.error(`Error processing screening for ${item.address}:`, error);
item.status = 'failed';
}
}
this.isProcessing = false;
}
/**
* Handle sanctions detected
*/
private async handleSanctionsDetected(
address: string,
result: ScreeningResult
): Promise<void> {
// Block address in compliance system
await prisma.complianceRecord.upsert({
where: { userAddress: address },
update: {
amlVerified: false,
lastAMLUpdate: new Date(),
},
create: {
userAddress: address,
complianceMode: 'Regulated',
amlVerified: false,
lastAMLUpdate: new Date(),
},
});
// Create alert
await prisma.systemAlert.create({
data: {
alertType: 'SANCTIONS_DETECTED',
severity: 'critical',
message: `Sanctions detected for address ${address}`,
metadata: {
address,
riskScore: result.riskScore,
providers: result.providers,
} as any,
},
});
}
/**
* Get screening history
*/
async getScreeningHistory(address: string, limit: number = 100) {
return await prisma.screeningResult.findMany({
where: { address },
orderBy: { timestamp: 'desc' },
take: limit,
});
}
}

View File

@@ -0,0 +1,230 @@
import { PrismaClient } from '@prisma/client';
import { ComplianceService } from './compliance';
const prisma = new PrismaClient();
export interface SARReport {
id: string;
reportId: string;
transactionHash: string;
userAddress: string;
amount: string;
reason: string;
status: 'draft' | 'submitted' | 'acknowledged' | 'rejected';
submittedAt?: Date;
jurisdiction: string;
createdAt: Date;
}
export interface CTRReport {
id: string;
reportId: string;
transactionHash: string;
userAddress: string;
amount: string;
currency: string;
transactionType: string;
status: 'draft' | 'submitted' | 'acknowledged';
submittedAt?: Date;
jurisdiction: string;
createdAt: Date;
}
export class RegulatoryReportingService {
private complianceService: ComplianceService;
constructor(complianceService: ComplianceService) {
this.complianceService = complianceService;
}
/**
* Generate Suspicious Activity Report (SAR)
*/
async generateSAR(
transactionHash: string,
userAddress: string,
amount: string,
reason: string,
jurisdiction: string = 'US'
): Promise<SARReport> {
const reportId = `SAR-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const sar = await prisma.sARReport.create({
data: {
reportId,
transactionHash,
userAddress,
amount,
reason,
status: 'draft',
jurisdiction,
},
});
return {
id: sar.id,
reportId: sar.reportId,
transactionHash: sar.transactionHash,
userAddress: sar.userAddress,
amount: sar.amount,
reason: sar.reason,
status: sar.status as any,
submittedAt: sar.submittedAt || undefined,
jurisdiction: sar.jurisdiction,
createdAt: sar.createdAt,
};
}
/**
* Submit SAR to regulatory authority
*/
async submitSAR(sarId: string): Promise<void> {
const sar = await prisma.sARReport.findUnique({
where: { id: sarId },
});
if (!sar) {
throw new Error('SAR not found');
}
if (sar.status !== 'draft') {
throw new Error('SAR already submitted');
}
// In production, this would submit to FinCEN or relevant authority
// For now, mark as submitted
await prisma.sARReport.update({
where: { id: sarId },
data: {
status: 'submitted',
submittedAt: new Date(),
},
});
}
/**
* Generate Currency Transaction Report (CTR)
*/
async generateCTR(
transactionHash: string,
userAddress: string,
amount: string,
currency: string,
transactionType: string,
jurisdiction: string = 'US'
): Promise<CTRReport> {
const reportId = `CTR-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const ctr = await prisma.cTRReport.create({
data: {
reportId,
transactionHash,
userAddress,
amount,
currency,
transactionType,
status: 'draft',
jurisdiction,
},
});
return {
id: ctr.id,
reportId: ctr.reportId,
transactionHash: ctr.transactionHash,
userAddress: ctr.userAddress,
amount: ctr.amount,
currency: ctr.currency,
transactionType: ctr.transactionType,
status: ctr.status as any,
submittedAt: ctr.submittedAt || undefined,
jurisdiction: ctr.jurisdiction,
createdAt: ctr.createdAt,
};
}
/**
* Submit CTR to regulatory authority
*/
async submitCTR(ctrId: string): Promise<void> {
const ctr = await prisma.cTRReport.findUnique({
where: { id: ctrId },
});
if (!ctr) {
throw new Error('CTR not found');
}
if (ctr.status !== 'draft') {
throw new Error('CTR already submitted');
}
// In production, this would submit to FinCEN or relevant authority
await prisma.cTRReport.update({
where: { id: ctrId },
data: {
status: 'submitted',
submittedAt: new Date(),
},
});
}
/**
* Get all SAR reports
*/
async getAllSARs(status?: string): Promise<SARReport[]> {
const where = status ? { status } : {};
const sars = await prisma.sARReport.findMany({
where,
orderBy: { createdAt: 'desc' },
});
return sars.map((sar) => ({
id: sar.id,
reportId: sar.reportId,
transactionHash: sar.transactionHash,
userAddress: sar.userAddress,
amount: sar.amount,
reason: sar.reason,
status: sar.status as any,
submittedAt: sar.submittedAt || undefined,
jurisdiction: sar.jurisdiction,
createdAt: sar.createdAt,
}));
}
/**
* Get all CTR reports
*/
async getAllCTRs(status?: string): Promise<CTRReport[]> {
const where = status ? { status } : {};
const ctrs = await prisma.cTRReport.findMany({
where,
orderBy: { createdAt: 'desc' },
});
return ctrs.map((ctr) => ({
id: ctr.id,
reportId: ctr.reportId,
transactionHash: ctr.transactionHash,
userAddress: ctr.userAddress,
amount: ctr.amount,
currency: ctr.currency,
transactionType: ctr.transactionType,
status: ctr.status as any,
submittedAt: ctr.submittedAt || undefined,
jurisdiction: ctr.jurisdiction,
createdAt: ctr.createdAt,
}));
}
/**
* Check if transaction requires CTR (threshold monitoring)
*/
async checkCTRThreshold(amount: string, currency: string): Promise<boolean> {
// US CTR threshold is $10,000
const threshold = currency === 'USD' ? '10000' : '0';
return BigInt(amount) >= BigInt(threshold);
}
}

View File

@@ -0,0 +1,142 @@
import { PrismaClient } from '@prisma/client';
import { SARGenerator } from './sar-generator';
import { CTRGenerator } from './ctr-generator';
const prisma = new PrismaClient();
export interface SubmissionResult {
success: boolean;
submissionId?: string;
error?: string;
timestamp: Date;
}
export class ReportSubmissionService {
private sarGenerator: SARGenerator;
private ctrGenerator: CTRGenerator;
constructor(sarGenerator: SARGenerator, ctrGenerator: CTRGenerator) {
this.sarGenerator = sarGenerator;
this.ctrGenerator = ctrGenerator;
}
/**
* Submit SAR to FinCEN (or relevant authority)
*/
async submitSAR(sarId: string): Promise<SubmissionResult> {
try {
const formattedSAR = await this.sarGenerator.formatSARForSubmission(sarId);
// In production, this would submit to FinCEN BSA E-Filing system
// For now, simulate submission
const submissionId = `FINCEN-${Date.now()}`;
await prisma.sARReport.update({
where: { id: sarId },
data: {
status: 'submitted',
submittedAt: new Date(),
},
});
// Log submission
await prisma.auditTrail.create({
data: {
userAddress: 'system',
action: 'SAR_SUBMITTED',
details: {
sarId,
submissionId,
timestamp: new Date().toISOString(),
} as any,
},
});
return {
success: true,
submissionId,
timestamp: new Date(),
};
} catch (error: any) {
return {
success: false,
error: error.message,
timestamp: new Date(),
};
}
}
/**
* Submit CTR to FinCEN
*/
async submitCTR(ctrId: string): Promise<SubmissionResult> {
try {
const formattedCTR = await this.ctrGenerator.formatCTRForSubmission(ctrId);
// In production, this would submit to FinCEN BSA E-Filing system
const submissionId = `FINCEN-${Date.now()}`;
await prisma.cTRReport.update({
where: { id: ctrId },
data: {
status: 'submitted',
submittedAt: new Date(),
},
});
// Log submission
await prisma.auditTrail.create({
data: {
userAddress: 'system',
action: 'CTR_SUBMITTED',
details: {
ctrId,
submissionId,
timestamp: new Date().toISOString(),
} as any,
},
});
return {
success: true,
submissionId,
timestamp: new Date(),
};
} catch (error: any) {
return {
success: false,
error: error.message,
timestamp: new Date(),
};
}
}
/**
* Batch submit multiple reports
*/
async batchSubmitSARs(sarIds: string[]): Promise<SubmissionResult[]> {
const results: SubmissionResult[] = [];
for (const sarId of sarIds) {
const result = await this.submitSAR(sarId);
results.push(result);
}
return results;
}
/**
* Batch submit multiple CTRs
*/
async batchSubmitCTRs(ctrIds: string[]): Promise<SubmissionResult[]> {
const results: SubmissionResult[] = [];
for (const ctrId of ctrIds) {
const result = await this.submitCTR(ctrId);
results.push(result);
}
return results;
}
}

View File

@@ -0,0 +1,102 @@
import { RegulatoryReportingService } from './regulatory-reporting';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export class SARGenerator {
private reportingService: RegulatoryReportingService;
constructor(reportingService: RegulatoryReportingService) {
this.reportingService = reportingService;
}
/**
* Generate SAR from suspicious transaction
*/
async generateFromTransaction(
transactionHash: string,
userAddress: string,
amount: string,
suspiciousReasons: string[]
): Promise<string> {
const reason = suspiciousReasons.join('; ');
const sar = await this.reportingService.generateSAR(
transactionHash,
userAddress,
amount,
reason
);
return sar.id;
}
/**
* Auto-generate SAR for high-risk transactions
*/
async autoGenerateForHighRisk(
transactionHash: string,
userAddress: string,
amount: string,
riskScore: number
): Promise<string | null> {
if (riskScore < 70) {
return null; // Not high enough risk
}
const reasons: string[] = [];
if (riskScore >= 90) {
reasons.push('Very high risk score');
}
if (riskScore >= 80) {
reasons.push('Potential sanctions match');
}
if (riskScore >= 70) {
reasons.push('Elevated risk indicators');
}
return await this.generateFromTransaction(
transactionHash,
userAddress,
amount,
reasons
);
}
/**
* Format SAR for submission (FinCEN format)
*/
async formatSARForSubmission(sarId: string): Promise<any> {
const sar = await prisma.sARReport.findUnique({
where: { id: sarId },
});
if (!sar) {
throw new Error('SAR not found');
}
// Format according to FinCEN SAR requirements
return {
reportType: 'SAR',
reportId: sar.reportId,
filerInfo: {
name: process.env.COMPANY_NAME || 'ASLE Platform',
ein: process.env.COMPANY_EIN || '',
},
subjectInfo: {
address: sar.userAddress,
transactionHash: sar.transactionHash,
},
transactionInfo: {
amount: sar.amount,
date: sar.createdAt.toISOString(),
},
suspiciousActivity: {
description: sar.reason,
date: sar.createdAt.toISOString(),
},
jurisdiction: sar.jurisdiction,
};
}
}

View File

@@ -0,0 +1,56 @@
/**
* Secret management service
* In production, integrate with AWS Secrets Manager, HashiCorp Vault, etc.
*/
export class SecretManager {
private static cache: Map<string, { value: any; expiresAt: number }> = new Map();
private static CACHE_TTL = 5 * 60 * 1000; // 5 minutes
/**
* Get secret value
* In production, fetch from secret management service
*/
static async getSecret(key: string): Promise<string | null> {
// Check cache first
const cached = this.cache.get(key);
if (cached && cached.expiresAt > Date.now()) {
return cached.value;
}
// In production, fetch from AWS Secrets Manager, Vault, etc.
// For now, use environment variables
const value = process.env[key] || null;
// Cache the value
if (value) {
this.cache.set(key, {
value,
expiresAt: Date.now() + this.CACHE_TTL,
});
}
return value;
}
/**
* Rotate secret (placeholder for production implementation)
*/
static async rotateSecret(key: string): Promise<void> {
// In production, implement secret rotation logic
// This would involve:
// 1. Generate new secret
// 2. Update in secret manager
// 3. Update in application
// 4. Invalidate old secret after grace period
console.log(`Secret rotation for ${key} - implement in production`);
}
/**
* Clear cache
*/
static clearCache(): void {
this.cache.clear();
}
}

View File

@@ -0,0 +1,199 @@
import axios from 'axios';
const SNAPSHOT_API_URL = 'https://hub.snapshot.org/api';
export interface SnapshotProposal {
id: string;
title: string;
body: string;
choices: string[];
start: number;
end: number;
snapshot: string;
state: string;
author: string;
space: {
id: string;
name: string;
};
scores: number[];
scores_by_strategy: any[];
scores_total: number;
scores_updated: number;
plugins: any;
network: string;
type: string;
strategies: any[];
}
export interface SnapshotVote {
id: string;
voter: string;
vp: number;
choice: number | number[];
proposal: {
id: string;
};
created: number;
}
export class SnapshotService {
private spaceId: string;
constructor(spaceId: string = 'asle.eth') {
this.spaceId = spaceId;
}
/**
* Create proposal on Snapshot
*/
async createProposal(
title: string,
body: string,
choices: string[],
start: number,
end: number,
snapshot: number,
metadata: any = {}
): Promise<SnapshotProposal> {
// In production, this would require signing with wallet
// For now, return structure
const proposal: SnapshotProposal = {
id: `proposal_${Date.now()}`,
title,
body,
choices,
start,
end,
snapshot: snapshot.toString(),
state: 'pending',
author: metadata.author || '',
space: {
id: this.spaceId,
name: 'ASLE',
},
scores: [],
scores_by_strategy: [],
scores_total: 0,
scores_updated: 0,
plugins: metadata.plugins || {},
network: metadata.network || '1',
type: metadata.type || 'single-choice',
strategies: metadata.strategies || [],
};
return proposal;
}
/**
* Get proposal from Snapshot
*/
async getProposal(proposalId: string): Promise<SnapshotProposal | null> {
try {
const response = await axios.get(`${SNAPSHOT_API_URL}/${this.spaceId}/proposal/${proposalId}`);
return response.data;
} catch (error: any) {
if (error.response?.status === 404) {
return null;
}
throw new Error(`Failed to fetch Snapshot proposal: ${error.message}`);
}
}
/**
* Get all proposals for space
*/
async getProposals(limit: number = 20, skip: number = 0): Promise<SnapshotProposal[]> {
try {
const response = await axios.get(`${SNAPSHOT_API_URL}/${this.spaceId}/proposals`, {
params: {
limit,
skip,
},
});
return response.data || [];
} catch (error: any) {
console.error('Error fetching Snapshot proposals:', error);
return [];
}
}
/**
* Get votes for a proposal
*/
async getVotes(proposalId: string): Promise<SnapshotVote[]> {
try {
const response = await axios.get(`${SNAPSHOT_API_URL}/${this.spaceId}/proposal/${proposalId}/votes`);
return response.data || [];
} catch (error: any) {
console.error('Error fetching Snapshot votes:', error);
return [];
}
}
/**
* Vote on Snapshot proposal
*/
async vote(
proposalId: string,
choice: number | number[],
voter: string,
signature: string
): Promise<SnapshotVote> {
// In production, this would submit vote to Snapshot
// For now, return structure
const vote: SnapshotVote = {
id: `vote_${Date.now()}`,
voter,
vp: 0, // Voting power would be calculated
choice,
proposal: {
id: proposalId,
},
created: Math.floor(Date.now() / 1000),
};
return vote;
}
/**
* Sync Snapshot proposal to local governance
*/
async syncProposalToLocal(proposalId: string): Promise<any> {
const proposal = await this.getProposal(proposalId);
if (!proposal) {
throw new Error('Proposal not found on Snapshot');
}
// Map Snapshot proposal to local proposal format
return {
snapshotId: proposal.id,
title: proposal.title,
description: proposal.body,
choices: proposal.choices,
startTime: new Date(proposal.start * 1000),
endTime: new Date(proposal.end * 1000),
state: proposal.state,
scores: proposal.scores,
scoresTotal: proposal.scores_total,
};
}
/**
* Get voting power for address
*/
async getVotingPower(address: string, snapshot: number): Promise<number> {
try {
const response = await axios.post(`${SNAPSHOT_API_URL}/scoring`, {
address,
space: this.spaceId,
snapshot,
});
return response.data?.vp || 0;
} catch (error: any) {
console.error('Error getting voting power:', error);
return 0;
}
}
}

View File

@@ -0,0 +1,87 @@
/**
* Solana-specific adapter for ASLE operations
* Integrates with Solana programs and Wormhole bridge
*/
export interface SolanaConfig {
rpcUrl: string;
programId: string;
wormholeBridge?: string;
}
export interface SolanaTransaction {
signature: string;
slot: number;
status: 'confirmed' | 'finalized' | 'failed';
}
export class SolanaAdapter {
private config: SolanaConfig;
private connection: any; // Would be @solana/web3.js Connection
constructor(config: SolanaConfig) {
this.config = config;
// Initialize Solana connection
// this.connection = new Connection(config.rpcUrl);
}
/**
* Create liquidity pool on Solana
*/
async createPool(baseToken: string, quoteToken: string, initialLiquidity: bigint): Promise<string> {
// Interact with Solana program
// Would use @solana/web3.js to send transaction
return `solana_pool_${Date.now()}`;
}
/**
* Add liquidity to Solana pool
*/
async addLiquidity(poolId: string, amount: bigint): Promise<SolanaTransaction> {
// Execute Solana transaction
return {
signature: `sig_${Date.now()}`,
slot: 0,
status: 'confirmed',
};
}
/**
* Bridge assets from EVM to Solana via Wormhole
*/
async bridgeFromEVM(evmChainId: number, amount: bigint, tokenAddress: string): Promise<string> {
// Use Wormhole to bridge assets
// 1. Lock assets on EVM chain
// 2. Emit Wormhole message
// 3. Redeem on Solana
return `bridge_tx_${Date.now()}`;
}
/**
* Bridge assets from Solana to EVM via Wormhole
*/
async bridgeToEVM(targetChainId: number, amount: bigint, tokenAddress: string): Promise<string> {
// Use Wormhole to bridge assets
return `bridge_tx_${Date.now()}`;
}
/**
* Get Solana account balance
*/
async getBalance(address: string, tokenMint?: string): Promise<bigint> {
// Query Solana account balance
return BigInt(0);
}
/**
* Get pool reserves
*/
async getPoolReserves(poolId: string): Promise<{ base: bigint; quote: bigint }> {
// Query Solana program state
return {
base: BigInt(0),
quote: BigInt(0),
};
}
}

View File

@@ -0,0 +1,87 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export interface SystemConfigData {
key: string;
value: any;
description?: string;
category?: string;
}
export class SystemConfigService {
/**
* Get config value
*/
async getConfig(key: string): Promise<any> {
const config = await prisma.systemConfig.findUnique({
where: { key },
});
return config?.value || null;
}
/**
* Set config value
*/
async setConfig(data: SystemConfigData, updatedBy?: string): Promise<void> {
await prisma.systemConfig.upsert({
where: { key: data.key },
update: {
value: data.value,
description: data.description,
category: data.category,
updatedBy,
},
create: {
key: data.key,
value: data.value,
description: data.description,
category: data.category || 'general',
updatedBy,
},
});
}
/**
* Get all configs by category
*/
async getConfigsByCategory(category: string): Promise<SystemConfigData[]> {
const configs = await prisma.systemConfig.findMany({
where: { category },
});
return configs.map(c => ({
key: c.key,
value: c.value,
description: c.description || undefined,
category: c.category,
}));
}
/**
* Get all configs
*/
async getAllConfigs(): Promise<SystemConfigData[]> {
const configs = await prisma.systemConfig.findMany({
orderBy: { category: 'asc' },
});
return configs.map(c => ({
key: c.key,
value: c.value,
description: c.description || undefined,
category: c.category,
}));
}
/**
* Delete config
*/
async deleteConfig(key: string): Promise<void> {
await prisma.systemConfig.delete({
where: { key },
});
}
}

View File

@@ -0,0 +1,92 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export interface WhiteLabelConfigData {
name: string;
domain: string;
logoUrl?: string;
primaryColor?: string;
secondaryColor?: string;
theme?: any;
features?: string[];
}
export class WhiteLabelService {
/**
* Create white-label config
*/
async createConfig(data: WhiteLabelConfigData) {
return prisma.whiteLabelConfig.create({
data: {
name: data.name,
domain: data.domain,
logoUrl: data.logoUrl,
primaryColor: data.primaryColor,
secondaryColor: data.secondaryColor,
theme: data.theme || {},
features: data.features || [],
},
});
}
/**
* Get config by domain
*/
async getConfigByDomain(domain: string) {
return prisma.whiteLabelConfig.findUnique({
where: { domain },
});
}
/**
* Get all configs
*/
async getAllConfigs() {
return prisma.whiteLabelConfig.findMany({
orderBy: { createdAt: 'desc' },
});
}
/**
* Update config
*/
async updateConfig(id: string, data: Partial<WhiteLabelConfigData>) {
return prisma.whiteLabelConfig.update({
where: { id },
data: {
...data,
theme: data.theme || undefined,
features: data.features || undefined,
},
});
}
/**
* Delete config
*/
async deleteConfig(id: string) {
return prisma.whiteLabelConfig.delete({
where: { id },
});
}
/**
* Toggle active status
*/
async toggleActive(id: string) {
const config = await prisma.whiteLabelConfig.findUnique({
where: { id },
});
if (!config) throw new Error('Config not found');
return prisma.whiteLabelConfig.update({
where: { id },
data: {
active: !config.active,
},
});
}
}

View File

@@ -0,0 +1,129 @@
/**
* Utility functions for processing chart data
*/
export const chartDataProcessor = {
processTimeSeriesData,
aggregateByPeriod,
calculatePercentageChange,
formatLargeNumber,
};
export interface TimeSeriesDataPoint {
timestamp: Date | string;
value: string | number;
label?: string;
}
export interface ChartData {
labels: string[];
datasets: {
label: string;
data: number[];
backgroundColor?: string;
borderColor?: string;
}[];
}
/**
* Process time series data for charts
*/
export function processTimeSeriesData(
data: TimeSeriesDataPoint[],
labelKey: string = 'value'
): ChartData {
const labels = data.map((point) => {
const date = typeof point.timestamp === 'string' ? new Date(point.timestamp) : point.timestamp;
return date.toISOString().split('T')[0];
});
const values = data.map((point) => {
const value = typeof point.value === 'string' ? parseFloat(point.value) : point.value;
return isNaN(value) ? 0 : value;
});
return {
labels,
datasets: [
{
label: labelKey,
data: values,
borderColor: 'rgb(59, 130, 246)',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
},
],
};
}
/**
* Aggregate data by time period
*/
export function aggregateByPeriod(
data: TimeSeriesDataPoint[],
period: 'hour' | 'day' | 'week' | 'month'
): TimeSeriesDataPoint[] {
const grouped = new Map<string, { sum: number; count: number }>();
for (const point of data) {
const date = typeof point.timestamp === 'string' ? new Date(point.timestamp) : point.timestamp;
const value = typeof point.value === 'string' ? parseFloat(point.value) : point.value;
let key: string;
switch (period) {
case 'hour':
key = date.toISOString().slice(0, 13) + ':00:00';
break;
case 'day':
key = date.toISOString().split('T')[0];
break;
case 'week':
const weekStart = new Date(date);
weekStart.setDate(date.getDate() - date.getDay());
key = weekStart.toISOString().split('T')[0];
break;
case 'month':
key = date.toISOString().slice(0, 7);
break;
default:
key = date.toISOString().split('T')[0];
}
if (!grouped.has(key)) {
grouped.set(key, { sum: 0, count: 0 });
}
const group = grouped.get(key)!;
group.sum += isNaN(value) ? 0 : value;
group.count += 1;
}
return Array.from(grouped.entries())
.map(([timestamp, { sum, count }]) => ({
timestamp,
value: sum / count, // Average
}))
.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
}
/**
* Calculate percentage change
*/
export function calculatePercentageChange(current: number, previous: number): number {
if (previous === 0) return current > 0 ? 100 : 0;
return ((current - previous) / previous) * 100;
}
/**
* Format large numbers for display
*/
export function formatLargeNumber(value: string | number): string {
const num = typeof value === 'string' ? parseFloat(value) : value;
if (isNaN(num)) return '0';
if (num >= 1e12) return (num / 1e12).toFixed(2) + 'T';
if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B';
if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M';
if (num >= 1e3) return (num / 1e3).toFixed(2) + 'K';
return num.toFixed(2);
}

View File

@@ -0,0 +1,67 @@
import { z } from 'zod';
/**
* Common validation schemas
*/
export const emailSchema = z.string().email('Invalid email address');
export const passwordSchema = z
.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
.regex(/[a-z]/, 'Password must contain at least one lowercase letter')
.regex(/[0-9]/, 'Password must contain at least one number')
.regex(/[^A-Za-z0-9]/, 'Password must contain at least one special character');
export const addressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address');
export const adminUserSchema = z.object({
email: emailSchema,
password: passwordSchema,
role: z.enum(['admin', 'super_admin', 'operator']).optional(),
permissions: z.array(z.string()).optional(),
});
export const systemConfigSchema = z.object({
key: z.string().min(1, 'Key is required'),
value: z.any(),
description: z.string().optional(),
category: z.string().optional(),
});
export const deploymentSchema = z.object({
name: z.string().min(1, 'Name is required'),
environment: z.enum(['staging', 'production', 'development']),
version: z.string().min(1, 'Version is required'),
config: z.record(z.any()),
});
export const whiteLabelSchema = z.object({
name: z.string().min(1, 'Name is required'),
domain: z.string().min(1, 'Domain is required').regex(/^[a-z0-9.-]+$/, 'Invalid domain format'),
logoUrl: z.string().url().optional(),
primaryColor: z.string().regex(/^#[0-9A-Fa-f]{6}$/, 'Invalid color format').optional(),
secondaryColor: z.string().regex(/^#[0-9A-Fa-f]{6}$/, 'Invalid color format').optional(),
theme: z.record(z.any()).optional(),
features: z.array(z.string()).optional(),
});
/**
* Validate and sanitize input
*/
export function validateInput<T>(schema: z.ZodSchema<T>, data: unknown): T {
return schema.parse(data);
}
/**
* Safe parse with error handling
*/
export function safeParse<T>(schema: z.ZodSchema<T>, data: unknown): { success: boolean; data?: T; error?: any } {
const result = schema.safeParse(data);
if (result.success) {
return { success: true, data: result.data };
}
return { success: false, error: result.error };
}

View File

@@ -0,0 +1,147 @@
import { WebSocketServer, WebSocket } from 'ws';
import { Server } from 'http';
import { AnalyticsService } from '../services/analytics';
export class WebSocketServerManager {
private wss: WebSocketServer;
private clients: Map<string, WebSocket> = new Map();
private subscriptions: Map<string, Set<string>> = new Map(); // type => Set<clientId>
private analyticsService: AnalyticsService;
constructor(server: Server) {
this.wss = new WebSocketServer({ server, path: '/ws' });
this.analyticsService = new AnalyticsService();
this.setup();
}
private setup() {
this.wss.on('connection', (ws: WebSocket, req) => {
const clientId = this.generateClientId();
this.clients.set(clientId, ws);
console.log(`WebSocket client connected: ${clientId}`);
ws.on('message', (message: Buffer) => {
try {
const data = JSON.parse(message.toString());
this.handleMessage(clientId, data);
} catch (error) {
console.error('Error parsing WebSocket message:', error);
ws.send(JSON.stringify({ error: 'Invalid message format' }));
}
});
ws.on('close', () => {
this.handleDisconnect(clientId);
});
ws.on('error', (error) => {
console.error(`WebSocket error for client ${clientId}:`, error);
this.handleDisconnect(clientId);
});
// Send welcome message
ws.send(JSON.stringify({
type: 'connected',
clientId,
timestamp: Date.now(),
}));
});
// Start broadcasting metrics
this.startMetricsBroadcast();
}
private generateClientId(): string {
return `client_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
private handleMessage(clientId: string, data: any) {
const { action, type } = data;
switch (action) {
case 'subscribe':
this.subscribe(clientId, type);
break;
case 'unsubscribe':
this.unsubscribe(clientId, type);
break;
case 'ping':
const ws = this.clients.get(clientId);
if (ws) {
ws.send(JSON.stringify({ type: 'pong', timestamp: Date.now() }));
}
break;
default:
console.warn(`Unknown action: ${action}`);
}
}
private subscribe(clientId: string, type: string) {
if (!this.subscriptions.has(type)) {
this.subscriptions.set(type, new Set());
}
this.subscriptions.get(type)!.add(clientId);
const ws = this.clients.get(clientId);
if (ws) {
ws.send(JSON.stringify({
type: 'subscribed',
subscriptionType: type,
timestamp: Date.now(),
}));
}
}
private unsubscribe(clientId: string, type: string) {
const subscribers = this.subscriptions.get(type);
if (subscribers) {
subscribers.delete(clientId);
}
}
private handleDisconnect(clientId: string) {
this.clients.delete(clientId);
// Remove from all subscriptions
for (const [type, subscribers] of this.subscriptions.entries()) {
subscribers.delete(clientId);
}
console.log(`WebSocket client disconnected: ${clientId}`);
}
private broadcast(type: string, data: any) {
const subscribers = this.subscriptions.get(type);
if (!subscribers || subscribers.size === 0) return;
const message = JSON.stringify({ type, data, timestamp: Date.now() });
subscribers.forEach((clientId) => {
const ws = this.clients.get(clientId);
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(message);
} else {
// Remove dead connections
subscribers.delete(clientId);
this.clients.delete(clientId);
}
});
}
private async startMetricsBroadcast() {
setInterval(async () => {
try {
const metrics = await this.analyticsService.calculateSystemMetrics();
this.broadcast('metrics', metrics);
} catch (error) {
console.error('Error broadcasting metrics:', error);
}
}, 30000); // Broadcast every 30 seconds
}
public broadcastCustom(type: string, data: any) {
this.broadcast(type, data);
}
}

18
backend/tsconfig.json Normal file
View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleResolution": "node"
},
"include": ["**/*.ts"],
"exclude": ["node_modules"]
}