feat: migrate nodeAliasCache to room
This commit is contained in:
+6
-6
@@ -7,10 +7,10 @@ import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Resolves a Lightning node pubkey to a human-readable alias.
|
||||
* Tries 1ml.com first, falls back to mempool.space.
|
||||
* Tries mempool.space first, falls back to 1ml.com.
|
||||
* Returns null if neither source has the node or both fail.
|
||||
*/
|
||||
class NodeAliasService {
|
||||
class NodeAliasClient {
|
||||
|
||||
private val client = OkHttpClient.Builder()
|
||||
.connectTimeout(5, TimeUnit.SECONDS)
|
||||
@@ -34,15 +34,15 @@ class NodeAliasService {
|
||||
suspend fun resolveAlias(pubkey: String): String? {
|
||||
if (pubkey.isBlank()) return null
|
||||
|
||||
// 1. Try 1ml.com
|
||||
runCatching { oneMlApi.getNode(pubkey) }
|
||||
// 1. Try mempool.space
|
||||
runCatching { mempoolApi.getNode(pubkey) }
|
||||
.onSuccess { response ->
|
||||
val alias = response.alias?.takeIf { it.isNotBlank() }
|
||||
if (alias != null) return alias
|
||||
}
|
||||
|
||||
// 2. Fall back to mempool.space
|
||||
runCatching { mempoolApi.getNode(pubkey) }
|
||||
// 2. Fall back to 1ml.com
|
||||
runCatching { oneMlApi.getNode(pubkey) }
|
||||
.onSuccess { response ->
|
||||
val alias = response.alias?.takeIf { it.isNotBlank() }
|
||||
if (alias != null) return alias
|
||||
@@ -10,10 +10,10 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
class LNbitsNodeAliasRepository(
|
||||
private val service: NodeAliasService
|
||||
) : NodeAliasRepository {
|
||||
|
||||
override val aliases: StateFlow<Map<String, String>>
|
||||
get() = service.aliases
|
||||
|
||||
override suspend fun resolve(pubkey: String): String? =
|
||||
service.resolve(pubkey)
|
||||
override suspend fun warmUp() = service.warmUp()
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package com.bitcointxoko.gudariwallet.data
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import com.bitcointxoko.gudariwallet.api.*
|
||||
import com.bitcointxoko.gudariwallet.api.LNbitsClient.httpClient
|
||||
import com.bitcointxoko.gudariwallet.security.SecretStore
|
||||
import com.bitcointxoko.gudariwallet.util.LnurlBech32
|
||||
import com.bitcointxoko.gudariwallet.util.LnurlMetadataParser
|
||||
@@ -15,9 +13,6 @@ private const val TAG = "LNbitsWalletRepo"
|
||||
class LNbitsWalletRepository(
|
||||
private val api: LNbitsApi,
|
||||
private val secrets: SecretStore,
|
||||
private val context : Context,
|
||||
private val nodeAliasService: com.bitcointxoko.gudariwallet.service.NodeAliasService =
|
||||
com.bitcointxoko.gudariwallet.service.NodeAliasService(httpClient, context),
|
||||
) : WalletRepository {
|
||||
|
||||
// ── Balance ───────────────────────────────────────────────────────────────
|
||||
@@ -157,7 +152,7 @@ class LNbitsWalletRepository(
|
||||
append(amountMsat)
|
||||
if (!comment.isNullOrBlank()) {
|
||||
append("&comment=")
|
||||
append(android.net.Uri.encode(comment))
|
||||
append(Uri.encode(comment))
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "LNURL [FETCH INVOICE] callback=$callbackUrl")
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.bitcointxoko.gudariwallet.data
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.bitcointxoko.gudariwallet.data.db.AppDatabase
|
||||
import com.bitcointxoko.gudariwallet.data.db.NodeAliasCacheEntity
|
||||
import com.bitcointxoko.gudariwallet.util.WalletConstants
|
||||
|
||||
private const val TAG = "NodeAliasCacheRepository"
|
||||
|
||||
class NodeAliasCacheRepository(context: Context) {
|
||||
|
||||
private val dao = AppDatabase.getInstance(context).nodeAliasCacheDao()
|
||||
|
||||
/**
|
||||
* Returns a cached alias for [pubkey] if it exists and is within
|
||||
* [WalletConstants.ALIAS_CACHE_TTL_MS]. Returns null on miss or stale.
|
||||
*/
|
||||
suspend fun get(pubkey: String): String? {
|
||||
val entry = dao.getByPubkey(pubkey) ?: run {
|
||||
Log.d(TAG, "ALIAS [CACHE MISS ] $pubkey")
|
||||
return null
|
||||
}
|
||||
val ageMs = System.currentTimeMillis() - entry.savedAt
|
||||
return if (ageMs < WalletConstants.ALIAS_CACHE_TTL_MS) {
|
||||
Log.d(TAG, "ALIAS [CACHE HIT ] $pubkey → \"${entry.alias}\" (age ${ageMs / 1000}s)")
|
||||
entry.alias
|
||||
} else {
|
||||
Log.d(TAG, "ALIAS [CACHE STALE] $pubkey (age ${ageMs / 1000}s) — will refetch")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/** Upsert an alias into the Room cache. */
|
||||
suspend fun put(pubkey: String, alias: String) {
|
||||
dao.insert(
|
||||
NodeAliasCacheEntity(
|
||||
pubkey = pubkey,
|
||||
alias = alias,
|
||||
savedAt = System.currentTimeMillis()
|
||||
)
|
||||
)
|
||||
Log.d(TAG, "ALIAS [PERSIST ] $pubkey → \"$alias\"")
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all non-stale entries from Room as a plain map.
|
||||
* Used to seed the StateFlow on startup — replaces [loadPersistedCache].
|
||||
*/
|
||||
suspend fun loadAll(): Map<String, String> {
|
||||
val now = System.currentTimeMillis()
|
||||
return dao.getAll()
|
||||
.filter { now - it.savedAt < WalletConstants.ALIAS_PERSIST_TTL_MS }
|
||||
.associate { it.pubkey to it.alias }
|
||||
.also { Log.d(TAG, "ALIAS [PERSIST ] Loaded ${it.size} non-stale entries from Room") }
|
||||
}
|
||||
|
||||
/** Remove a single entry. */
|
||||
suspend fun remove(pubkey: String) {
|
||||
dao.deleteByPubkey(pubkey)
|
||||
}
|
||||
|
||||
/** Wipe the entire alias cache — mirrors [clearPersistedCache]. */
|
||||
suspend fun clearAll() {
|
||||
dao.deleteAll()
|
||||
Log.d(TAG, "ALIAS [PERSIST ] Cache cleared")
|
||||
}
|
||||
}
|
||||
@@ -18,4 +18,5 @@ interface NodeAliasRepository {
|
||||
* Results are cached — repeated calls for the same pubkey are cheap.
|
||||
*/
|
||||
suspend fun resolve(pubkey: String): String?
|
||||
suspend fun warmUp()
|
||||
}
|
||||
|
||||
@@ -9,8 +9,12 @@ import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
@Database(
|
||||
entities = [PaymentRecordEntity::class, LnurlCacheEntity::class],
|
||||
version = 2,
|
||||
entities = [
|
||||
PaymentRecordEntity::class,
|
||||
LnurlCacheEntity::class,
|
||||
NodeAliasCacheEntity::class, // ← added
|
||||
],
|
||||
version = 3, // ← bumped
|
||||
exportSchema = false
|
||||
)
|
||||
@TypeConverters(PaymentConverters::class)
|
||||
@@ -18,6 +22,7 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
abstract fun paymentDao(): PaymentDao
|
||||
abstract fun lnurlCacheDao(): LnurlCacheDao
|
||||
abstract fun nodeAliasCacheDao(): NodeAliasCacheDao // ← added
|
||||
|
||||
companion object {
|
||||
@Volatile private var INSTANCE: AppDatabase? = null
|
||||
@@ -37,6 +42,21 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
}
|
||||
}
|
||||
|
||||
private val MIGRATION_2_3 = object : Migration(2, 3) { // ← added
|
||||
override fun migrate(db: SupportSQLiteDatabase) {
|
||||
db.execSQL(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS `node_alias_cache` (
|
||||
`pubkey` TEXT NOT NULL,
|
||||
`alias` TEXT NOT NULL,
|
||||
`savedAt` INTEGER NOT NULL,
|
||||
PRIMARY KEY(`pubkey`)
|
||||
)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun getInstance(context: Context): AppDatabase =
|
||||
INSTANCE ?: synchronized(this) {
|
||||
INSTANCE ?: Room.databaseBuilder(
|
||||
@@ -44,7 +64,7 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
AppDatabase::class.java,
|
||||
"gudari_wallet.db"
|
||||
)
|
||||
.addMigrations(MIGRATION_1_2)
|
||||
.addMigrations(MIGRATION_1_2, MIGRATION_2_3) // ← added
|
||||
.build()
|
||||
.also { INSTANCE = it }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.bitcointxoko.gudariwallet.data.db
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
|
||||
@Dao
|
||||
interface NodeAliasCacheDao {
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insert(entry: NodeAliasCacheEntity)
|
||||
|
||||
@Query("SELECT * FROM node_alias_cache WHERE pubkey = :pubkey LIMIT 1")
|
||||
suspend fun getByPubkey(pubkey: String): NodeAliasCacheEntity?
|
||||
|
||||
@Query("SELECT * FROM node_alias_cache")
|
||||
suspend fun getAll(): List<NodeAliasCacheEntity>
|
||||
|
||||
@Query("DELETE FROM node_alias_cache WHERE pubkey = :pubkey")
|
||||
suspend fun deleteByPubkey(pubkey: String)
|
||||
|
||||
@Query("DELETE FROM node_alias_cache")
|
||||
suspend fun deleteAll()
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.bitcointxoko.gudariwallet.data.db
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "node_alias_cache")
|
||||
data class NodeAliasCacheEntity(
|
||||
@PrimaryKey
|
||||
val pubkey: String, // node public key
|
||||
val alias: String, // resolved human-readable alias
|
||||
val savedAt: Long // epoch ms — used for TTL checks
|
||||
)
|
||||
@@ -1,184 +1,54 @@
|
||||
package com.bitcointxoko.gudariwallet.service
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import com.bitcointxoko.gudariwallet.util.WalletConstants
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import com.bitcointxoko.gudariwallet.api.NodeAliasClient as NodeAliasApiClient
|
||||
import com.bitcointxoko.gudariwallet.data.NodeAliasCacheRepository
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
private const val TAG = "NodeAliasService"
|
||||
|
||||
class NodeAliasService(
|
||||
private val httpClient : OkHttpClient,
|
||||
private val context : Context
|
||||
private val apiClient: NodeAliasApiClient, // ← injected, not duplicated
|
||||
context: Context
|
||||
) {
|
||||
|
||||
// ── Persistence ───────────────────────────────────────────────────────────
|
||||
|
||||
private val prefs: SharedPreferences = context.getSharedPreferences(
|
||||
WalletConstants.ALIAS_PREFS_NAME, Context.MODE_PRIVATE
|
||||
)
|
||||
private val gson = Gson()
|
||||
|
||||
// ── In-memory alias cache ─────────────────────────────────────────────────
|
||||
|
||||
private data class CachedAlias(val alias: String, val fetchedAt: Long)
|
||||
|
||||
// Type token for Gson deserialisation
|
||||
private val cacheType = object : TypeToken<Map<String, CachedAlias>>() {}.type
|
||||
|
||||
private val cache = ConcurrentHashMap<String, CachedAlias>()
|
||||
|
||||
// ── Shared alias StateFlow — all screens collect this ─────────────────────
|
||||
private val repo = NodeAliasCacheRepository(context)
|
||||
|
||||
private val _aliases = MutableStateFlow<Map<String, String>>(emptyMap())
|
||||
val aliases: StateFlow<Map<String, String>> = _aliases.asStateFlow()
|
||||
|
||||
// ── Init — load persisted cache from SharedPreferences ────────────────────
|
||||
|
||||
init {
|
||||
loadPersistedCache()
|
||||
suspend fun warmUp() {
|
||||
_aliases.value = repo.loadAll()
|
||||
}
|
||||
|
||||
private fun loadPersistedCache() {
|
||||
val json = prefs.getString(WalletConstants.ALIAS_PREFS_KEY, null) ?: run {
|
||||
Log.d(TAG, "ALIAS [PERSIST ] No persisted cache found — starting fresh")
|
||||
return
|
||||
}
|
||||
|
||||
runCatching {
|
||||
val persisted: Map<String, CachedAlias> = gson.fromJson(json, cacheType)
|
||||
val now = System.currentTimeMillis()
|
||||
var loaded = 0
|
||||
var dropped = 0
|
||||
|
||||
persisted.forEach { (pubkey, entry) ->
|
||||
val ageMs = now - entry.fetchedAt
|
||||
if (ageMs < WalletConstants.ALIAS_PERSIST_TTL_MS) {
|
||||
cache[pubkey] = entry
|
||||
loaded++
|
||||
} else {
|
||||
dropped++
|
||||
Log.d(TAG, "ALIAS [PERSIST ] Dropped stale entry for $pubkey (age ${ageMs / 3_600_000}h)")
|
||||
}
|
||||
}
|
||||
|
||||
// Seed the StateFlow with everything we loaded
|
||||
_aliases.value = cache.mapValues { it.value.alias }
|
||||
|
||||
Log.d(TAG, "ALIAS [PERSIST ] Loaded $loaded entries, dropped $dropped stale entries")
|
||||
}.onFailure { e ->
|
||||
Log.w(TAG, "ALIAS [PERSIST ] Failed to deserialise cache — starting fresh: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun persistCache() {
|
||||
runCatching {
|
||||
val json = gson.toJson(cache)
|
||||
prefs.edit().putString(WalletConstants.ALIAS_PREFS_KEY, json).apply()
|
||||
Log.d(TAG, "ALIAS [PERSIST ] Wrote ${cache.size} entries to SharedPreferences")
|
||||
}.onFailure { e ->
|
||||
Log.w(TAG, "ALIAS [PERSIST ] Failed to write cache: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
// ── Public API ────────────────────────────────────────────────────────────
|
||||
|
||||
suspend fun resolve(pubkey: String): String? {
|
||||
val now = System.currentTimeMillis()
|
||||
|
||||
cache[pubkey]?.let { entry ->
|
||||
if (now - entry.fetchedAt < WalletConstants.ALIAS_CACHE_TTL_MS) {
|
||||
Log.d(TAG, "ALIAS [CACHE HIT ] $pubkey → \"${entry.alias}\" (age ${(now - entry.fetchedAt) / 1000}s)")
|
||||
return entry.alias
|
||||
} else {
|
||||
Log.d(TAG, "ALIAS [CACHE STALE] $pubkey (age ${(now - entry.fetchedAt) / 1000}s) — refetching")
|
||||
// 1. Room cache hit
|
||||
repo.get(pubkey)?.let {
|
||||
Log.d(TAG, "ALIAS [CACHE HIT ] $pubkey → \"$it\"")
|
||||
return it
|
||||
}
|
||||
} ?: Log.d(TAG, "ALIAS [CACHE MISS ] $pubkey — fetching from network")
|
||||
|
||||
val alias = fetchFromNetwork(pubkey)
|
||||
|
||||
if (alias == null) {
|
||||
Log.w(TAG, "ALIAS [NOT FOUND ] $pubkey — no alias from mempool or 1ml")
|
||||
// 2. Network via api/NodeAliasService (Retrofit, typed responses)
|
||||
Log.d(TAG, "ALIAS [CACHE MISS] $pubkey — fetching from network")
|
||||
val alias = apiClient.resolveAlias(pubkey) ?: run {
|
||||
Log.w(TAG, "ALIAS [NOT FOUND ] $pubkey")
|
||||
return null
|
||||
}
|
||||
|
||||
// Update in-memory cache
|
||||
cache[pubkey] = CachedAlias(alias = alias, fetchedAt = System.currentTimeMillis())
|
||||
// 3. Persist to Room
|
||||
repo.put(pubkey, alias)
|
||||
|
||||
// Publish to shared StateFlow
|
||||
// 4. Publish to StateFlow
|
||||
_aliases.value = _aliases.value + (pubkey to alias)
|
||||
|
||||
// Persist to SharedPreferences
|
||||
persistCache()
|
||||
|
||||
Log.d(TAG, "ALIAS [API FETCHED] $pubkey → \"$alias\"")
|
||||
Log.d(TAG, "ALIAS [FETCHED ] $pubkey → \"$alias\"")
|
||||
return alias
|
||||
}
|
||||
|
||||
/** Clears in-memory cache, StateFlow, and persisted SharedPreferences entry. */
|
||||
fun clearPersistedCache() {
|
||||
cache.clear()
|
||||
suspend fun clearPersistedCache() {
|
||||
repo.clearAll()
|
||||
_aliases.value = emptyMap()
|
||||
prefs.edit().remove(WalletConstants.ALIAS_PREFS_KEY).apply()
|
||||
Log.d(TAG, "ALIAS [PERSIST ] Cache cleared")
|
||||
}
|
||||
|
||||
// ── Private helpers ───────────────────────────────────────────────────────
|
||||
|
||||
private suspend fun fetchFromNetwork(pubkey: String): String? = withContext(Dispatchers.IO) {
|
||||
Log.d(TAG, "ALIAS [TRYING ] mempool.space for $pubkey")
|
||||
val mempoolResult = fetchFromMempool(pubkey)
|
||||
if (mempoolResult != null) {
|
||||
Log.d(TAG, "ALIAS [SOURCE ] mempool.space resolved $pubkey → \"$mempoolResult\"")
|
||||
return@withContext mempoolResult
|
||||
}
|
||||
Log.d(TAG, "ALIAS [TRYING ] 1ml.com fallback for $pubkey")
|
||||
val mlResult = fetchFrom1ml(pubkey)
|
||||
if (mlResult != null) {
|
||||
Log.d(TAG, "ALIAS [SOURCE ] 1ml.com resolved $pubkey → \"$mlResult\"")
|
||||
}
|
||||
mlResult
|
||||
}
|
||||
|
||||
private fun fetchFromMempool(pubkey: String): String? {
|
||||
return try {
|
||||
val request = Request.Builder()
|
||||
.url("https://mempool.space/api/v1/lightning/nodes/$pubkey")
|
||||
.build()
|
||||
httpClient.newCall(request).execute().use { response ->
|
||||
if (!response.isSuccessful) return null
|
||||
val body = response.body?.string() ?: return null
|
||||
Regex(""""alias"\s*:\s*"([^"]+)"""").find(body)?.groupValues?.get(1)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "mempool.space lookup failed for $pubkey: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchFrom1ml(pubkey: String): String? {
|
||||
return try {
|
||||
val request = Request.Builder()
|
||||
.url("https://1ml.com/node/$pubkey/json")
|
||||
.build()
|
||||
httpClient.newCall(request).execute().use { response ->
|
||||
if (!response.isSuccessful) return null
|
||||
val body = response.body?.string() ?: return null
|
||||
Regex(""""alias"\s*:\s*"([^"]+)"""").find(body)?.groupValues?.get(1)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "1ml.com lookup failed for $pubkey: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,7 @@ class WalletViewModel(
|
||||
init {
|
||||
observePaymentEvents()
|
||||
fetchLightningAddress()
|
||||
viewModelScope.launch { aliasRepo.warmUp() }
|
||||
}
|
||||
|
||||
// ── WebSocket event collection ────────────────────────────────────────────
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.app.Application
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.bitcointxoko.gudariwallet.api.LNbitsClient
|
||||
import com.bitcointxoko.gudariwallet.api.NodeAliasClient
|
||||
import com.bitcointxoko.gudariwallet.data.BalancePrefsStore
|
||||
import com.bitcointxoko.gudariwallet.data.LNbitsNodeAliasRepository
|
||||
import com.bitcointxoko.gudariwallet.data.LNbitsWalletRepository
|
||||
@@ -24,12 +25,12 @@ class WalletViewModelFactory(private val app: Application) : ViewModelProvider.F
|
||||
val secrets = EncryptedSecretStore(app)
|
||||
val baseUrl = secrets.baseUrl.ifBlank { "https://placeholder.invalid" }
|
||||
val api = LNbitsClient.create(baseUrl)
|
||||
val nodeAliasSvc = NodeAliasService(httpClient = LNbitsClient.httpClient, context = app)
|
||||
val nodeAliasSvc = NodeAliasService(apiClient = NodeAliasClient(), context = app)
|
||||
val lnurlCache = LnurlCacheRepository(app)
|
||||
val paymentCache = PaymentCacheRepository(app)
|
||||
|
||||
val aliasRepo = LNbitsNodeAliasRepository(nodeAliasSvc)
|
||||
val repo = LNbitsWalletRepository(api = api, secrets = secrets, context = app)
|
||||
val repo = LNbitsWalletRepository(api = api, secrets = secrets)
|
||||
val balancePrefs = BalancePrefsStore(app)
|
||||
val balanceVm = BalanceViewModel(repo, balancePrefs)
|
||||
val fiatVm = FiatViewModel(repo, balancePrefs, balanceVm)
|
||||
|
||||
@@ -5,10 +5,6 @@ object WalletConstants {
|
||||
// ── Unit conversion ───────────────────────────────────────────────────────
|
||||
const val MSAT_PER_SAT = 1_000L // millisatoshis per satoshi
|
||||
|
||||
// ── Polling ───────────────────────────────────────────────────────────────
|
||||
const val POLL_INTERVAL_MS = 3_000L // delay between payment status checks
|
||||
const val POLL_MAX_ATTEMPTS = 200 // 200 × 3s = 10 minutes max
|
||||
|
||||
// ── WebSocket reconnect ───────────────────────────────────────────────────
|
||||
const val WS_INITIAL_BACKOFF_MS = 1_000L // first retry delay
|
||||
const val WS_MAX_BACKOFF_MS = 16_000L // cap — never wait longer than this
|
||||
@@ -33,9 +29,7 @@ object WalletConstants {
|
||||
const val PAYMENTS_PAGE_SIZE = 20
|
||||
// ── Alias cache ───────────────────────────────────────────────────────────
|
||||
const val ALIAS_CACHE_TTL_MS = 3_600_000L // 1 hour — in-memory stale threshold
|
||||
const val ALIAS_PREFS_NAME = "alias_cache_prefs"
|
||||
const val ALIAS_PREFS_KEY = "alias_cache"
|
||||
const val ALIAS_PERSIST_TTL_MS = 86_400_000L // 24 hours — drop on load if older than this
|
||||
const val ALIAS_PERSIST_TTL_MS = 21 * 24 * 60 * 60 * 1000L // 21 days — drop on load if older than this
|
||||
// ── LNURL metadata cache ──────────────────────────────────────────────────
|
||||
const val LNURL_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000L // 7 days
|
||||
// ── Payments cache ────────────────────────────────────────────────────────
|
||||
@@ -48,5 +42,4 @@ object WalletConstants {
|
||||
const val BALANCE_PREFS_KEY = "last_balance_sats"
|
||||
const val BALANCE_PREFS_SAVED_AT = "last_balance_saved_at"
|
||||
const val BALANCE_CACHE_TTL_MS = 3_600_000L // 1 hour
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user