-
Notifications
You must be signed in to change notification settings - Fork 4
feat(promo): grant creation with a durable idempotency guard #178
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
cd7d2af
feat(promo): grant creation with a durable idempotency guard
zaxovaiko 890383e
fix(promo): snapshot weight profile rows onto the grant
zaxovaiko 8b7c205
feat(promo): hold the bonus balance on the grant row
zaxovaiko 682d3c1
refactor(promo): attribute a manual grant and bound the grant inputs
zaxovaiko e8a45f0
style(promo): format the grant migration snapshot
zaxovaiko 875827e
fix(promo): bound grant inputs and resolve a replay before live config
zaxovaiko 54a9015
fix(promo): attribute grants to the right actor and refuse dead weigh…
zaxovaiko b66730d
fix(promo): satisfy the wager product literal type in the grant servi…
zaxovaiko File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| # ADR-0040: Bonus funds live on the grant, not in the wallet balance | ||
|
|
||
| **Date**: 2026-09-18 | ||
| **Status**: Accepted | ||
|
|
||
| ## Context | ||
|
|
||
| The platform needs a bonus a player must wager before they can withdraw it. Two shapes were | ||
| available. | ||
|
|
||
| The first is the one already shipped for chat gifts and rain: the bonus is credited into | ||
| `wallet_balance` like any other money, a `wallet_bonus_credit` row records how much of it is still | ||
| un-wagered, and `debitWithdrawableBalance` subtracts a proportional locked share at withdrawal | ||
| time. One balance, one number the player sees, and a formula standing between them and their own | ||
| cash. | ||
|
|
||
| The second keeps bonus funds out of the wallet entirely. A grant row carries its own balance, a | ||
| bet decides which side pays, and the money becomes real exactly once - when the wagering | ||
| requirement is met. | ||
|
|
||
| The specification asks for the second: bonus balance tracked separately from real balance, bets | ||
| drawing on both, and a forfeit that takes the bonus together with any winnings it produced. The | ||
| last clause is the one that decides it. Winnings from a bonus-funded stake have to be | ||
| attributable to the grant, and under one fungible balance there is nothing to attribute them to. | ||
|
|
||
| ## Decision | ||
|
|
||
| `promo_grant.bonus_balance` is the bonus balance. Bonus funds never enter `wallet_balance` until | ||
| they convert, and conversion writes one `wallet_transaction` of type `bonus` for the exact amount. | ||
|
|
||
| `wallet_transaction` stays the real-money ledger and still records every bet and every win for | ||
| the full amount. `promo_grant_entry` is a second, append-only ledger recording which part of each | ||
| movement was bonus. Neither is bypassed and neither is a subset of the other. | ||
|
|
||
| ## Consequences | ||
|
|
||
| Withdrawal needs no bonus logic at all. The proportional locked-share subquery inside | ||
| `debitWithdrawableBalance` exists only because the old model mixed the two kinds of money in one | ||
| row; with them separated, `amount >= requested` is a complete guard. Responsible-gambling limits | ||
| are unaffected: they read `wallet_transaction`, which still sees the full stake. | ||
|
|
||
| Reconciliation stays correct. It compares internal balances against on-chain custody, and bonus | ||
| money was never deposited. Holding it in `wallet_balance` would manufacture a permanent | ||
| unexplained surplus on every run. | ||
|
|
||
| Thirty-odd existing reads of `wallet_balance` - reconciliation, the custody sweep, swap, deposit, | ||
| withdrawal, the balance stream, admin reporting - keep their current meaning. The alternative, a | ||
| `kind` discriminator column, would have required a `kind = 'real'` predicate in every one of them, | ||
| and the one that got missed would be a player withdrawing bonus money. | ||
|
|
||
| The cost is that "total balance" is two reads rather than one: the wallet balance plus the | ||
| player's active grants. The client sums them. | ||
|
|
||
| The fungible model is removed rather than kept alongside. Two mechanisms implementing one product | ||
| rule means two answers to "what is locked" and a debit path with two bonus branches, which is a | ||
| bug waiting for whoever consolidates them. Chat gifts and rain move onto grants, which changes | ||
| their behaviour: that money now has to be wagered before it converts, instead of being spendable | ||
| immediately with a share locked at withdrawal. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.