diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..540fbce --- /dev/null +++ b/.env.example @@ -0,0 +1,16 @@ +# Database +POSTGRES_USER=gateway +POSTGRES_PASSWORD=gateway +POSTGRES_DB=gateway + +# RPC endpoints +ETHEREUM_RPC_WS= +SOLANA_RPC= +BTC_RPC= +FTM_RPC= +BSC_RPC= + +# API Keys +DEX_AGGREGATOR_KEY= +KYC_PROVIDER_KEY= +WEBHOOK_SECRET= diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..260a143 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: CI +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + service: [ingestion, swap-engine, wallet-manager, settlement, compliance, monitoring] + steps: + - uses: actions/checkout@v3 + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: '18' + - name: Install dependencies + run: | + cd services/${{ matrix.service }} + npm install + - name: Run tests + run: | + cd services/${{ matrix.service }} + npm test --if-present diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..deed335 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +.env diff --git a/README.md b/README.md new file mode 100644 index 0000000..e21c264 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# Multi-Chain Crypto Payment Gateway Scaffold + +This repository contains a minimal scaffold for a multi-service crypto payment gateway. + +## Services + +- **ingestion** – Generates invoices and QR codes, listens for chain payments. +- **swap-engine** – Calls DEX aggregators (1inch, LI.FI, OpenOcean, ParaSwap) for quotes/swaps. +- **wallet-manager** – Manages MPC key shares for custodial wallets. +- **settlement** – Handles USDC balances and withdrawals to exchanges. +- **compliance** – Merchant onboarding with KYC checks. +- **monitoring** – Emits webhooks for payment and swap events. + +## Getting Started + +1. Install dependencies in each service directory. +2. Copy `.env.example` to `.env` and fill in required variables. +3. Run `docker-compose up --build` to start all services and databases. + +## TODO + +- Implement API documentation. +- Complete compliance rules and Travel Rule logic. +- Add environment variables for RPC URLs, API keys, and database credentials. +- Flesh out blockchain listeners and swap execution logic. +- Configure CI pipeline and tests. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6193162 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,62 @@ +version: '3.8' +services: + postgres: + image: postgres:14 + restart: always + environment: + POSTGRES_USER: gateway + POSTGRES_PASSWORD: gateway + POSTGRES_DB: gateway + ports: + - "5432:5432" + + mongo: + image: mongo:5 + restart: always + ports: + - "27017:27017" + + ingestion: + build: ./services/ingestion + depends_on: + - postgres + environment: + - DATABASE_URL=postgresql://gateway:gateway@postgres:5432/gateway + ports: + - "3001:3000" + + swap-engine: + build: ./services/swap-engine + depends_on: + - postgres + ports: + - "3002:3000" + + wallet-manager: + build: ./services/wallet-manager + depends_on: + - postgres + ports: + - "3003:3000" + + settlement: + build: ./services/settlement + depends_on: + - postgres + ports: + - "3004:3000" + + compliance: + build: ./services/compliance + depends_on: + - postgres + ports: + - "3005:3000" + + monitoring: + build: ./services/monitoring + depends_on: + - postgres + ports: + - "3006:3000" + diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..0d9dd2f --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,5 @@ +# Architecture Overview + +This document outlines the microservice layout and key components for the payment gateway. + +TODO: expand with sequence diagrams, database schemas, and API contracts. diff --git a/scripts/start.sh b/scripts/start.sh new file mode 100755 index 0000000..173e4eb --- /dev/null +++ b/scripts/start.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker-compose up --build diff --git a/services/compliance/Dockerfile b/services/compliance/Dockerfile new file mode 100644 index 0000000..8c219f5 --- /dev/null +++ b/services/compliance/Dockerfile @@ -0,0 +1,7 @@ +FROM node:18-alpine +WORKDIR /app +COPY package.json tsconfig.json ./ +RUN npm install +COPY src ./src +RUN npm run build +CMD ["node", "dist/main.js"] diff --git a/services/compliance/package.json b/services/compliance/package.json new file mode 100644 index 0000000..a73fc7b --- /dev/null +++ b/services/compliance/package.json @@ -0,0 +1,26 @@ +{ + "name": "compliance", + "version": "0.1.0", + "main": "dist/main.js", + "scripts": { + "start": "nest start", + "build": "nest build", + "test": "jest" + }, + "dependencies": { + "@nestjs/common": "^9.0.0", + "@nestjs/core": "^9.0.0", + "@nestjs/platform-express": "^9.0.0", + "reflect-metadata": "^0.1.13", + "rxjs": "^7.5.0", + "uuid": "^9.0.0" + }, + "devDependencies": { + "@nestjs/cli": "^9.0.0", + "@types/node": "^18", + "ts-node": "^10.0.0", + "typescript": "^4.8.0", + "jest": "^29.0.0", + "ts-jest": "^29.0.0" + } +} diff --git a/services/compliance/src/app.module.ts b/services/compliance/src/app.module.ts new file mode 100644 index 0000000..a235fc2 --- /dev/null +++ b/services/compliance/src/app.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { ComplianceService } from './compliance.service'; +import { OnboardingController } from './onboarding.controller'; + +@Module({ + controllers: [OnboardingController], + providers: [ComplianceService], +}) +export class AppModule {} diff --git a/services/compliance/src/compliance.service.ts b/services/compliance/src/compliance.service.ts new file mode 100644 index 0000000..004f5ec --- /dev/null +++ b/services/compliance/src/compliance.service.ts @@ -0,0 +1,9 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class ComplianceService { + // TODO: integrate KYC providers (Jumio/Trulioo) and AML (TRM Labs/Elliptic) + async checkKyc(userInfo: any) { + // placeholder + } +} diff --git a/services/compliance/src/main.ts b/services/compliance/src/main.ts new file mode 100644 index 0000000..58fd2f4 --- /dev/null +++ b/services/compliance/src/main.ts @@ -0,0 +1,8 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + await app.listen(process.env.PORT || 3000); +} +bootstrap(); diff --git a/services/compliance/src/onboarding.controller.ts b/services/compliance/src/onboarding.controller.ts new file mode 100644 index 0000000..982ab67 --- /dev/null +++ b/services/compliance/src/onboarding.controller.ts @@ -0,0 +1,20 @@ +import { Controller, Post, Body } from '@nestjs/common'; +import { ComplianceService } from './compliance.service'; +import { Merchant } from '../../../shared/interfaces'; +import { v4 as uuid } from 'uuid'; + +@Controller('merchants') +export class OnboardingController { + constructor(private readonly compliance: ComplianceService) {} + + @Post() + async onboard(@Body() body: { name: string }): Promise { + // TODO: call KYC provider + return { + id: uuid(), + name: body.name, + apiKey: 'generated-api-key', + kycStatus: 'pending', + }; + } +} diff --git a/services/compliance/test/example.spec.ts b/services/compliance/test/example.spec.ts new file mode 100644 index 0000000..2c95943 --- /dev/null +++ b/services/compliance/test/example.spec.ts @@ -0,0 +1,5 @@ +describe('placeholder test', () => { + it('should pass', () => { + expect(true).toBe(true); + }); +}); diff --git a/services/compliance/tsconfig.json b/services/compliance/tsconfig.json new file mode 100644 index 0000000..3cef7ca --- /dev/null +++ b/services/compliance/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2017", + "noImplicitAny": false, + "sourceMap": true, + "outDir": "dist", + "baseUrl": "./", + "experimentalDecorators": true, + "emitDecoratorMetadata": true + }, + "include": ["src/**/*"] +} diff --git a/services/ingestion/Dockerfile b/services/ingestion/Dockerfile new file mode 100644 index 0000000..8c219f5 --- /dev/null +++ b/services/ingestion/Dockerfile @@ -0,0 +1,7 @@ +FROM node:18-alpine +WORKDIR /app +COPY package.json tsconfig.json ./ +RUN npm install +COPY src ./src +RUN npm run build +CMD ["node", "dist/main.js"] diff --git a/services/ingestion/package.json b/services/ingestion/package.json new file mode 100644 index 0000000..d812bae --- /dev/null +++ b/services/ingestion/package.json @@ -0,0 +1,27 @@ +{ + "name": "ingestion", + "version": "0.1.0", + "main": "dist/main.js", + "scripts": { + "start": "nest start", + "build": "nest build", + "test": "jest" + }, + "dependencies": { + "@nestjs/common": "^9.0.0", + "@nestjs/core": "^9.0.0", + "@nestjs/platform-express": "^9.0.0", + "reflect-metadata": "^0.1.13", + "rxjs": "^7.5.0", + "qrcode": "^1.5.2", + "uuid": "^9.0.0" + }, + "devDependencies": { + "@nestjs/cli": "^9.0.0", + "@types/node": "^18", + "ts-node": "^10.0.0", + "typescript": "^4.8.0", + "jest": "^29.0.0", + "ts-jest": "^29.0.0" + } +} diff --git a/services/ingestion/src/app.module.ts b/services/ingestion/src/app.module.ts new file mode 100644 index 0000000..2549afa --- /dev/null +++ b/services/ingestion/src/app.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { InvoiceController } from './invoice.controller'; +import { InvoiceService } from './invoice.service'; +import { ChainListener } from './chain.listener'; +import { ApiKeyGuard } from "./auth.guard"; + +@Module({ + controllers: [InvoiceController], + providers: [InvoiceService, ChainListener, ApiKeyGuard], +}) +export class AppModule {} diff --git a/services/ingestion/src/auth.guard.ts b/services/ingestion/src/auth.guard.ts new file mode 100644 index 0000000..c8cc87a --- /dev/null +++ b/services/ingestion/src/auth.guard.ts @@ -0,0 +1,11 @@ +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; + +@Injectable() +export class ApiKeyGuard implements CanActivate { + canActivate(context: ExecutionContext): boolean { + const request = context.switchToHttp().getRequest(); + const apiKey = request.headers['x-api-key']; + // TODO: validate apiKey against merchant store + return !!apiKey; + } +} diff --git a/services/ingestion/src/chain.listener.ts b/services/ingestion/src/chain.listener.ts new file mode 100644 index 0000000..26c5395 --- /dev/null +++ b/services/ingestion/src/chain.listener.ts @@ -0,0 +1,10 @@ +import { Injectable, OnModuleInit } from '@nestjs/common'; + +@Injectable() +export class ChainListener implements OnModuleInit { + async onModuleInit() { + // TODO: spin up listeners for BTC, ETH, SOL, FTM, BNB + // Example using ethers.js WebSocketProvider + // new ethers.providers.WebSocketProvider(process.env.ETHEREUM_RPC_WS); + } +} diff --git a/services/ingestion/src/invoice.controller.ts b/services/ingestion/src/invoice.controller.ts new file mode 100644 index 0000000..12b6fd6 --- /dev/null +++ b/services/ingestion/src/invoice.controller.ts @@ -0,0 +1,16 @@ +import { UseGuards } from "@nestjs/common"; +import { ApiKeyGuard } from "./auth.guard"; +import { Controller, Post, Body } from '@nestjs/common'; +import { InvoiceService } from './invoice.service'; +import { Invoice } from '../../../shared/interfaces'; + +@Controller('invoices') +export class InvoiceController { + constructor(private readonly invoiceService: InvoiceService) {} + + @Post() + @UseGuards(ApiKeyGuard) + async create(@Body() body: { amount: number; currency: string }): Promise { + return this.invoiceService.createInvoice(body.amount, body.currency); + } +} diff --git a/services/ingestion/src/invoice.service.ts b/services/ingestion/src/invoice.service.ts new file mode 100644 index 0000000..f4027b9 --- /dev/null +++ b/services/ingestion/src/invoice.service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@nestjs/common'; +import { Invoice } from '../../../shared/interfaces'; +import * as QRCode from 'qrcode'; +import { v4 as uuid } from 'uuid'; + +@Injectable() +export class InvoiceService { + async createInvoice(amount: number, currency: string): Promise { + const invoice: Invoice = { + id: uuid(), + merchantId: 'todo-merchant', + amount, + currency, + address: 'todo-address', + qrCode: '', + status: 'pending', + }; + const uri = `payto://crypto/${currency}?amount=${amount}`; + invoice.qrCode = await QRCode.toDataURL(uri); + return invoice; + } +} diff --git a/services/ingestion/src/main.ts b/services/ingestion/src/main.ts new file mode 100644 index 0000000..58fd2f4 --- /dev/null +++ b/services/ingestion/src/main.ts @@ -0,0 +1,8 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + await app.listen(process.env.PORT || 3000); +} +bootstrap(); diff --git a/services/ingestion/test/example.spec.ts b/services/ingestion/test/example.spec.ts new file mode 100644 index 0000000..2c95943 --- /dev/null +++ b/services/ingestion/test/example.spec.ts @@ -0,0 +1,5 @@ +describe('placeholder test', () => { + it('should pass', () => { + expect(true).toBe(true); + }); +}); diff --git a/services/ingestion/tsconfig.json b/services/ingestion/tsconfig.json new file mode 100644 index 0000000..3cef7ca --- /dev/null +++ b/services/ingestion/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2017", + "noImplicitAny": false, + "sourceMap": true, + "outDir": "dist", + "baseUrl": "./", + "experimentalDecorators": true, + "emitDecoratorMetadata": true + }, + "include": ["src/**/*"] +} diff --git a/services/monitoring/Dockerfile b/services/monitoring/Dockerfile new file mode 100644 index 0000000..8c219f5 --- /dev/null +++ b/services/monitoring/Dockerfile @@ -0,0 +1,7 @@ +FROM node:18-alpine +WORKDIR /app +COPY package.json tsconfig.json ./ +RUN npm install +COPY src ./src +RUN npm run build +CMD ["node", "dist/main.js"] diff --git a/services/monitoring/package.json b/services/monitoring/package.json new file mode 100644 index 0000000..27c3908 --- /dev/null +++ b/services/monitoring/package.json @@ -0,0 +1,25 @@ +{ + "name": "monitoring", + "version": "0.1.0", + "main": "dist/main.js", + "scripts": { + "start": "nest start", + "build": "nest build", + "test": "jest" + }, + "dependencies": { + "@nestjs/common": "^9.0.0", + "@nestjs/core": "^9.0.0", + "@nestjs/platform-express": "^9.0.0", + "reflect-metadata": "^0.1.13", + "rxjs": "^7.5.0" + }, + "devDependencies": { + "@nestjs/cli": "^9.0.0", + "@types/node": "^18", + "ts-node": "^10.0.0", + "typescript": "^4.8.0", + "jest": "^29.0.0", + "ts-jest": "^29.0.0" + } +} diff --git a/services/monitoring/src/app.module.ts b/services/monitoring/src/app.module.ts new file mode 100644 index 0000000..5c7b8f7 --- /dev/null +++ b/services/monitoring/src/app.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { WebhookService } from './webhook.service'; + +@Module({ + providers: [WebhookService], +}) +export class AppModule {} diff --git a/services/monitoring/src/main.ts b/services/monitoring/src/main.ts new file mode 100644 index 0000000..58fd2f4 --- /dev/null +++ b/services/monitoring/src/main.ts @@ -0,0 +1,8 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + await app.listen(process.env.PORT || 3000); +} +bootstrap(); diff --git a/services/monitoring/src/webhook.service.ts b/services/monitoring/src/webhook.service.ts new file mode 100644 index 0000000..b64c83e --- /dev/null +++ b/services/monitoring/src/webhook.service.ts @@ -0,0 +1,9 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class WebhookService { + // TODO: store webhook URLs per merchant and POST events + async emit(event: string, payload: any) { + // placeholder emit to webhook + } +} diff --git a/services/monitoring/test/example.spec.ts b/services/monitoring/test/example.spec.ts new file mode 100644 index 0000000..2c95943 --- /dev/null +++ b/services/monitoring/test/example.spec.ts @@ -0,0 +1,5 @@ +describe('placeholder test', () => { + it('should pass', () => { + expect(true).toBe(true); + }); +}); diff --git a/services/monitoring/tsconfig.json b/services/monitoring/tsconfig.json new file mode 100644 index 0000000..3cef7ca --- /dev/null +++ b/services/monitoring/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2017", + "noImplicitAny": false, + "sourceMap": true, + "outDir": "dist", + "baseUrl": "./", + "experimentalDecorators": true, + "emitDecoratorMetadata": true + }, + "include": ["src/**/*"] +} diff --git a/services/settlement/Dockerfile b/services/settlement/Dockerfile new file mode 100644 index 0000000..8c219f5 --- /dev/null +++ b/services/settlement/Dockerfile @@ -0,0 +1,7 @@ +FROM node:18-alpine +WORKDIR /app +COPY package.json tsconfig.json ./ +RUN npm install +COPY src ./src +RUN npm run build +CMD ["node", "dist/main.js"] diff --git a/services/settlement/package.json b/services/settlement/package.json new file mode 100644 index 0000000..197979b --- /dev/null +++ b/services/settlement/package.json @@ -0,0 +1,25 @@ +{ + "name": "settlement", + "version": "0.1.0", + "main": "dist/main.js", + "scripts": { + "start": "nest start", + "build": "nest build", + "test": "jest" + }, + "dependencies": { + "@nestjs/common": "^9.0.0", + "@nestjs/core": "^9.0.0", + "@nestjs/platform-express": "^9.0.0", + "reflect-metadata": "^0.1.13", + "rxjs": "^7.5.0" + }, + "devDependencies": { + "@nestjs/cli": "^9.0.0", + "@types/node": "^18", + "ts-node": "^10.0.0", + "typescript": "^4.8.0", + "jest": "^29.0.0", + "ts-jest": "^29.0.0" + } +} diff --git a/services/settlement/src/app.module.ts b/services/settlement/src/app.module.ts new file mode 100644 index 0000000..c46800a --- /dev/null +++ b/services/settlement/src/app.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { SettlementController } from './settlement.controller'; +import { WithdrawService } from './withdraw.service'; + +@Module({ + controllers: [SettlementController], + providers: [WithdrawService], +}) +export class AppModule {} diff --git a/services/settlement/src/main.ts b/services/settlement/src/main.ts new file mode 100644 index 0000000..58fd2f4 --- /dev/null +++ b/services/settlement/src/main.ts @@ -0,0 +1,8 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + await app.listen(process.env.PORT || 3000); +} +bootstrap(); diff --git a/services/settlement/src/settlement.controller.ts b/services/settlement/src/settlement.controller.ts new file mode 100644 index 0000000..14f8573 --- /dev/null +++ b/services/settlement/src/settlement.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Post, Body } from '@nestjs/common'; +import { WithdrawService } from './withdraw.service'; + +@Controller('withdraw') +export class SettlementController { + constructor(private readonly withdrawService: WithdrawService) {} + + @Post() + withdraw(@Body() body: { address: string; amount: string }) { + return this.withdrawService.withdrawUSDC(body.address, body.amount); + } +} diff --git a/services/settlement/src/withdraw.service.ts b/services/settlement/src/withdraw.service.ts new file mode 100644 index 0000000..a8a10ae --- /dev/null +++ b/services/settlement/src/withdraw.service.ts @@ -0,0 +1,9 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class WithdrawService { + // TODO: integrate with Binance/Coinbase/Circle APIs using API keys + async withdrawUSDC(address: string, amount: string) { + // placeholder for withdrawal logic + } +} diff --git a/services/settlement/test/example.spec.ts b/services/settlement/test/example.spec.ts new file mode 100644 index 0000000..2c95943 --- /dev/null +++ b/services/settlement/test/example.spec.ts @@ -0,0 +1,5 @@ +describe('placeholder test', () => { + it('should pass', () => { + expect(true).toBe(true); + }); +}); diff --git a/services/settlement/tsconfig.json b/services/settlement/tsconfig.json new file mode 100644 index 0000000..3cef7ca --- /dev/null +++ b/services/settlement/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2017", + "noImplicitAny": false, + "sourceMap": true, + "outDir": "dist", + "baseUrl": "./", + "experimentalDecorators": true, + "emitDecoratorMetadata": true + }, + "include": ["src/**/*"] +} diff --git a/services/swap-engine/Dockerfile b/services/swap-engine/Dockerfile new file mode 100644 index 0000000..8c219f5 --- /dev/null +++ b/services/swap-engine/Dockerfile @@ -0,0 +1,7 @@ +FROM node:18-alpine +WORKDIR /app +COPY package.json tsconfig.json ./ +RUN npm install +COPY src ./src +RUN npm run build +CMD ["node", "dist/main.js"] diff --git a/services/swap-engine/package.json b/services/swap-engine/package.json new file mode 100644 index 0000000..428e134 --- /dev/null +++ b/services/swap-engine/package.json @@ -0,0 +1,26 @@ +{ + "name": "swap-engine", + "version": "0.1.0", + "main": "dist/main.js", + "scripts": { + "start": "nest start", + "build": "nest build", + "test": "jest" + }, + "dependencies": { + "@nestjs/common": "^9.0.0", + "@nestjs/core": "^9.0.0", + "@nestjs/platform-express": "^9.0.0", + "reflect-metadata": "^0.1.13", + "rxjs": "^7.5.0", + "@nestjs/axios": "^9.0.0" + }, + "devDependencies": { + "@nestjs/cli": "^9.0.0", + "@types/node": "^18", + "ts-node": "^10.0.0", + "typescript": "^4.8.0", + "jest": "^29.0.0", + "ts-jest": "^29.0.0" + } +} diff --git a/services/swap-engine/src/app.module.ts b/services/swap-engine/src/app.module.ts new file mode 100644 index 0000000..e652f4f --- /dev/null +++ b/services/swap-engine/src/app.module.ts @@ -0,0 +1,10 @@ +import { Module, HttpModule } from '@nestjs/common'; +import { SwapService } from './swap.service'; +import { SwapController } from './swap.controller'; + +@Module({ + imports: [HttpModule], + controllers: [SwapController], + providers: [SwapService], +}) +export class AppModule {} diff --git a/services/swap-engine/src/main.ts b/services/swap-engine/src/main.ts new file mode 100644 index 0000000..58fd2f4 --- /dev/null +++ b/services/swap-engine/src/main.ts @@ -0,0 +1,8 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + await app.listen(process.env.PORT || 3000); +} +bootstrap(); diff --git a/services/swap-engine/src/swap.controller.ts b/services/swap-engine/src/swap.controller.ts new file mode 100644 index 0000000..422f534 --- /dev/null +++ b/services/swap-engine/src/swap.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Post, Body } from '@nestjs/common'; +import { SwapService } from './swap.service'; + +@Controller('swap') +export class SwapController { + constructor(private readonly swapService: SwapService) {} + + @Post('quote') + quote(@Body() body: { from: string; to: string; amount: string; slippage: number }) { + return this.swapService.quote(body.from, body.to, body.amount, body.slippage); + } +} diff --git a/services/swap-engine/src/swap.service.ts b/services/swap-engine/src/swap.service.ts new file mode 100644 index 0000000..be1558d --- /dev/null +++ b/services/swap-engine/src/swap.service.ts @@ -0,0 +1,17 @@ +import { Injectable, HttpService } from '@nestjs/common'; + +@Injectable() +export class SwapService { + constructor(private readonly http: HttpService) {} + + async quote(from: string, to: string, amount: string, slippage: number) { + // TODO: call DEX aggregator quote API + return this.http.get('https://api.1inch.io/v5.0/1/quote', { + params: { fromTokenAddress: from, toTokenAddress: to, amount, slippage }, + }).toPromise(); + } + + async swap(from: string, to: string, amount: string, slippage: number) { + // TODO: execute swap via aggregator + } +} diff --git a/services/swap-engine/test/example.spec.ts b/services/swap-engine/test/example.spec.ts new file mode 100644 index 0000000..2c95943 --- /dev/null +++ b/services/swap-engine/test/example.spec.ts @@ -0,0 +1,5 @@ +describe('placeholder test', () => { + it('should pass', () => { + expect(true).toBe(true); + }); +}); diff --git a/services/swap-engine/tsconfig.json b/services/swap-engine/tsconfig.json new file mode 100644 index 0000000..3cef7ca --- /dev/null +++ b/services/swap-engine/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2017", + "noImplicitAny": false, + "sourceMap": true, + "outDir": "dist", + "baseUrl": "./", + "experimentalDecorators": true, + "emitDecoratorMetadata": true + }, + "include": ["src/**/*"] +} diff --git a/services/wallet-manager/Dockerfile b/services/wallet-manager/Dockerfile new file mode 100644 index 0000000..8c219f5 --- /dev/null +++ b/services/wallet-manager/Dockerfile @@ -0,0 +1,7 @@ +FROM node:18-alpine +WORKDIR /app +COPY package.json tsconfig.json ./ +RUN npm install +COPY src ./src +RUN npm run build +CMD ["node", "dist/main.js"] diff --git a/services/wallet-manager/package.json b/services/wallet-manager/package.json new file mode 100644 index 0000000..78a2c68 --- /dev/null +++ b/services/wallet-manager/package.json @@ -0,0 +1,25 @@ +{ + "name": "wallet-manager", + "version": "0.1.0", + "main": "dist/main.js", + "scripts": { + "start": "nest start", + "build": "nest build", + "test": "jest" + }, + "dependencies": { + "@nestjs/common": "^9.0.0", + "@nestjs/core": "^9.0.0", + "@nestjs/platform-express": "^9.0.0", + "reflect-metadata": "^0.1.13", + "rxjs": "^7.5.0" + }, + "devDependencies": { + "@nestjs/cli": "^9.0.0", + "@types/node": "^18", + "ts-node": "^10.0.0", + "typescript": "^4.8.0", + "jest": "^29.0.0", + "ts-jest": "^29.0.0" + } +} diff --git a/services/wallet-manager/src/app.module.ts b/services/wallet-manager/src/app.module.ts new file mode 100644 index 0000000..ed96816 --- /dev/null +++ b/services/wallet-manager/src/app.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { MpcService } from './mpc.service'; + +@Module({ + providers: [MpcService], +}) +export class AppModule {} diff --git a/services/wallet-manager/src/main.ts b/services/wallet-manager/src/main.ts new file mode 100644 index 0000000..58fd2f4 --- /dev/null +++ b/services/wallet-manager/src/main.ts @@ -0,0 +1,8 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + await app.listen(process.env.PORT || 3000); +} +bootstrap(); diff --git a/services/wallet-manager/src/mpc.service.ts b/services/wallet-manager/src/mpc.service.ts new file mode 100644 index 0000000..2636bdd --- /dev/null +++ b/services/wallet-manager/src/mpc.service.ts @@ -0,0 +1,9 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class MpcService { + // TODO: integrate with Fireblocks or ThresholdCrypto for MPC key shares + async signTransaction(tx: any) { + // placeholder for signing via MPC provider + } +} diff --git a/services/wallet-manager/test/example.spec.ts b/services/wallet-manager/test/example.spec.ts new file mode 100644 index 0000000..2c95943 --- /dev/null +++ b/services/wallet-manager/test/example.spec.ts @@ -0,0 +1,5 @@ +describe('placeholder test', () => { + it('should pass', () => { + expect(true).toBe(true); + }); +}); diff --git a/services/wallet-manager/tsconfig.json b/services/wallet-manager/tsconfig.json new file mode 100644 index 0000000..3cef7ca --- /dev/null +++ b/services/wallet-manager/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2017", + "noImplicitAny": false, + "sourceMap": true, + "outDir": "dist", + "baseUrl": "./", + "experimentalDecorators": true, + "emitDecoratorMetadata": true + }, + "include": ["src/**/*"] +} diff --git a/shared/interfaces/index.ts b/shared/interfaces/index.ts new file mode 100644 index 0000000..132cedd --- /dev/null +++ b/shared/interfaces/index.ts @@ -0,0 +1,3 @@ +export * from './merchant'; +export * from './invoice'; +export * from './swap-job'; diff --git a/shared/interfaces/invoice.ts b/shared/interfaces/invoice.ts new file mode 100644 index 0000000..b0ee7a6 --- /dev/null +++ b/shared/interfaces/invoice.ts @@ -0,0 +1,9 @@ +export interface Invoice { + id: string; + merchantId: string; + amount: number; + currency: string; + address: string; + qrCode: string; + status: 'pending' | 'paid' | 'confirmed' | 'failed'; +} diff --git a/shared/interfaces/merchant.ts b/shared/interfaces/merchant.ts new file mode 100644 index 0000000..e7fbf07 --- /dev/null +++ b/shared/interfaces/merchant.ts @@ -0,0 +1,6 @@ +export interface Merchant { + id: string; + name: string; + apiKey: string; + kycStatus: 'pending' | 'approved' | 'rejected'; +} diff --git a/shared/interfaces/swap-job.ts b/shared/interfaces/swap-job.ts new file mode 100644 index 0000000..2cfd135 --- /dev/null +++ b/shared/interfaces/swap-job.ts @@ -0,0 +1,9 @@ +export interface SwapJob { + id: string; + txHash: string; + fromToken: string; + toToken: string; + amount: string; + slippage: number; + status: 'pending' | 'completed' | 'failed'; +} diff --git a/shared/tsconfig.json b/shared/tsconfig.json new file mode 100644 index 0000000..24425c1 --- /dev/null +++ b/shared/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2017", + "declaration": true, + "outDir": "dist", + "strict": true, + "esModuleInterop": true + }, + "include": ["interfaces/**/*"] +}