Initial project setup: Add contracts, API definitions, tests, and documentation

- Add Foundry project configuration (foundry.toml, foundry.lock)
- Add Solidity contracts (TokenFactory138, BridgeVault138, ComplianceRegistry, etc.)
- Add API definitions (OpenAPI, GraphQL, gRPC, AsyncAPI)
- Add comprehensive test suite (unit, integration, fuzz, invariants)
- Add API services (REST, GraphQL, orchestrator, packet service)
- Add documentation (ISO20022 mapping, runbooks, adapter guides)
- Add development tools (RBC tool, Swagger UI, mock server)
- Update OpenZeppelin submodules to v5.0.0
This commit is contained in:
defiQUG
2025-12-12 10:59:41 -08:00
parent 26b5aaf932
commit 651ff4f7eb
281 changed files with 24813 additions and 2 deletions

115
api/tools/README.md Normal file
View File

@@ -0,0 +1,115 @@
# API Development Tools
This directory contains development tools for the eMoney Token Factory API.
## Tools
### OpenAPI Generator
Generates SDKs and Postman collections from OpenAPI specifications.
```bash
cd openapi-generator
pnpm install
pnpm run generate:typescript
pnpm run generate:python
pnpm run generate:go
pnpm run generate:java
pnpm run generate:postman
```
Or use the shell script:
```bash
./generate-sdks.sh
```
### Mock Servers
Mock servers for testing without full infrastructure.
#### REST API Mock (Prism)
```bash
cd mock-server
pnpm install
pnpm run start:rest
```
Mocks all REST endpoints based on OpenAPI spec.
#### GraphQL Mock
```bash
npm run start:graphql
```
Mocks GraphQL queries, mutations, and subscriptions.
#### Rail Simulator
```bash
npm run start:rail
```
Simulates Fedwire/SWIFT/SEPA/RTGS responses.
#### Packet Simulator
```bash
npm run start:packet
```
Simulates AS4 receipts and email acknowledgements.
#### Start All
```bash
npm run start:all
```
Starts all mock servers concurrently.
### SDK Templates
Templates and examples for SDK implementations:
- `typescript-sdk-template/` - TypeScript SDK structure
- Generated SDKs from OpenAPI (after running generator)
## Usage
### Generating SDKs
1. Ensure OpenAPI spec is up to date
2. Run generator: `cd openapi-generator && pnpm run generate:typescript`
3. SDKs will be generated in `sdk-templates/` directory
4. Copy to separate repositories for publishing
### Using Mock Servers
1. Start mock servers: `cd mock-server && pnpm run start:all`
2. Point tests to mock endpoints
3. Use for local development and CI/CD
## CI/CD Integration
### Generate SDKs in CI
```yaml
- name: Generate SDKs
run: |
cd api/tools/openapi-generator
pnpm install
pnpm run generate:typescript
pnpm run generate:python
```
### Run Contract Tests
```yaml
- name: Validate OpenAPI Contract
run: |
pnpm test -- test/api/contract
```

View File

@@ -0,0 +1,30 @@
{
"name": "@emoney/mock-server",
"version": "1.0.0",
"description": "Mock servers for eMoney API testing",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start:rest": "node dist/rest-mock.js",
"start:graphql": "node dist/graphql-mock.js",
"start:all": "concurrently \"pnpm run start:rest\" \"pnpm run start:graphql\"",
"test": "jest"
},
"dependencies": {
"@stoplight/prism-http": "^5.1.0",
"@stoplight/prism-cli": "^5.1.0",
"@graphql-tools/mock": "^9.0.0",
"@graphql-tools/schema": "^10.0.0",
"express": "^4.18.2",
"graphql": "^16.8.1",
"graphql-yoga": "^4.0.0"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^20.10.0",
"typescript": "^5.3.0",
"concurrently": "^8.2.2",
"jest": "^29.7.0"
}
}

View File

@@ -0,0 +1,74 @@
/**
* GraphQL Mock Server
* Mocks GraphQL schema for testing
*/
import { createYoga } from 'graphql-yoga';
import { createServer } from 'http';
import { addMocksToSchema } from '@graphql-tools/mock';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { readFileSync } from 'fs';
import { join } from 'path';
const SCHEMA_PATH = join(__dirname, '../../packages/graphql/schema.graphql');
function startGraphQLMockServer() {
const typeDefs = readFileSync(SCHEMA_PATH, 'utf-8');
const schema = makeExecutableSchema({
typeDefs,
});
// Add mocks
const mockedSchema = addMocksToSchema({
schema,
mocks: {
Token: () => ({
code: 'USDW',
address: '0x1234567890123456789012345678901234567890',
name: 'USD Wrapped',
symbol: 'USDW',
decimals: 18,
issuer: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd',
policy: {
paused: false,
bridgeOnly: false,
lienMode: 'ENCUMBERED',
forceTransferMode: false,
routes: ['FEDWIRE', 'SWIFT'],
},
}),
Lien: () => ({
lienId: '123',
debtor: '0xabcd...',
amount: '1000000000000000000',
active: true,
priority: 1,
reasonCode: 'DEBT_ENFORCEMENT',
}),
Trigger: () => ({
triggerId: 'abc123',
rail: 'FEDWIRE',
msgType: 'pacs.008',
state: 'PENDING',
instructionId: '0x1234...',
amount: '1000000000000000000',
}),
},
});
const yoga = createYoga({
schema: mockedSchema,
graphqlEndpoint: '/graphql',
});
const server = createServer(yoga);
const PORT = process.env.MOCK_GRAPHQL_PORT || 4020;
server.listen(PORT, () => {
console.log(`GraphQL Mock Server running on http://localhost:${PORT}/graphql`);
});
}
startGraphQLMockServer();

