Skip to content

feat(wallet): add runtime auto-withdrawal thresholds - #45

Draft
marek-chmielowski-blurify wants to merge 1 commit into
devfrom
feat/wallet-auto-withdrawal-thresholds
Draft

feat(wallet): add runtime auto-withdrawal thresholds#45
marek-chmielowski-blurify wants to merge 1 commit into
devfrom
feat/wallet-auto-withdrawal-thresholds

Conversation

@marek-chmielowski-blurify

Copy link
Copy Markdown
Collaborator

Summary

Moves global fiat and crypto auto-withdrawal thresholds from static platform configuration into a DB-backed wallet singleton. Adds Super Admin read/update routes, audit recording, safe seed defaults, migration coverage, and unit/e2e tests.

Why

Introduced these changes so operators can update auto-withdrawal thresholds at runtime without a redeploy while preserving fail-closed payout behavior and a complete audit trail.

Worth knowing

The new wallet_auto_withdrawal_config row is a global singleton for the current single-tenant runtime, so it intentionally has no tenantId or RLS policy. Both seeded thresholds default to zero, preventing an upgrade from silently enabling auto-approval; missing config also fails closed.

  • pnpm verify is green (typecheck + lint + boundaries + module-shape + tests)
  • pnpm check:drift is green (catalog / OpenAPI not stale) - run pnpm regen if not
  • New cross-module talk goes through events / command ports / contracts / the /schema subpath (no direct module imports)
  • New data tables carry tenantId and are RLS-covered (pnpm regen runs gen:rls)
  • No secrets, real player data, or internal/customer names added

Closes BF-211

@@ -1047,14 +1062,54 @@ export class WalletService {
if (rule) {
return { value: rule.threshold, source: 'per-player' };

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Blocking: when the singleton row is missing, this early return skips getAutoWithdrawalConfig(). A player with an override can still have a withdrawal auto-approved, so an unseeded install is not fail-closed. Check that the global row exists before accepting the per-player threshold.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I know this return already existed, but this PR adds the new fail-closed requirement based on the DB singleton. Because getAutoWithdrawalConfig() is called only after the early return, that requirement is skipped for players with a per-player rule.

// install, and `set` below self-heals it via upsert - a missing `before`
// must not block that self-heal.
const before = await wallet.getAutoWithdrawalConfigOrNull();
const config = await wallet.setAutoWithdrawalConfig(adminId, input);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Blocking: this commits the threshold update before the audit write. If audit.record() fails, the API returns an error but the threshold remains changed without the required audit entry. Make the update and audit record atomic, or roll the update back when auditing fails.

export type WalletAutoWithdrawalConfig = z.infer<typeof WalletAutoWithdrawalConfigSchema>;

export const SetWalletAutoWithdrawalConfigInputSchema = z.object({
fiatThreshold: MoneyAmountSchema,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This accepts unlimited integer digits, but the database column is numeric(18,8) and only allows 10 digits before the decimal. For example, 10000000000 passes this schema and then causes a database 500. Limit the input precision so invalid values return a 4xx response.

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.

2 participants