Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -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=
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
dist/
.env
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
62 changes: 62 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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"

5 changes: 5 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 2 additions & 0 deletions scripts/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
docker-compose up --build
7 changes: 7 additions & 0 deletions services/compliance/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
26 changes: 26 additions & 0 deletions services/compliance/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
9 changes: 9 additions & 0 deletions services/compliance/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -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 {}
9 changes: 9 additions & 0 deletions services/compliance/src/compliance.service.ts
Original file line number Diff line number Diff line change
@@ -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
}
}
8 changes: 8 additions & 0 deletions services/compliance/src/main.ts
Original file line number Diff line number Diff line change
@@ -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();
20 changes: 20 additions & 0 deletions services/compliance/src/onboarding.controller.ts
Original file line number Diff line number Diff line change
@@ -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<Merchant> {
// TODO: call KYC provider
return {
id: uuid(),
name: body.name,
apiKey: 'generated-api-key',
kycStatus: 'pending',
};
}
}
5 changes: 5 additions & 0 deletions services/compliance/test/example.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
describe('placeholder test', () => {
it('should pass', () => {
expect(true).toBe(true);
});
});
13 changes: 13 additions & 0 deletions services/compliance/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2017",
"noImplicitAny": false,
"sourceMap": true,
"outDir": "dist",
"baseUrl": "./",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["src/**/*"]
}
7 changes: 7 additions & 0 deletions services/ingestion/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
27 changes: 27 additions & 0 deletions services/ingestion/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
11 changes: 11 additions & 0 deletions services/ingestion/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -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 {}
11 changes: 11 additions & 0 deletions services/ingestion/src/auth.guard.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
10 changes: 10 additions & 0 deletions services/ingestion/src/chain.listener.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
16 changes: 16 additions & 0 deletions services/ingestion/src/invoice.controller.ts
Original file line number Diff line number Diff line change
@@ -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<Invoice> {
return this.invoiceService.createInvoice(body.amount, body.currency);
}
}
22 changes: 22 additions & 0 deletions services/ingestion/src/invoice.service.ts
Original file line number Diff line number Diff line change
@@ -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<Invoice> {
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;
}
}
8 changes: 8 additions & 0 deletions services/ingestion/src/main.ts
Original file line number Diff line number Diff line change
@@ -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();
5 changes: 5 additions & 0 deletions services/ingestion/test/example.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
describe('placeholder test', () => {
it('should pass', () => {
expect(true).toBe(true);
});
});
13 changes: 13 additions & 0 deletions services/ingestion/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2017",
"noImplicitAny": false,
"sourceMap": true,
"outDir": "dist",
"baseUrl": "./",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["src/**/*"]
}
Loading
Loading