View File

@@ -0,0 +1,26 @@
/**
* Start all mock servers
*/
import { spawn } from 'child_process';
import { join } from 'path';
const servers = [
{ name: 'REST Mock', script: 'rest-mock.js' },
{ name: 'GraphQL Mock', script: 'graphql-mock.js' },
{ name: 'Rail Simulator', script: 'rail-simulator.js' },
{ name: 'Packet Simulator', script: 'packet-simulator.js' },
];
console.log('Starting all mock servers...');
servers.forEach(({ name, script }) => {
const proc = spawn('node', [join(__dirname, script)], {
stdio: 'inherit',
});
proc.on('error', (error) => {
console.error(`Failed to start ${name}:`, error);
});
});

View File

@@ -0,0 +1,57 @@
/**
* Packet Simulator
* Simulates AS4 receipts and email acknowledgements for testing
*/
import express from 'express';
const app = express();
app.use(express.json());
const PORT = process.env.PACKET_SIMULATOR_PORT || 4040;
// Simulate AS4 receipt
app.post('/simulate/as4/receipt', (req, res) => {
const { packetId, messageRef } = req.body;
setTimeout(() => {
res.json({
packetId,
messageRef,
ackId: `AS4-ACK-${Date.now()}`,
status: 'RECEIVED',
receivedAt: new Date().toISOString(),
});
}, 500);
});
// Simulate email acknowledgement
app.post('/simulate/email/ack', (req, res) => {
const { packetId, recipient } = req.body;
setTimeout(() => {
res.json({
packetId,
recipient,
ackId: `EMAIL-ACK-${Date.now()}`,
status: 'ACCEPTED',
receivedAt: new Date().toISOString(),
});
}, 1000);
});
// Simulate packet delivery failure
app.post('/simulate/failure', (req, res) => {
const { packetId, reason } = req.body;
res.status(400).json({
packetId,
error: reason || 'Delivery failed',
timestamp: new Date().toISOString(),
});
});
app.listen(PORT, () => {
console.log(`Packet Simulator running on http://localhost:${PORT}`);
});

View File

@@ -0,0 +1,73 @@
/**
* Rail Simulator
* Simulates Fedwire/SWIFT/SEPA/RTGS responses for testing
*/
import express from 'express';
const app = express();
app.use(express.json());
const PORT = process.env.RAIL_SIMULATOR_PORT || 4030;
// Simulate Fedwire response
app.post('/simulate/fedwire', (req, res) => {
const { instructionId, amount } = req.body;
// Simulate processing delay
setTimeout(() => {
res.json({
railTxRef: `FED-${Date.now()}`,
status: 'ACCEPTED',
settlementDate: new Date().toISOString(),
instructionId,
amount,
});
}, 1000);
});
// Simulate SWIFT response
app.post('/simulate/swift', (req, res) => {
const { instructionId, amount } = req.body;
setTimeout(() => {
res.json({
railTxRef: `SWIFT-${Date.now()}`,
status: 'ACCEPTED',
settlementDate: new Date().toISOString(),
instructionId,
amount,
});
}, 1500);
});
// Simulate SEPA response
app.post('/simulate/sepa', (req, res) => {
const { instructionId, amount } = req.body;
setTimeout(() => {
res.json({
railTxRef: `SEPA-${Date.now()}`,
status: 'ACCEPTED',
settlementDate: new Date().toISOString(),
instructionId,
amount,
});
}, 2000);
});
// Simulate status update (pacs.002)
app.post('/simulate/status', (req, res) => {
const { railTxRef, status } = req.body;
res.json({
railTxRef,
status: status || 'ACSC', // ACSC = AcceptedSettlementCompleted
timestamp: new Date().toISOString(),
});
});
app.listen(PORT, () => {
console.log(`Rail Simulator running on http://localhost:${PORT}`);
});

View File

@@ -0,0 +1,37 @@
/**
* REST API Mock Server using Prism
* Mocks OpenAPI specification for testing
*/
import { createServer } from '@stoplight/prism-http';
import { createHttpServer } from '@stoplight/prism-http-server';
import { readFileSync } from 'fs';
import { join } from 'path';
const OPENAPI_SPEC = join(__dirname, '../../packages/openapi/v1/openapi.yaml');
async function startMockServer() {
const spec = readFileSync(OPENAPI_SPEC, 'utf-8');
const server = createHttpServer({
document: spec,
config: {
mock: {
dynamic: true,
exampleKey: 'default',
},
cors: true,
errors: false,
},
});
const PORT = process.env.MOCK_PORT || 4010;
server.listen(PORT, () => {
console.log(`REST API Mock Server running on http://localhost:${PORT}`);
console.log(`OpenAPI spec: ${OPENAPI_SPEC}`);
});
}
startMockServer().catch(console.error);

