Skip to content

[476] Backend: Add per-wallet daily withdrawal limit guard with admin override#655

Merged
Junirezz merged 7 commits into
Junirezz:mainfrom
yinkscss:fix/476-backend-add-per-wallet-daily-withdrawal-limit-guard-with-admin-override
May 30, 2026
Merged

[476] Backend: Add per-wallet daily withdrawal limit guard with admin override#655
Junirezz merged 7 commits into
Junirezz:mainfrom
yinkscss:fix/476-backend-add-per-wallet-daily-withdrawal-limit-guard-with-admin-override

Conversation

@yinkscss
Copy link
Copy Markdown
Contributor

Summary

  • Adds UTC daily withdrawal tracking per wallet against WITHDRAWAL_DAILY_LIMIT_USDC
  • Blocks over-limit withdrawals with structured 429 responses including remaining allowance and reset time
  • Supports super-admin inline override (x-admin-override-withdrawal + overrideReason) and admin override grants via POST /admin/withdrawal-limits/override
  • Audits blocked and overridden attempts via admin audit log and GET /admin/withdrawal-limits/audit

Closes #476

Test plan

  • npm test -- --testPathPattern=withdrawalDailyLimit (3 tests passing)
  • Over-limit withdrawal returns 429 with limit metadata
  • Super-admin override allows over-limit withdrawal
  • Admin override endpoint creates temporary override record

Copilot AI review requested due to automatic review settings May 30, 2026 12:29
@drips-wave
Copy link
Copy Markdown

drips-wave Bot commented May 30, 2026

@yinkscss Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR adds a daily withdrawal limit guard with admin override, an impersonation session ledger with start/end/list endpoints, and updates the Soroban client to use the newer @stellar/stellar-sdk rpc namespace. It also includes several smaller refactors and cleanups.

Changes:

  • New withdrawalDailyLimitMiddleware and admin endpoints to enforce/override per-wallet daily withdrawal caps.
  • New impersonationSessionService with Prisma models, migration, ledger entries, and session-bound /admin/impersonate/:wallet access.
  • Migration to @stellar/stellar-sdk v13 rpc namespace and various small refactors (tracing noop span, redis SET arg order, Prisma.join separator, etc.).

Reviewed changes

Copilot reviewed 22 out of 23 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
backend/src/middleware/withdrawalDailyLimit.ts New middleware enforcing daily withdrawal caps with override support
backend/src/middleware/validate.ts Adds overrideReason to VaultOperationSchema
backend/src/vaultEndpoints.ts Wires withdrawal limit middleware into /withdrawals
backend/src/impersonationSessionService.ts New session/ledger service with memory/prisma/hybrid storage
backend/src/index.ts New impersonation session and withdrawal-override admin routes
backend/src/sorobanClient.ts Updates to new Stellar SDK rpc namespace and response shape
backend/src/tracing.ts Replaces real span with shared NOOP_SPAN when OTEL disabled
backend/src/pagination.ts Changes invalidCursor from let to const
backend/src/exportJobs.ts, apiKeyAudit.ts Replaces Prisma.sql separator with raw string in Prisma.join
backend/src/eventPollingService.ts Reorders Redis SET options; removes duplicate export
backend/src/emailQueue.ts Uses getPrismaClient(); coerces nullable text/html
backend/src/latencyMonitoring.ts, integration-test.ts Minor regex/let→const cleanups
backend/prisma/schema.prisma + migrations Adds AdminImpersonationSession, AdminImpersonationLedgerEntry, EmailQueue
backend/src/tests/*.test.ts New tests for withdrawal limits and impersonation sessions; governance updated
backend/.env.example, package.json New env vars and @stellar/stellar-sdk dependency
Files not reviewed (1)
  • backend/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +191 to +194
if (txResponse.status !== 'PENDING') {
const errorMessage = `Soroban transaction submission failed: ${
txResponse.errorResult?.toXDR?.('base64') || 'Unknown error'
}`;
Comment on lines +73 to +75
function createId(prefix: string): string {
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
}
Comment thread backend/src/pagination.ts
const limit = query.limit || DEFAULT_PAGINATION_CONFIG.defaultLimit;
let startIndex = 0;
let invalidCursor = false;
const invalidCursor = false;
Comment on lines +26 to +27
const inMemoryOverrides = new Map<string, WithdrawalLimitOverrideRecord>();
const inMemoryAuditLog: Array<Record<string, unknown>> = [];
Comment on lines 36 to 41
walletAddress: walletAddressSchema,
email: z.string().email().optional(),
referralCode: z.string().max(64).optional(),
overrideReason: z.string().min(1).max(500).optional(),
})
.strict();
Comment on lines +49 to +62
async function sumWithdrawalsForDay(wallet: string, start: Date, end: Date): Promise<Decimal> {
const rows = await prisma.transaction.findMany({
where: {
user: wallet,
type: 'withdrawal',
timestamp: {
gte: start,
lt: end,
},
},
select: { amount: true },
});

return rows.reduce((total: Decimal, row: { amount: string }) => total.plus(new Decimal(row.amount || '0')), new Decimal(0));
Comment thread backend/src/exportJobs.ts
const whereSql =
whereClauses.length > 0
? Prisma.sql`WHERE ${Prisma.join(whereClauses, Prisma.sql` AND `)}`
? Prisma.sql`WHERE ${Prisma.join(whereClauses, ' AND ')}`
Comment thread backend/src/tracing.ts Outdated
Comment on lines +91 to +96
const NOOP_SPAN = {
setAttributes: () => {},
setStatus: () => {},
recordException: () => {},
end: () => {},
} as unknown as Span;

// Try to acquire lock with 30 second expiry
const result = await client.set(this.lockKey, this.lockValue, 'NX', 'PX', 30000);
const result = await client.set(this.lockKey, this.lockValue, 'PX', 30000, 'NX');
Comment thread backend/src/index.ts
Comment on lines +1027 to +1030
const limit = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : 50;
res.status(200).json({
entries: listWithdrawalLimitAuditEntries(Number.isFinite(limit) ? limit : 50),
});
yinkscss added 4 commits May 30, 2026 14:14
Enforces configurable UTC daily caps on withdrawals, returns structured limit metadata on blocks, and supports super-admin overrides with full audit logging.
…ion.

Registers default admin keys in test setup and updates test wallets to valid 56-character base32 addresses so governance checks pass in CI.
Aligns validation, referral, and transaction tests with the 56-character base32 address format enforced by request schemas.
@yinkscss yinkscss force-pushed the fix/476-backend-add-per-wallet-daily-withdrawal-limit-guard-with-admin-override branch from 5ec24c5 to 726d02d Compare May 30, 2026 13:23
yinkscss and others added 3 commits May 30, 2026 14:26
Prevents CI migrate deploy from failing when a stale dev.db already contains tables pending migration.
…allets.

Sets high rate-limit ceilings before app bootstrap and fixes referral integration fixtures to use valid Stellar addresses.
@Junirezz Junirezz merged commit 3c14956 into Junirezz:main May 30, 2026
4 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Backend: Add per-wallet daily withdrawal limit guard with admin override

3 participants