From 9ff025116bc04f788a092d8fce9935ee4735655e Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Mon, 24 Aug 2026 16:15:11 -0400 Subject: [PATCH 1/2] chore(protos): update flipcash protobuf definitions Adds UserFlags.username_min_balance, the USDF amount in quarks a user must hold across all currencies before setting a username. Vendored from flipcash2-protobuf-api at HEAD; opencode was already current. --- .../src/main/proto/account/v1/flipcash_account_service.proto | 3 +++ 1 file changed, 3 insertions(+) diff --git a/definitions/flipcash/protos/src/main/proto/account/v1/flipcash_account_service.proto b/definitions/flipcash/protos/src/main/proto/account/v1/flipcash_account_service.proto index f584f5e316..387a03d355 100644 --- a/definitions/flipcash/protos/src/main/proto/account/v1/flipcash_account_service.proto +++ b/definitions/flipcash/protos/src/main/proto/account/v1/flipcash_account_service.proto @@ -169,6 +169,9 @@ message UserFlags { // Tip presets for all currencies repeated TipPresets tip_presets = 15; + + // USDF amount, in quarks, that must be held across all currencies in order to set a username + uint64 username_min_balance = 16; } message TipPresets { From a175b6c4936850f7b41219872c7fd4bd6f506e7e Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Mon, 24 Aug 2026 16:15:19 -0400 Subject: [PATCH 2/2] feat(userflags): surface the username minimum balance flag Maps UserFlags.username_min_balance through to the domain model and the debug flag editor as a read-only entry. It gates the username claim flow added in #1320, which has no UI yet, so nothing reads it so far. The default is Fiat.Zero rather than Fiat.MAX_VALUE. This is a minimum balance to hold, like minimumHolderValue, not a price to pay like newCurrencyPurchaseAmount. An absent flag should leave the gate open and let the server answer with INSUFFICIENT_BALANCE, not hard-block a claim the server would have allowed. CachedFlags also carries the field, since its toDomain() reconstructs the whole UserFlags. It defaults to zero quarks so payloads cached before this change still decode -- UserFlagsCoordinator drops the entire cached flag set when decodeFromString fails, so an undefaulted field would blank the flags on the first launch after upgrade. ReadOnlyEntry holds a Boolean, so the amount goes in readOnlyTextEntries formatted with Fiat.FormattingRule.Truncated, matching WithdrawalFeeAmount. --- .../flipcash/app/userflags/internal/UserFlagsViewModel.kt | 5 +++++ .../kotlin/com/flipcash/app/userflags/ResolvedUserFlags.kt | 2 ++ .../com/flipcash/app/userflags/UserFlagsCoordinator.kt | 3 +++ .../shared/userflags/src/main/res/values/strings.xml | 1 + .../com/flipcash/services/internal/domain/UserFlagsMapper.kt | 1 + .../main/kotlin/com/flipcash/services/models/UserFlags.kt | 3 +++ 6 files changed, 15 insertions(+) diff --git a/apps/flipcash/features/userflags/src/main/kotlin/com/flipcash/app/userflags/internal/UserFlagsViewModel.kt b/apps/flipcash/features/userflags/src/main/kotlin/com/flipcash/app/userflags/internal/UserFlagsViewModel.kt index e3735b5e4c..76e8c21a3a 100644 --- a/apps/flipcash/features/userflags/src/main/kotlin/com/flipcash/app/userflags/internal/UserFlagsViewModel.kt +++ b/apps/flipcash/features/userflags/src/main/kotlin/com/flipcash/app/userflags/internal/UserFlagsViewModel.kt @@ -77,6 +77,11 @@ internal class UserFlagsViewModel @Inject constructor( "High ${format(presets.high)}", ).joinToString(separator = "\n") ), + ReadOnlyTextEntry( + R.string.label_flag_usernameMinBalance, + flags.data.usernameMinBalance.effectiveValue + .formatted(rule = Fiat.FormattingRule.Truncated) + ), ) } diff --git a/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/ResolvedUserFlags.kt b/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/ResolvedUserFlags.kt index e77604447c..50f5f10594 100644 --- a/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/ResolvedUserFlags.kt +++ b/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/ResolvedUserFlags.kt @@ -36,6 +36,7 @@ data class ResolvedUserFlags( val minimumHolderAmountForLeaderboard: ResolvedFlag, val requireCoinbaseEmailVerification: ResolvedFlag, val tipPresets: ResolvedFlag>, + val usernameMinBalance: ResolvedFlag, ) internal fun UserFlags.resolve(overrides: Overrides): ResolvedUserFlags = ResolvedUserFlags( @@ -54,4 +55,5 @@ internal fun UserFlags.resolve(overrides: Overrides): ResolvedUserFlags = Resolv minimumHolderAmountForLeaderboard = ResolvedFlag(minimumHolderValue, overrides.minimumHolderAmountForLeaderboard), requireCoinbaseEmailVerification = ResolvedFlag(requireCoinbaseEmailVerification, overrides.requireCoinbaseEmailVerification), tipPresets = ResolvedFlag(tipPresets, FieldOverride.None), + usernameMinBalance = ResolvedFlag(usernameMinBalance, FieldOverride.None), ) \ No newline at end of file diff --git a/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/UserFlagsCoordinator.kt b/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/UserFlagsCoordinator.kt index 196485507d..805c5f7cd3 100644 --- a/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/UserFlagsCoordinator.kt +++ b/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/UserFlagsCoordinator.kt @@ -181,6 +181,7 @@ private data class CachedFlags( val minimumHolderValue: CachedFiat, val requireCoinbaseEmailVerification: Boolean, val tipPresets: List = emptyList(), + val usernameMinBalance: CachedFiat = CachedFiat(quarks = 0), ) { fun toDomain(): UserFlags = UserFlags( isStaff = isStaff, @@ -200,6 +201,7 @@ private data class CachedFlags( minimumHolderValue = minimumHolderValue.toDomain(), requireCoinbaseEmailVerification = requireCoinbaseEmailVerification, tipPresets = tipPresets.map { it.toDomain() }, + usernameMinBalance = usernameMinBalance.toDomain(), ) companion object { @@ -223,6 +225,7 @@ private data class CachedFlags( minimumHolderValue = CachedFiat.fromDomain(flags.minimumHolderValue), requireCoinbaseEmailVerification = flags.requireCoinbaseEmailVerification, tipPresets = flags.tipPresets.map { CachedTipPresets.fromDomain(it) }, + usernameMinBalance = CachedFiat.fromDomain(flags.usernameMinBalance), ) } } diff --git a/apps/flipcash/shared/userflags/src/main/res/values/strings.xml b/apps/flipcash/shared/userflags/src/main/res/values/strings.xml index d4bf666ce4..d1966630ae 100644 --- a/apps/flipcash/shared/userflags/src/main/res/values/strings.xml +++ b/apps/flipcash/shared/userflags/src/main/res/values/strings.xml @@ -22,4 +22,5 @@ Minimum Holder Amount for Leaderboard Enter amount Tip Presets + Username Minimum Balance \ No newline at end of file diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/domain/UserFlagsMapper.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/domain/UserFlagsMapper.kt index 152ecf9a8a..0156fce551 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/domain/UserFlagsMapper.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/domain/UserFlagsMapper.kt @@ -31,6 +31,7 @@ internal class UserFlagsMapper @Inject constructor(): minimumHolderValue = Fiat(quarks = from.minimumHolderValue), requireCoinbaseEmailVerification = from.requireCoinbaseEmailVerification, tipPresets = from.tipPresetsList.map { it.toDomain() }, + usernameMinBalance = Fiat(quarks = from.usernameMinBalance), ) } } diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/models/UserFlags.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/models/UserFlags.kt index ae0298497d..6b4f46e725 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/models/UserFlags.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/models/UserFlags.kt @@ -21,6 +21,8 @@ data class UserFlags( val minimumHolderValue: Fiat, val requireCoinbaseEmailVerification: Boolean, val tipPresets: List, + // USDF that must be held across all currencies before a username can be set. + val usernameMinBalance: Fiat, ) { companion object { val Default = UserFlags( @@ -39,6 +41,7 @@ data class UserFlags( minimumHolderValue = Fiat.Zero, requireCoinbaseEmailVerification = false, tipPresets = emptyList(), + usernameMinBalance = Fiat.Zero, ) } } \ No newline at end of file