View File

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

View File

@@ -0,0 +1,45 @@
#!/bin/bash
# Generate SDKs from OpenAPI specification
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
API_DIR="$SCRIPT_DIR/../.."
OPENAPI_SPEC="$API_DIR/packages/openapi/v1/openapi.yaml"
echo "Generating SDKs from OpenAPI specification..."
# TypeScript SDK
echo "Generating TypeScript SDK..."
pnpm exec @openapitools/openapi-generator-cli generate \
-i "$OPENAPI_SPEC" \
-g typescript-axios \
-o "$API_DIR/sdk-templates/typescript" \
--additional-properties=npmName=@emoney/sdk-js,npmVersion=1.0.0,withInterfaces=true
# Python SDK
echo "Generating Python SDK..."
pnpm exec @openapitools/openapi-generator-cli generate \
-i "$OPENAPI_SPEC" \
-g python \
-o "$API_DIR/sdk-templates/python" \
--additional-properties=packageName=emoney_sdk,packageVersion=1.0.0
# Go SDK
echo "Generating Go SDK..."
pnpm exec @openapitools/openapi-generator-cli generate \
-i "$OPENAPI_SPEC" \
-g go \
-o "$API_DIR/sdk-templates/go" \
--additional-properties=packageName=emoney,packageVersion=1.0.0
# Java SDK
echo "Generating Java SDK..."
pnpm exec @openapitools/openapi-generator-cli generate \
-i "$OPENAPI_SPEC" \
-g java \
-o "$API_DIR/sdk-templates/java" \
--additional-properties=groupId=com.emoney,artifactId=emoney-sdk,packageVersion=1.0.0
echo "SDK generation complete!"

View File

@@ -0,0 +1,20 @@
{
"name": "@emoney/openapi-generator",
"version": "1.0.0",
"description": "OpenAPI to SDK generation tooling",
"scripts": {
"generate:typescript": "pnpm exec openapi-generator-cli generate -i ../../packages/openapi/v1/openapi.yaml -g typescript-axios -o ../../sdk-templates/typescript",
"generate:python": "pnpm exec openapi-generator-cli generate -i ../../packages/openapi/v1/openapi.yaml -g python -o ../../sdk-templates/python",
"generate:go": "pnpm exec openapi-generator-cli generate -i ../../packages/openapi/v1/openapi.yaml -g go -o ../../sdk-templates/go",
"generate:java": "pnpm exec openapi-generator-cli generate -i ../../packages/openapi/v1/openapi.yaml -g java -o ../../sdk-templates/java",
"generate:postman": "pnpm exec openapi2postmanv2 -s ../../packages/openapi/v1/openapi.yaml -o ../../packages/postman/eMoney-API.postman_collection.json"
},
"dependencies": {
"@openapitools/openapi-generator-cli": "^2.7.0",
"openapi-to-postmanv2": "^4.24.0"
},
"devDependencies": {
"@types/node": "^20.10.0"
}
}

View File

@@ -0,0 +1,112 @@
# eMoney Token Factory TypeScript SDK
TypeScript/JavaScript SDK for the eMoney Token Factory API.
## Installation
```bash
pnpm add @emoney/sdk-js
```
## Usage
### Basic Setup
```typescript
import { createSDK } from '@emoney/sdk-js';
const sdk = createSDK({
baseUrl: 'https://api.emoney.example.com/v1',
graphqlUrl: 'https://api.emoney.example.com/graphql',
wsUrl: 'wss://api.emoney.example.com/graphql',
accessToken: 'your-oauth-token',
});
```
### REST API Examples
```typescript
// Deploy a token
const token = await sdk.tokens.deploy({
name: 'USD Wrapped',
symbol: 'USDW',
decimals: 18,
issuer: '0x1234...',
});
// Place a lien
const lien = await sdk.liens.place({
debtor: '0xabcd...',
amount: '1000000000000000000',
priority: 1,
});
// Submit ISO-20022 message
const trigger = await sdk.triggers.submitOutbound({
msgType: 'pacs.008',
instructionId: '0x1234...',
payloadHash: '0xabcd...',
payload: '<Document>...</Document>',
rail: 'FEDWIRE',
token: '0x5678...',
amount: '1000000000000000000',
accountRefId: '0xdef0...',
counterpartyRefId: '0x9876...',
});
```
### GraphQL Examples
```typescript
// Query
const result = await sdk.query(`
query GetToken($code: String!) {
token(code: $code) {
code
address
policy {
lienMode
}
}
}
`, { code: 'USDW' });
// Mutation
const token = await sdk.mutate(`
mutation DeployToken($input: DeployTokenInput!) {
deployToken(input: $input) {
code
address
}
}
`, {
input: {
name: 'USD Wrapped',
symbol: 'USDW',
decimals: 18,
issuer: '0x1234...',
},
});
```
### Idempotency
```typescript
const idempotencyKey = sdk.generateIdempotencyKey();
// Use in requests that require idempotency
const token = await sdk.tokens.deploy(
{ /* config */ },
{ headers: { 'Idempotency-Key': idempotencyKey } }
);
```
## Features
- ✅ Full REST API coverage
- ✅ GraphQL query/mutation support
- ✅ WebSocket subscriptions (coming soon)
- ✅ TypeScript types
- ✅ Idempotency helpers
- ✅ OAuth2 authentication

