From 5e1e71946999aab8ca84ff5526d9a14c795c7952 Mon Sep 17 00:00:00 2001 From: khaadish <176320316+khaadish@users.noreply.github.com> Date: Sun, 23 Aug 2026 06:25:40 +0100 Subject: [PATCH] feat(blockchain): wire BlockchainModule into dependent modules Register STELLAR_* environment variables through a namespaced stellar config loaded via ConfigModule.forFeature, and import BlockchainModule in ProgressModule, UsersModule and QuestsModule so BlockchainService is injectable where on-chain actions are triggered (issues 11-14). Add a module-wiring spec guarding exports and consumer imports. --- backend/.env.example | 6 +++ .../src/blockchain/blockchain.module.spec.ts | 48 +++++++++++++++++++ backend/src/blockchain/blockchain.module.ts | 21 +++++++- backend/src/config/stellar.config.ts | 17 +++++++ backend/src/progress/progress.module.ts | 4 ++ backend/src/quests/quests.module.ts | 4 ++ backend/src/users/users.module.ts | 4 ++ 7 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 backend/src/blockchain/blockchain.module.spec.ts create mode 100644 backend/src/config/stellar.config.ts diff --git a/backend/.env.example b/backend/.env.example index 3e53f67a..e4a9a49a 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -27,3 +27,9 @@ MAIL_USER=your-email@gmail.com MAIL_PASSWORD=your-app-password MAIL_FROM_NAME=MindBlock MAIL_FROM_ADDRESS=noreply@mindblock.com + +# Stellar / Soroban configuration (BlockchainModule) +STELLAR_SECRET_KEY=your-stellar-secret-key +STELLAR_CONTRACT_ID=your-soroban-contract-id +STELLAR_RPC_URL=https://soroban-testnet.stellar.org +STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015 diff --git a/backend/src/blockchain/blockchain.module.spec.ts b/backend/src/blockchain/blockchain.module.spec.ts new file mode 100644 index 00000000..251922a1 --- /dev/null +++ b/backend/src/blockchain/blockchain.module.spec.ts @@ -0,0 +1,48 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { EventEmitterModule } from '@nestjs/event-emitter'; +import { BlockchainModule } from './blockchain.module'; +import { BlockchainService } from './provider/blockchain.service'; +import { ProgressModule } from '../progress/progress.module'; +import { UsersModule } from '../users/users.module'; +import { QuestsModule } from '../quests/quests.module'; + +const metadataArrayOf = (key: 'imports' | 'providers', target: object) => + (Reflect.getMetadata(key, target) as unknown[] | undefined) ?? []; + +describe('BlockchainModule wiring', () => { + it('provides and exports BlockchainService', async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [EventEmitterModule.forRoot(), BlockchainModule], + }).compile(); + + expect(module.get(BlockchainService, { strict: false })).toBeInstanceOf( + BlockchainService, + ); + }); + + it('is imported by ProgressModule', () => { + expect(metadataArrayOf('imports', ProgressModule)).toContain( + BlockchainModule, + ); + }); + + it('is imported by UsersModule', () => { + expect(metadataArrayOf('imports', UsersModule)).toContain(BlockchainModule); + }); + + it('is imported by QuestsModule', () => { + expect(metadataArrayOf('imports', QuestsModule)).toContain( + BlockchainModule, + ); + }); + + it('does not re-export consumers as service providers', () => { + // BlockchainService must come through the BlockchainModule import, + // never be listed directly in a consumer's own providers array. + for (const consumer of [ProgressModule, UsersModule, QuestsModule]) { + expect(metadataArrayOf('providers', consumer)).not.toContain( + BlockchainService, + ); + } + }); +}); diff --git a/backend/src/blockchain/blockchain.module.ts b/backend/src/blockchain/blockchain.module.ts index a10e5920..f512c339 100644 --- a/backend/src/blockchain/blockchain.module.ts +++ b/backend/src/blockchain/blockchain.module.ts @@ -1,10 +1,29 @@ import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import stellarConfig from '../config/stellar.config'; import { BlockchainController } from './controller/blockchain.controller'; import { BlockchainService } from './provider/blockchain.service'; +/** + * Registers the blockchain providers and exports BlockchainService so any + * importing module (ProgressModule, UsersModule, QuestsModule, ...) can + * inject it to trigger on-chain actions. + * + * STELLAR_SECRET_KEY, STELLAR_CONTRACT_ID, STELLAR_RPC_URL and + * STELLAR_NETWORK_PASSPHRASE are exposed through the namespaced + * `stellar` configuration registered below (see src/config/stellar.config.ts). + */ @Module({ + imports: [ConfigModule.forFeature(stellarConfig)], controllers: [BlockchainController], - providers: [BlockchainService], + providers: [ + // On-chain action providers land with issues 11-14: + // RegisterPlayerProvider, SubmitPuzzleProvider, GetPlayerProvider, + // SyncXpMilestoneProvider. Each must be added to this array (and the + // matching method on BlockchainService) as soon as it is merged so + // they are injectable alongside BlockchainService. + BlockchainService, + ], exports: [BlockchainService], }) export class BlockchainModule {} diff --git a/backend/src/config/stellar.config.ts b/backend/src/config/stellar.config.ts new file mode 100644 index 00000000..731cb447 --- /dev/null +++ b/backend/src/config/stellar.config.ts @@ -0,0 +1,17 @@ +import { registerAs } from '@nestjs/config'; + +/** + * Stellar/Soroban on-chain configuration. + * + * Loaded through `ConfigModule.forFeature(stellarConfig)` inside + * BlockchainModule so every blockchain provider can resolve these values + * via `ConfigService.get('stellar')` without reaching for `process.env`. + */ +export default registerAs('stellar', () => ({ + secretKey: process.env.STELLAR_SECRET_KEY, + contractId: process.env.STELLAR_CONTRACT_ID, + rpcUrl: process.env.STELLAR_RPC_URL || 'https://soroban-testnet.stellar.org', + networkPassphrase: + process.env.STELLAR_NETWORK_PASSPHRASE || + 'Test SDF Network ; September 2015', +})); diff --git a/backend/src/progress/progress.module.ts b/backend/src/progress/progress.module.ts index 8b7c4aaa..feb16442 100644 --- a/backend/src/progress/progress.module.ts +++ b/backend/src/progress/progress.module.ts @@ -13,10 +13,14 @@ import { ProgressCalculationProvider } from './providers/progress-calculation.pr import { Puzzle } from '../puzzles/entities/puzzle.entity'; import { XpLevelService } from '../users/providers/xp-level.service'; import { ScoreService } from '../score/providers/score.service'; +import { BlockchainModule } from '../blockchain/blockchain.module'; @Module({ imports: [ TypeOrmModule.forFeature([UserProgress, User, Puzzle, Streak, DailyQuest]), + // BlockchainService is needed by ProgressCalculationProvider for + // submitPuzzleOnChain after puzzle answer verification. + BlockchainModule, ], controllers: [ProgressController], providers: [ diff --git a/backend/src/quests/quests.module.ts b/backend/src/quests/quests.module.ts index ef2f9285..f079ed72 100644 --- a/backend/src/quests/quests.module.ts +++ b/backend/src/quests/quests.module.ts @@ -11,6 +11,7 @@ import { PuzzlesModule } from '../puzzles/puzzles.module'; import { ProgressModule } from '../progress/progress.module'; import { UsersModule } from '../users/users.module'; import { StreakModule } from '../streak/strerak.module'; +import { BlockchainModule } from '../blockchain/blockchain.module'; @Module({ imports: [ @@ -19,6 +20,9 @@ import { StreakModule } from '../streak/strerak.module'; ProgressModule, UsersModule, StreakModule, + // BlockchainService is needed by CompleteDailyQuestProvider for + // syncXpMilestone after the daily quest completion bonus XP is awarded. + BlockchainModule, ], controllers: [DailyQuestController], providers: [ diff --git a/backend/src/users/users.module.ts b/backend/src/users/users.module.ts index b47ff044..4c66e855 100644 --- a/backend/src/users/users.module.ts +++ b/backend/src/users/users.module.ts @@ -17,12 +17,16 @@ import { UserProgress } from '../progress/entities/progress.entity'; import { Streak } from '../streak/entities/streak.entity'; import { DailyQuest } from '../quests/entities/daily-quest.entity'; import { XpLevelService } from './providers/xp-level.service'; +import { BlockchainModule } from '../blockchain/blockchain.module'; @Module({ imports: [ forwardRef(() => AuthModule), TypeOrmModule.forFeature([User, UserProgress, Streak, DailyQuest]), PaginationModule, + // BlockchainService is needed by the wallet-linking onboarding flow + // (registerPlayerOnChain) and for syncXpMilestone after level-up. + BlockchainModule, ], controllers: [UsersController], providers: [