perf(cashu): dispatch token prefixes via DualCase startsWith

Replace startsWith(..., ignoreCase = true) — which case-folds on every
call — with the precomputed DualCase prefixes and the new
String.startsWith(DualCase) helper, so the cashuA/cashuB dispatch only
compares against already-cased strings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013UMNKix4qEfiAPP9s2a4gB
This commit is contained in:
Claude
2026-06-19 17:26:48 +00:00
parent ebdf18139b
commit b8db7ae6b7
3 changed files with 12 additions and 3 deletions
@@ -34,6 +34,7 @@ import com.vitorpamplona.quartz.nip60Cashu.mintApi.MintHttpException
import com.vitorpamplona.quartz.nip60Cashu.token.CashuTokenB64Parser
import com.vitorpamplona.quartz.nip87Ecash.recommendation.MintRecommendationEvent
import com.vitorpamplona.quartz.utils.Log
import com.vitorpamplona.quartz.utils.startsWith
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -838,7 +839,7 @@ class CashuWalletViewModel : ViewModel() {
return
}
if (!trimmed.startsWith("cashuA", ignoreCase = true) && !trimmed.startsWith("cashuB", ignoreCase = true)) {
if (!trimmed.startsWith(CashuTokenB64Parser.CashuAPrefix) && !trimmed.startsWith(CashuTokenB64Parser.CashuBPrefix)) {
_redeemState.value =
CashuRedeemFlowState.Error("Not a Cashu token (must start with cashuA or cashuB)")
return
@@ -21,6 +21,8 @@
package com.vitorpamplona.quartz.nip60Cashu.token
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
import com.vitorpamplona.quartz.utils.DualCase
import com.vitorpamplona.quartz.utils.startsWith
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromByteArray
@@ -45,6 +47,10 @@ object CashuTokenB64Parser {
/** Both "cashuA" and "cashuB" prefixes are 6 chars. */
private const val PREFIX_LENGTH = 6
/** Precomputed dual-case prefixes so dispatch avoids per-call case folding. */
val CashuAPrefix = DualCase("cashuA")
val CashuBPrefix = DualCase("cashuB")
private val json =
Json {
ignoreUnknownKeys = true
@@ -53,8 +59,8 @@ object CashuTokenB64Parser {
fun parse(token: String): List<CashuToken>? =
when {
token.startsWith("cashuA", ignoreCase = true) -> parseCashuA(token)
token.startsWith("cashuB", ignoreCase = true) -> parseCashuB(token)
token.startsWith(CashuAPrefix) -> parseCashuA(token)
token.startsWith(CashuBPrefix) -> parseCashuB(token)
else -> null
}
@@ -109,6 +109,8 @@ fun String.startsWithAny(terms: List<DualCase>): Boolean {
return terms.any { startsWithIgnoreCase(it.lowercase, it.uppercase) }
}
fun String.startsWith(prefix: DualCase): Boolean = startsWithIgnoreCase(prefix.lowercase, prefix.uppercase)
class DualCase(
val lowercase: String,
val uppercase: String,