View File

@@ -0,0 +1,33 @@
{
"name": "@emoney/sdk-js",
"version": "1.0.0",
"description": "TypeScript/JavaScript SDK for eMoney Token Factory API",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"test": "jest",
"prepublishOnly": "pnpm run build"
},
"dependencies": {
"axios": "^1.6.2",
"graphql": "^16.8.1",
"graphql-request": "^6.1.0",
"graphql-ws": "^5.14.2"
},
"devDependencies": {
"@types/node": "^20.10.0",
"typescript": "^5.3.0",
"jest": "^29.7.0",
"@types/jest": "^29.5.11"
},
"keywords": [
"emoney",
"token-factory",
"api",
"sdk"
],
"author": "",
"license": "MIT"
}

View File

@@ -0,0 +1,147 @@
/**
* eMoney Token Factory TypeScript SDK
* Generated from OpenAPI specification with additional GraphQL support
*/
import { Configuration, DefaultApi } from './generated';
import { GraphQLClient } from 'graphql-request';
import { Client } from 'graphql-ws';
export interface SDKConfig {
baseUrl: string;
graphqlUrl?: string;
wsUrl?: string;
apiKey?: string;
accessToken?: string;
}
export class EMoneySDK {
private restClient: DefaultApi;
private graphqlClient?: GraphQLClient;
private wsClient?: Client;
constructor(config: SDKConfig) {
// Initialize REST client
const restConfig = new Configuration({
basePath: config.baseUrl,
accessToken: config.accessToken,
apiKey: config.apiKey,
});
this.restClient = new DefaultApi(restConfig);
// Initialize GraphQL client
if (config.graphqlUrl) {
this.graphqlClient = new GraphQLClient(config.graphqlUrl, {
headers: {
Authorization: config.accessToken ? `Bearer ${config.accessToken}` : '',
},
});
}
// Initialize WebSocket client for subscriptions
if (config.wsUrl) {
this.wsClient = new Client({
url: config.wsUrl,
connectionParams: {
Authorization: config.accessToken ? `Bearer ${config.accessToken}` : '',
},
});
}
}
// REST API methods (generated from OpenAPI)
get tokens() {
return {
deploy: (config: any) => this.restClient.deployToken(config),
list: (filters?: any) => this.restClient.listTokens(filters),
get: (code: string) => this.restClient.getToken(code),
updatePolicy: (code: string, policy: any) => this.restClient.updateTokenPolicy(code, policy),
mint: (code: string, params: any) => this.restClient.mintTokens(code, params),
burn: (code: string, params: any) => this.restClient.burnTokens(code, params),
};
}
get liens() {
return {
place: (params: any) => this.restClient.placeLien(params),
get: (lienId: string) => this.restClient.getLien(lienId),
list: (filters?: any) => this.restClient.listLiens(filters),
reduce: (lienId: string, reduceBy: string) => this.restClient.reduceLien(lienId, reduceBy),
release: (lienId: string) => this.restClient.releaseLien(lienId),
};
}
get compliance() {
return {
setAccount: (accountRefId: string, profile: any) =>
this.restClient.setAccountCompliance(accountRefId, profile),
getAccount: (accountRefId: string) => this.restClient.getAccountCompliance(accountRefId),
setFreeze: (refId: string, frozen: boolean) => this.restClient.setFreeze(refId, frozen),
};
}
get triggers() {
return {
list: (filters?: any) => this.restClient.listTriggers(filters),
get: (triggerId: string) => this.restClient.getTrigger(triggerId),
submitInbound: (message: any) => this.restClient.submitInboundMessage(message),
submitOutbound: (message: any) => this.restClient.submitOutboundMessage(message),
};
}
get packets() {
return {
generate: (params: any) => this.restClient.generatePacket(params),
get: (packetId: string) => this.restClient.getPacket(packetId),
dispatch: (packetId: string, params: any) => this.restClient.dispatchPacket(packetId, params),
acknowledge: (packetId: string, params: any) =>
this.restClient.acknowledgePacket(packetId, params),
};
}
get bridge() {
return {
lock: (params: any) => this.restClient.bridgeLock(params),
unlock: (params: any) => this.restClient.bridgeUnlock(params),
getLock: (lockId: string) => this.restClient.getBridgeLock(lockId),
};
}
// GraphQL methods
async query<T = any>(query: string, variables?: any): Promise<T> {
if (!this.graphqlClient) {
throw new Error('GraphQL client not configured');
}
return this.graphqlClient.request<T>(query, variables);
}
async mutate<T = any>(mutation: string, variables?: any): Promise<T> {
if (!this.graphqlClient) {
throw new Error('GraphQL client not configured');
}
return this.graphqlClient.request<T>(mutation, variables);
}
// WebSocket subscriptions
subscribe<T = any>(subscription: string, variables?: any): AsyncIterator<T> {
if (!this.wsClient) {
throw new Error('WebSocket client not configured');
}
// TODO: Implement subscription iterator
throw new Error('Subscriptions not yet implemented');
}
// Idempotency helper
generateIdempotencyKey(): string {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
}
// Export convenience function
export function createSDK(config: SDKConfig): EMoneySDK {
return new EMoneySDK(config);
}
// Re-export generated types
export * from './generated';

View File

@@ -0,0 +1,7 @@
node_modules
dist
*.log
.env
.git
.DS_Store

View File

@@ -0,0 +1,28 @@
FROM node:18-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
COPY tsconfig.json ./
# Install pnpm
RUN npm install -g pnpm
# Install dependencies
RUN pnpm install --frozen-lockfile
# Copy source files
COPY src/ ./src/
COPY ../../packages/openapi/v1/openapi.yaml ./packages/openapi/v1/openapi.yaml
COPY static/ ./static/
# Build
RUN pnpm run build
# Expose port
EXPOSE 8080
# Start server
CMD ["pnpm", "start"]

View File

@@ -0,0 +1,33 @@
.PHONY: install build start dev generate-standalone docker-build docker-run help
help:
@echo "Swagger UI Server - Available commands:"
@echo " make install - Install dependencies"
@echo " make build - Build TypeScript"
@echo " make start - Start server"
@echo " make dev - Start in development mode"
@echo " make generate-standalone - Generate standalone HTML"
@echo " make docker-build - Build Docker image"
@echo " make docker-run - Run Docker container"
install:
pnpm install
build:
pnpm run build
start: build
pnpm start
dev:
pnpm run dev
generate-standalone:
pnpm run generate:standalone
docker-build:
docker build -t emoney-swagger-ui .
docker-run:
docker run -p 8080:8080 emoney-swagger-ui

View File

@@ -0,0 +1,126 @@
# Swagger UI Quick Start Guide
## Local Development
### Option 1: Node.js (Recommended)
```bash
cd api/tools/swagger-ui
pnpm install
pnpm run dev
```
Visit: http://localhost:8080/api-docs
### Option 2: Docker
```bash
cd api/tools/swagger-ui
docker-compose up
```
Visit: http://localhost:8080/api-docs
## Features
### Interactive Documentation
- Browse all API endpoints
- View request/response schemas
- See example payloads
- Explore data models
### Try It Out
- Test API calls directly from the browser
- Set authentication tokens
- View real responses
- Debug API interactions
### Authentication Testing
- OAuth2 client credentials flow
- mTLS configuration
- API key testing
- Token persistence
### Export Options
- Download OpenAPI spec as JSON
- Download OpenAPI spec as YAML
- Share documentation links
## API Endpoints
The Swagger UI server provides:
- `GET /api-docs` - Interactive documentation
- `GET /openapi.json` - OpenAPI spec (JSON)
- `GET /openapi.yaml` - OpenAPI spec (YAML)
- `GET /health` - Health check
## Configuration
### Environment Variables
```bash
SWAGGER_PORT=8080 # Server port (default: 8080)
```
### Customization
Edit `src/index.ts` to customize:
- Swagger UI theme
- Default expansion level
- Supported HTTP methods
- OAuth2 redirect URL
## Integration with Main API
To integrate Swagger UI into the main REST API server:
```typescript
// In api/services/rest-api/src/index.ts
import swaggerUi from 'swagger-ui-express';
import YAML from 'yamljs';
import { join } from 'path';
const openapiSpec = YAML.load(join(__dirname, '../../packages/openapi/v1/openapi.yaml'));
app.use('/docs', swaggerUi.serve, swaggerUi.setup(openapiSpec));
```
## Production Deployment
### Standalone Server
Deploy as separate service:
- Lightweight Express server
- Serves only documentation
- Can be behind CDN
- No API dependencies
### Embedded in API
Include in main API server:
- Single deployment
- Shared authentication
- Live spec updates
- Integrated experience
## Troubleshooting
### OpenAPI Spec Not Loading
1. Check file path: `../../packages/openapi/v1/openapi.yaml`
2. Verify YAML syntax is valid
3. Check file permissions
### OAuth2 Not Working
1. Verify redirect URL matches configuration
2. Check CORS settings
3. Ensure OAuth2 server is accessible
### Styles Not Loading
1. Check network tab for 404s
2. Verify CDN is accessible
3. Check custom CSS syntax

View File

@@ -0,0 +1,55 @@
# Swagger UI Server
Interactive API documentation server for the eMoney Token Factory API.
## Quick Start
```bash
# Install dependencies
pnpm install
# Start server
pnpm start
# Or in development mode
pnpm run dev
```
The Swagger UI will be available at:
- **Documentation**: http://localhost:8080/api-docs
- **OpenAPI JSON**: http://localhost:8080/openapi.json
- **OpenAPI YAML**: http://localhost:8080/openapi.yaml
## Features
- ✅ Interactive API documentation
- ✅ Try-it-out functionality
- ✅ Request/response examples
- ✅ Authentication testing (OAuth2, mTLS, API Key)
- ✅ Schema exploration
- ✅ Export OpenAPI spec (JSON/YAML)
## Configuration
Set environment variables:
```bash
export SWAGGER_PORT=8080 # Default: 8080
```
## Usage
1. Navigate to http://localhost:8080/api-docs
2. Click "Authorize" to set up authentication
3. Explore endpoints by expanding sections
4. Use "Try it out" to test API calls
5. View request/response schemas
## Integration
This server can be:
- Deployed standalone for documentation
- Integrated into main API server
- Used in CI/CD for API validation
- Embedded in developer portals

View File

@@ -0,0 +1,278 @@
# Swagger Documentation - Complete Guide
## Overview
Full Swagger/OpenAPI documentation for the eMoney Token Factory API is available through an interactive Swagger UI server.
## Quick Start
### Option 1: Node.js Server (Recommended)
```bash
cd api/tools/swagger-ui
pnpm install
pnpm run dev
```
**Access**: http://localhost:8080/api-docs
### Option 2: Docker
```bash
cd api/tools/swagger-ui
docker-compose up
```
**Access**: http://localhost:8080/api-docs
### Option 3: Standalone HTML
```bash
cd api/tools/swagger-ui
pnpm install
pnpm run build
pnpm run generate:standalone
```
**Output**: `static/standalone.html` (open directly in browser)
## Documentation Endpoints
When the server is running:
- **Interactive Docs**: http://localhost:8080/api-docs
- **OpenAPI JSON**: http://localhost:8080/openapi.json
- **OpenAPI YAML**: http://localhost:8080/openapi.yaml
- **Standalone HTML**: http://localhost:8080/standalone
- **Health Check**: http://localhost:8080/health
## Features
### 1. Complete API Coverage
All API endpoints documented:
- ✅ Token operations (deploy, mint, burn, clawback, force-transfer)
- ✅ Lien management (place, reduce, release, query)
- ✅ Compliance operations (set, freeze, query)
- ✅ Account-Wallet mappings
- ✅ Trigger management (ISO-20022 processing)
- ✅ Packet operations (generate, dispatch, acknowledge)
- ✅ Bridge operations (lock, unlock)
### 2. Interactive Testing
- **Try It Out**: Test any endpoint directly from the browser
- **Request Builder**: Fill in parameters and see request format
- **Response Viewer**: See actual API responses
- **Error Handling**: View error responses with reason codes
### 3. Authentication Support
- **OAuth2**: Test client credentials flow
- **mTLS**: Configure mutual TLS for adapters
- **API Key**: Set API keys for internal services
- **Token Persistence**: Tokens saved across page reloads
### 4. Schema Documentation
- **Data Models**: All request/response schemas
- **Enums**: All enum values (ReasonCodes, TriggerStates, Rails, etc.)
- **Examples**: Example payloads for each endpoint
- **Validation Rules**: Field requirements and constraints
### 5. Export Options
- **Download JSON**: Get OpenAPI spec as JSON
- **Download YAML**: Get OpenAPI spec as YAML
- **Share Links**: Share specific endpoint documentation
- **Embed**: Embed Swagger UI in other applications
## API Modules Documented
### Tokens Module
- Deploy new eMoney tokens
- Configure token policies
- Mint and burn operations
- Clawback and force transfer
- Query token metadata
### Liens Module
- Place liens on accounts
- Reduce lien amounts
- Release liens
- Query lien information
- Check encumbrance summaries
### Compliance Module
- Set compliance profiles
- Freeze/unfreeze accounts
- Manage risk tiers
- Set jurisdiction information
- Query compliance status
### Mappings Module
- Link accounts to wallets
- Unlink mappings
- Query account wallets
- Query wallet accounts
### Triggers Module
- Submit ISO-20022 messages
- Query trigger status
- Manage trigger lifecycle
- View trigger history
### ISO-20022 Module
- Submit inbound messages (from rails)
- Submit outbound messages (to rails)
- Message normalization
- Instruction ID tracking
### Packets Module
- Generate packets (PDF/AS4/Email)
- Dispatch packets
- Track acknowledgements
- Download packet files
### Bridge Module
- Lock tokens for cross-chain
- Unlock tokens with proofs
- Query lock status
- View supported corridors
## Usage Examples
### Testing Token Deployment
1. Navigate to `/tokens` endpoint
2. Click "Try it out"
3. Fill in token configuration:
```json
{
"name": "USD Wrapped",
"symbol": "USDW",
"decimals": 18,
"issuer": "0x1234...",
"defaultLienMode": "ENCUMBERED"
}
```
4. Click "Execute"
5. View response with token address
### Testing Lien Placement
1. Navigate to `/liens` endpoint
2. Click "Try it out"
3. Fill in lien details:
```json
{
"debtor": "0xabcd...",
"amount": "1000000000000000000",
"priority": 1,
"reasonCode": "DEBT_ENFORCEMENT"
}
```
4. Click "Execute"
5. View response with lien ID
### Setting Authentication
1. Click "Authorize" button at top
2. Enter OAuth2 token in "Value" field
3. Click "Authorize"
4. Token will be used for all requests
5. Click "Logout" to clear
## Integration
### Embed in Main API Server
Add to `api/services/rest-api/src/index.ts`:
```typescript
import swaggerUi from 'swagger-ui-express';
import YAML from 'yamljs';
import { join } from 'path';
const openapiSpec = YAML.load(join(__dirname, '../../packages/openapi/v1/openapi.yaml'));
app.use('/docs', swaggerUi.serve, swaggerUi.setup(openapiSpec));
```
### Production Deployment
1. **Standalone Service**: Deploy as separate service
2. **CDN Distribution**: Serve via CDN for performance
3. **Embedded**: Include in main API server
4. **Static HTML**: Generate and serve static file
## Customization
### Change Port
```bash
export SWAGGER_PORT=9000
pnpm start
```
### Custom Theme
Edit `src/index.ts`:
```typescript
const swaggerOptions = {
customCss: `
.swagger-ui .info .title {
color: #your-brand-color;
font-family: 'Your Font';
}
`,
};
```
### Default Server
Set default API server:
```typescript
swaggerOptions: {
url: 'https://api.emoney.example.com/v1',
}
```
## Troubleshooting
### Server Won't Start
- Check if port 8080 is available
- Verify OpenAPI spec path is correct
- Check YAML syntax is valid
### Spec Not Loading
- Verify `openapi.yaml` exists
- Check file permissions
- Validate YAML syntax
### Try It Out Fails
- Check CORS settings on API server
- Verify authentication is set
- Check network tab for errors
- Ensure API server is running
## Best Practices
1. **Keep Spec Updated**: Update OpenAPI spec as API changes
2. **Use Examples**: Provide realistic examples in spec
3. **Document Errors**: Include error response examples
4. **Test Regularly**: Use Try It Out to validate endpoints
5. **Share Links**: Share specific endpoint URLs with team
## Additional Resources
- [OpenAPI Specification](https://swagger.io/specification/)
- [Swagger UI Documentation](https://swagger.io/tools/swagger-ui/)
- [API Integration Cookbook](../docs/api/integration-cookbook.md)
- [Error Catalog](../docs/api/error-catalog.md)

View File

@@ -0,0 +1,13 @@
version: '3.8'
services:
swagger-ui:
build: .
ports:
- "8080:8080"
environment:
- SWAGGER_PORT=8080
volumes:
- ../packages/openapi/v1/openapi.yaml:/app/packages/openapi/v1/openapi.yaml:ro
restart: unless-stopped

View File

@@ -0,0 +1,28 @@
{
"name": "@emoney/swagger-ui",
"version": "1.0.0",
"description": "Swagger UI server for eMoney API documentation",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "ts-node-dev --respawn --transpile-only src/index.ts",
"generate:standalone": "ts-node src/generate-standalone.ts",
"docker:build": "docker build -t emoney-swagger-ui .",
"docker:run": "docker run -p 8080:8080 emoney-swagger-ui"
},
"dependencies": {
"express": "^4.18.2",
"swagger-ui-express": "^5.0.0",
"swagger-jsdoc": "^6.2.8",
"yamljs": "^0.3.0"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/swagger-ui-express": "^4.1.4",
"@types/node": "^20.10.0",
"typescript": "^5.3.0",
"ts-node-dev": "^2.0.0",
"ts-node": "^10.9.2"
}
}

View File

@@ -0,0 +1,87 @@
/**
* Generate standalone HTML documentation
* Creates a self-contained HTML file with embedded OpenAPI spec
*/
import { readFileSync, writeFileSync } from 'fs';
import { join } from 'path';
import YAML from 'yamljs';
const openapiPath = join(__dirname, '../../packages/openapi/v1/openapi.yaml');
const openapiSpec = YAML.load(openapiPath);
const outputPath = join(__dirname, '../swagger-ui/static/standalone.html');
const htmlTemplate = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>eMoney Token Factory API Documentation</title>
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5.10.3/swagger-ui.css" />
<style>
html {
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
margin: 0;
padding: 0;
background: #fafafa;
}
.swagger-ui .topbar {
display: none;
}
.swagger-ui .info {
margin: 50px 0;
}
.swagger-ui .info .title {
font-size: 36px;
color: #3b4151;
}
.swagger-ui .info .description {
font-size: 16px;
line-height: 1.6;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@5.10.3/swagger-ui-bundle.js"></script>
<script src="https://unpkg.com/swagger-ui-dist@5.10.3/swagger-ui-standalone-preset.js"></script>
<script>
window.onload = function() {
const spec = ${JSON.stringify(openapiSpec, null, 2)};
const ui = SwaggerUIBundle({
spec: spec,
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout",
persistAuthorization: true,
displayRequestDuration: true,
filter: true,
tryItOutEnabled: true,
supportedSubmitMethods: ['get', 'post', 'put', 'patch', 'delete'],
docExpansion: 'list',
defaultModelsExpandDepth: 2,
defaultModelExpandDepth: 2,
});
};
</script>
</body>
</html>`;
writeFileSync(outputPath, htmlTemplate);
console.log(`Standalone HTML documentation generated: ${outputPath}`);

View File

@@ -0,0 +1,85 @@
/**
* Swagger UI Server
* Serves interactive API documentation from OpenAPI specification
*/
import express from 'express';
import swaggerUi from 'swagger-ui-express';
import YAML from 'yamljs';
import { readFileSync } from 'fs';
import { join } from 'path';
import path from 'path';
const app = express();
const PORT = process.env.SWAGGER_PORT || 8080;
// Load OpenAPI specification
const openapiPath = join(__dirname, '../../packages/openapi/v1/openapi.yaml');
const openapiSpec = YAML.load(openapiPath);
// Serve static files (OAuth2 redirect, standalone HTML)
app.use('/static', express.static(join(__dirname, '../../swagger-ui/static')));
// Swagger UI options
const swaggerOptions = {
customCss: `
.swagger-ui .topbar { display: none; }
.swagger-ui .info { margin: 50px 0; }
.swagger-ui .info .title { font-size: 36px; }
.swagger-ui .info .description { font-size: 16px; line-height: 1.6; }
.swagger-ui .scheme-container { margin: 20px 0; }
`,
customSiteTitle: 'eMoney Token Factory API Documentation',
customfavIcon: '/favicon.ico',
swaggerOptions: {
persistAuthorization: true,
displayRequestDuration: true,
filter: true,
tryItOutEnabled: true,
supportedSubmitMethods: ['get', 'post', 'put', 'patch', 'delete'],
docExpansion: 'list',
defaultModelsExpandDepth: 2,
defaultModelExpandDepth: 2,
oauth2RedirectUrl: '/static/oauth2-redirect.html',
},
};
// Serve Swagger UI
app.use('/api-docs', swaggerUi.serve);
app.get('/api-docs', swaggerUi.setup(openapiSpec, swaggerOptions));
// Serve OpenAPI spec as JSON
app.get('/openapi.json', (req, res) => {
res.json(openapiSpec);
});
// Serve OpenAPI spec as YAML
app.get('/openapi.yaml', (req, res) => {
res.setHeader('Content-Type', 'text/yaml');
res.send(readFileSync(openapiPath, 'utf-8'));
});
// Serve standalone HTML
app.get('/standalone', (req, res) => {
res.sendFile(join(__dirname, '../../swagger-ui/static/standalone.html'));
});
// Redirect root to docs
app.get('/', (req, res) => {
res.redirect('/api-docs');
});
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok', service: 'swagger-ui' });
});
app.listen(PORT, () => {
console.log(`Swagger UI server running on http://localhost:${PORT}`);
console.log(`API Documentation: http://localhost:${PORT}/api-docs`);
console.log(`OpenAPI JSON: http://localhost:${PORT}/openapi.json`);
console.log(`OpenAPI YAML: http://localhost:${PORT}/openapi.yaml`);
});
export default app;

View File

@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>eMoney Token Factory API Documentation</title>
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5.10.3/swagger-ui.css" />
<style>
html {
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
margin: 0;
padding: 0;
background: #fafafa;
}
.swagger-ui .topbar {
display: none;
}
.swagger-ui .info {
margin: 50px 0;
}
.swagger-ui .info .title {
font-size: 36px;
color: #3b4151;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@5.10.3/swagger-ui-bundle.js"></script>
<script src="https://unpkg.com/swagger-ui-dist@5.10.3/swagger-ui-standalone-preset.js"></script>
<script>
window.onload = function() {
const ui = SwaggerUIBundle({
url: "/openapi.yaml",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout",
persistAuthorization: true,
displayRequestDuration: true,
filter: true,
tryItOutEnabled: true,
supportedSubmitMethods: ['get', 'post', 'put', 'patch', 'delete'],
docExpansion: 'list',
defaultModelsExpandDepth: 2,
defaultModelExpandDepth: 2,
oauth2RedirectUrl: window.location.origin + '/oauth2-redirect.html'
});
};
</script>
</body>
</html>

View File

@@ -0,0 +1,76 @@
<!doctype html>
<html>
<head>
<title>Swagger UI: OAuth2 Redirect</title>
</head>
<body>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1);
} else {
qp = location.search.substring(1);
}
arr = qp.split("&");
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
qp = qp ? JSON.parse('{' + arr.join() +'}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value);
}
) : {};
isValid = qp.state === sentState;
if ((
oauth2.auth.schema.get("flow") === "accessCode" ||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
oauth2.auth.schema.get("flow") === "authorization_code"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
err: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg;
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
err: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
if (document.readyState !== 'loading') {
run();
} else {
document.addEventListener('DOMContentLoaded', run);
}
</script>
</body>
</html>

View File

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