Skip to content
Open
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
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@nestjs/platform-express": "^11.0.1",
"@nestjs/swagger": "^11.2.6",
"@nestjs/throttler": "^6.5.0",
"@nestjs/schedule": "^3.0.0",
"@nestjs/typeorm": "^11.0.0",
"@stellar/stellar-sdk": "^14.5.0",
"axios": "^1.13.5",
Expand Down
6 changes: 6 additions & 0 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler';
import { APP_GUARD } from '@nestjs/core';
import { ScheduleModule } from '@nestjs/schedule';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import configuration from './config/configuration';
Expand All @@ -19,6 +20,7 @@ import { WebhooksModule } from './modules/webhooks/webhooks.module';
import { ClaimsModule } from './modules/claims/claims.module';
import { DisputesModule } from './modules/disputes/disputes.module';
import { AdminAnalyticsModule } from './modules/admin-analytics/admin-analytics.module';
import { SweepModule } from './modules/sweep/sweep.module';

@Module({
imports: [
Expand All @@ -40,6 +42,7 @@ import { AdminAnalyticsModule } from './modules/admin-analytics/admin-analytics.
synchronize: true,
}),
}),
ScheduleModule.forRoot(),
AuthModule,
RedisCacheModule,
HealthModule,
Expand All @@ -51,6 +54,9 @@ import { AdminAnalyticsModule } from './modules/admin-analytics/admin-analytics.
ClaimsModule,
DisputesModule,
AdminAnalyticsModule,
// Sweep tasks for periodic sweeping of excess funds
// SweepModule is added below
SweepModule,
ThrottlerModule.forRoot([
{
ttl: 60000,
Expand Down
69 changes: 69 additions & 0 deletions backend/src/modules/sweep/sweep-tasks.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { UserService } from '../user/user.service';
import { StellarService } from '../blockchain/stellar.service';

@Injectable()
export class SweepTasksService {
private readonly logger = new Logger(SweepTasksService.name);

constructor(
private readonly userService: UserService,
private readonly stellarService: StellarService,
) {}

// Runs every minute; adjust schedule as needed
@Cron(CronExpression.EVERY_MINUTE)
async handleSweep() {
this.logger.log('Starting sweep job');

// Fetch users who have auto-sweep enabled. The UserService method is expected
// to return users with their sweep preferences. If not present, this should
// be implemented on the UserService / user entity.
const usersWithSweep: any[] =
(this.userService as any).findUsersWithAutoSweep
? await (this.userService as any).findUsersWithAutoSweep()
: [];

this.logger.log(`Found ${usersWithSweep.length} users with auto-sweep`);

for (const user of usersWithSweep) {
try {
const threshold = (user as any).sweepThreshold ?? 0;

// Attempt to fetch balance from StellarService if available; otherwise
// fall back to a stubbed value (0) so the job remains safe.
const balance =
(this.stellarService as any).getAccountBalance?.(user.publicKey) ??
(user as any).mockBalance ??
0;

const excess = Number(balance) - Number(threshold);

if (excess > 0) {
this.logger.log(
`User ${user.id} has excess funds: ${excess} (balance ${balance}, threshold ${threshold})`,
);

// Stubbed transfer logic — replace with real transfer via StellarService / SavingsService
await this.transferToSavingsStub(user, excess);
}
} catch (err) {
this.logger.error(`Sweep failed for user ${user.id}`, err as Error);
}
}

this.logger.log('Sweep job complete');
}

private async transferToSavingsStub(user: any, amount: number) {
// This is intentionally a stub. Real implementation should:
// - Build and sign a Stellar transaction (or call a savings contract)
// - Move funds from user wallet to savings product
// - Record the sweep transaction in the DB
this.logger.log(
`Stub: transferring ${amount} from ${user.id} (pub ${user.publicKey}) to savings product`,
);
return Promise.resolve(true);
}
}
10 changes: 10 additions & 0 deletions backend/src/modules/sweep/sweep.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { SweepTasksService } from './sweep-tasks.service';
import { UserModule } from '../user/user.module';
import { BlockchainModule } from '../blockchain/blockchain.module';

@Module({
imports: [UserModule, BlockchainModule],
providers: [SweepTasksService],
})
export class SweepModule {}
64 changes: 64 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading