From bac93118a30ca9bd32c60d51e9839d2e17449eab Mon Sep 17 00:00:00 2001 From: Victor Omorogbe Date: Tue, 5 May 2026 03:39:14 +0100 Subject: [PATCH] fix: ISSUE-002 - Backend: Implement environment configuration and validation --- .../src/config/config.module.ts | 18 ++++++ .../src/config/env.validation.ts | 11 ++++ packages/payments-engine/src/index.ts | 15 ++++- .../payments-engine/src/stellar.service.ts | 61 ++++--------------- 4 files changed, 55 insertions(+), 50 deletions(-) create mode 100644 packages/payments-engine/src/config/config.module.ts create mode 100644 packages/payments-engine/src/config/env.validation.ts diff --git a/packages/payments-engine/src/config/config.module.ts b/packages/payments-engine/src/config/config.module.ts new file mode 100644 index 0000000..3ea4b2e --- /dev/null +++ b/packages/payments-engine/src/config/config.module.ts @@ -0,0 +1,18 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { envValidationSchema } from './env.validation'; + +@Module({ + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + validationSchema: envValidationSchema, + validationOptions: { + abortEarly: true, + allowUnknown: true, + }, + }), + ], + exports: [ConfigModule], +}) +export class AppConfigModule {} diff --git a/packages/payments-engine/src/config/env.validation.ts b/packages/payments-engine/src/config/env.validation.ts new file mode 100644 index 0000000..508502c --- /dev/null +++ b/packages/payments-engine/src/config/env.validation.ts @@ -0,0 +1,11 @@ +import * as Joi from 'joi'; + +export const envValidationSchema = Joi.object({ + DATABASE_URL: Joi.string().uri().required(), + JWT_SECRET: Joi.string().min(16).required(), + TREASURY_PRIVATE_KEY: Joi.string().required(), + STELLAR_RPC_URL: Joi.string().uri().required(), + BTC_NODE_URL: Joi.string().uri().required(), + ETH_NODE_URL: Joi.string().uri().required(), + REDIS_URL: Joi.string().uri().required(), +}); diff --git a/packages/payments-engine/src/index.ts b/packages/payments-engine/src/index.ts index f56bb36..594b815 100644 --- a/packages/payments-engine/src/index.ts +++ b/packages/payments-engine/src/index.ts @@ -1 +1,14 @@ -export * from './stellar.service'; +import { Module } from '@nestjs/common'; +import { AppConfigModule } from './config/config.module'; +import { StellarService } from './stellar.service'; +// ... other imports + +@Module({ + imports: [ + AppConfigModule, + // ... other modules + ], + providers: [StellarService], + // ... +}) +export class PaymentsEngineModule {} diff --git a/packages/payments-engine/src/stellar.service.ts b/packages/payments-engine/src/stellar.service.ts index 026fccb..a09aec1 100644 --- a/packages/payments-engine/src/stellar.service.ts +++ b/packages/payments-engine/src/stellar.service.ts @@ -1,56 +1,19 @@ -import * as StellarSdk from 'stellar-sdk'; +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +@Injectable() export class StellarService { - private server: StellarSdk.Horizon.Server; - private sourceKeypair!: StellarSdk.Keypair; - - constructor() { - // Default to testnet if not explicitly set - const networkUrl = process.env.STELLAR_NETWORK_URL || 'https://horizon-testnet.stellar.org'; - this.server = new StellarSdk.Horizon.Server(networkUrl); - - // In production, this must be securely injected - const secret = - process.env.STELLAR_STORAGE_SECRET || - 'SAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; // Replace with a valid testnet secret for local dev - - try { - this.sourceKeypair = StellarSdk.Keypair.fromSecret(secret); - } catch { - console.warn('Invalid STELLAR_STORAGE_SECRET. Stellar operations will fail.'); - } + constructor(private configService: ConfigService) { + // validation at startup ensures all required env vars are present } - /** - * Sends funds from the operational storage to a destination address - */ - async sendFunds(destinationAddress: string, amount: string): Promise { - try { - const sourceAccount = await this.server.loadAccount(this.sourceKeypair.publicKey()); - - const transaction = new StellarSdk.TransactionBuilder(sourceAccount, { - fee: StellarSdk.BASE_FEE, - networkPassphrase: process.env.STELLAR_NETWORK_URL?.includes('public') - ? StellarSdk.Networks.PUBLIC - : StellarSdk.Networks.TESTNET, - }) - .addOperation( - StellarSdk.Operation.payment({ - destination: destinationAddress, - asset: StellarSdk.Asset.native(), - amount: amount, - }), - ) - .setTimeout(30) - .build(); - - transaction.sign(this.sourceKeypair); + private getStellarRpcUrl(): string { + return this.configService.get('STELLAR_RPC_URL'); + } - const response = await this.server.submitTransaction(transaction); - return response.hash; - } catch (error) { - console.error('Stellar transaction failed:', error); - throw error; // Rethrow to let the worker handle the failure state - } + private getTreasuryPrivateKey(): string { + return this.configService.get('TREASURY_PRIVATE_KEY'); } + + // Additional methods using ConfigService }