Service Architecture
Overview
The UBI Wallet Service is a comprehensive digital wallet solution built with NestJS that serves as a middleware between consumer applications and wallet service providers. The system provides a standardized API for managing Verifiable Credentials (VCs) and user authentication across multiple wallet providers through a pluggable adapter pattern.
High-Level Architecture
Core Components
1. User Management Module (src/users/
)
src/users/
)Responsibilities:
User registration and onboarding
Authentication and authorization
User profile management
Password hashing and validation
Key Files:
user.entity.ts
: Database entity definitionuser.service.ts
: Business logic for user operationsuser.module.ts
: Module configuration
2. Wallet Module (src/wallet/
)
src/wallet/
)Responsibilities:
Wallet operations coordination
VC management and operations
VC watching and callback processing
API endpoint definitions
Key Files:
wallet.controller.ts
: REST API endpointswallet.service.ts
: Core business logicwallet-vc.entity.ts
: VC database entitywallet-vc-watcher.entity.ts
: VC watcher entitywatcher-cron.service.ts
: Scheduled tasks for VC monitoring
3. Adapter Layer (src/adapters/
)
src/adapters/
)Responsibilities:
External wallet provider integration
API communication abstraction
Response transformation
Error handling for external services
Key Files:
adapter.factory.ts
: Adapter creation factorydhiway.adapter.ts
: Dhiway-specific implementationinterfaces/wallet-adapter.interface.ts
: Adapter contracts
4. Common Module (src/common/
)
src/common/
)Responsibilities:
Shared utilities and services
Logging infrastructure
Authentication guards
Common decorators
Key Files:
logger/logger.service.ts
: Centralized loggingguards/auth.guard.ts
: Authentication guarddecorators/user.decorator.ts
: User extraction decorator
5. Housekeeping Module (src/housekeeping/
)
src/housekeeping/
)Responsibilities:
Automated maintenance tasks
VC synchronization
Missing VC detection and registration
System health monitoring
Data Flow Diagrams
User Registration Flow
VC Upload Flow
VC Watching Flow
Watcher Flow 1 – Watcher registered when VC is added in Wallet
This flow describes the scenario where a watcher is automatically registered when a VC is added to the wallet service.
Flow 1 Description:
Trigger: When a VC is uploaded/added to the wallet service
Process: The wallet service automatically registers a watcher with the Dhiway issuance instance
Monitoring: The watcher monitors for any updates to the VC
Update Handling: When the VC is updated, the issuance instance sends a callback to the wallet service
Synchronization: The wallet service fetches the latest VC details and updates its local database
Watcher Flow 2 – Watcher registered when VC is fetched in Beneficiary
This flow describes the scenario where a watcher is registered when a VC is fetched by a beneficiary service.
Flow 2 Description:
Trigger: When a beneficiary service requests a VC from the wallet service
Process: The beneficiary service explicitly requests the wallet service to register a watcher
Registration: The wallet service registers the watcher with the Dhiway issuance instance
Monitoring: The watcher monitors for any updates to the VC
Update Handling: When the VC is updated, the issuance instance sends a callback to the wallet service
Synchronization: Both the wallet service and beneficiary service are notified and updated with the latest VC details
Adding New Wallet Providers
The system is designed to be provider-agnostic through the adapter pattern. To add a new wallet provider, follow these steps:
1. Create a New Adapter
Create a new adapter file in src/adapters/
(e.g., newprovider.adapter.ts
):
import { Injectable } from '@nestjs/common';
import { IWalletAdapter, IWalletAdapterWithOtp } from './interfaces/wallet-adapter.interface';
import { OnboardUserDto } from '../dto/onboard-user.dto';
import { UploadVcDto } from '../dto/upload-vc.dto';
import { WatchVcDto } from '../dto/watch-vc.dto';
import { WatchCallbackDto } from '../dto/watch-callback.dto';
@Injectable()
export class NewProviderAdapter implements IWalletAdapter {
private readonly apiBaseUrl: string;
private readonly apiKey: string;
constructor() {
this.apiBaseUrl = process.env.NEW_PROVIDER_API_BASE || '';
this.apiKey = process.env.NEW_PROVIDER_API_KEY || '';
}
async onboardUser(data: OnboardUserDto) {
// Implement user onboarding logic
// Make API calls to the new provider
// Transform responses to match the interface
}
async getAllVCs(userId: string, token: string) {
// Implement VC listing logic
}
async getVCById(userId: string, vcId: string, token: string) {
// Implement VC retrieval logic
}
async uploadVCFromQR(userId: string, qrData: string, token: string) {
// Implement VC upload logic
}
async login(data: any) {
// Implement login logic
}
// Optional: Implement OTP methods if supported
async verifyLogin?(data: any) {
// Implement OTP verification
}
async resendOtp?(data: any) {
// Implement OTP resend
}
// Optional: Implement watching methods if supported
async watchVC?(data: WatchVcDto) {
// Implement VC watching
}
async processCallback?(data: WatchCallbackDto) {
// Implement callback processing
}
}
2. Update the Adapter Factory
Modify src/adapters/adapter.factory.ts
:
import { DhiwayAdapter } from './dhiway.adapter';
import { NewProviderAdapter } from './newprovider.adapter';
export function createWalletAdapter(): IWalletAdapter {
const provider = process.env.WALLET_PROVIDER || 'dhiway';
switch (provider) {
case 'dhiway':
return new DhiwayAdapter();
case 'newprovider':
return new NewProviderAdapter();
default:
throw new Error(`Unsupported wallet provider: ${provider}`);
}
}
3. Update Environment Variables
Add new provider configuration to .env.example
:
# New Provider Configuration
NEW_PROVIDER_API_BASE=https://api.newprovider.com
NEW_PROVIDER_API_KEY=your-new-provider-api-key
4. Update the Wallet Module
Modify src/wallet/wallet.module.ts
:
import { NewProviderAdapter } from '../adapters/newprovider.adapter';
@Module({
// ... existing imports
providers: [
WalletService,
WalletVCService,
WalletVCWatcherService,
WatcherCronService,
{
provide: 'WALLET_ADAPTER',
useFactory: createWalletAdapter,
},
DhiwayAdapter,
NewProviderAdapter, // Add new adapter
],
// ... rest of module
})
export class WalletModule {}
5. Update Configuration
Set the new provider in your environment:
WALLET_PROVIDER=newprovider
Design Patterns Used
Adapter Pattern: Used for wallet provider integration
Repository Pattern: Used for data access layer
Service Layer Pattern: Used for business logic
Observer Pattern: Used for VC watching and callbacks
Factory Pattern: Used for adapter creation
Last updated
Was this helpful?