rename example
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
package com.bitcointxoko.gudariwallet.data
|
||||
|
||||
import com.bitcointxoko.gudariwallet.api.LnurlScanResponse
|
||||
import com.bitcointxoko.gudariwallet.api.RouteHintHop
|
||||
|
||||
/**
|
||||
* Domain model classes for the wallet layer.
|
||||
*
|
||||
* These are the types returned by [WalletRepository] methods — deliberately
|
||||
* decoupled from the raw API response models in [com.bitcointxoko.gudariwallet.api].
|
||||
*/
|
||||
|
||||
data class WalletBalance(
|
||||
val sats: Long
|
||||
)
|
||||
|
||||
data class CreatedInvoice(
|
||||
val bolt11 : String,
|
||||
val paymentHash : String,
|
||||
val expiry : String?
|
||||
)
|
||||
|
||||
data class DecodedBolt11(
|
||||
val amountSats : Long,
|
||||
val memo : String,
|
||||
val payee : String?,
|
||||
val routeHints : List<RouteHintHop> = emptyList()
|
||||
)
|
||||
|
||||
data class ScannedLnurl(
|
||||
val tag : String?,
|
||||
val callback : String?,
|
||||
val minSats : Long,
|
||||
val maxSats : Long,
|
||||
val description : String,
|
||||
val domain : String,
|
||||
val commentAllowed : Int,
|
||||
val rawRes : LnurlScanResponse
|
||||
)
|
||||
|
||||
data class PaymentConfirmation(
|
||||
val paymentHash: String
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.bitcointxoko.gudariwallet.data
|
||||
|
||||
import com.bitcointxoko.gudariwallet.service.NodeAliasService
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
* [NodeAliasRepository] backed by [NodeAliasService], which queries
|
||||
* mempool.space then 1ml.com and caches results in SharedPreferences.
|
||||
*/
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
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
|
||||
import com.bitcointxoko.gudariwallet.util.WalletConstants
|
||||
|
||||
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 ───────────────────────────────────────────────────────────────
|
||||
|
||||
override suspend fun getBalance(): WalletBalance {
|
||||
val response = api.getWallet(secrets.invoiceKey)
|
||||
return WalletBalance(sats = response.balance / WalletConstants.MSAT_PER_SAT)
|
||||
}
|
||||
|
||||
// ── Receive ───────────────────────────────────────────────────────────────
|
||||
|
||||
override suspend fun createInvoice(amountSats: Long, memo: String): CreatedInvoice {
|
||||
val response = api.createInvoice(
|
||||
secrets.invoiceKey,
|
||||
CreateInvoiceRequest(
|
||||
out = false,
|
||||
amount = amountSats,
|
||||
memo = memo.ifBlank { "Gudari Wallet" }
|
||||
)
|
||||
)
|
||||
return CreatedInvoice(
|
||||
bolt11 = response.paymentRequest,
|
||||
paymentHash = response.paymentHash,
|
||||
expiry = response.expiry
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun isPaymentReceived(paymentHash: String): Boolean {
|
||||
return api.checkPayment(secrets.invoiceKey, paymentHash).paid
|
||||
}
|
||||
|
||||
// ── Send ──────────────────────────────────────────────────────────────────
|
||||
|
||||
override suspend fun decodeBolt11(bolt11: String): DecodedBolt11 {
|
||||
val response = api.decodeInvoice(secrets.invoiceKey, DecodeInvoiceRequest(data = bolt11))
|
||||
val amountSats = response.amountMsat
|
||||
?.takeIf { it > 0L }
|
||||
?.div(WalletConstants.MSAT_PER_SAT)
|
||||
?: throw UnsupportedOperationException(
|
||||
"Zero-amount invoices are not supported. Please ask the sender to specify an amount."
|
||||
)
|
||||
return DecodedBolt11(
|
||||
amountSats = amountSats,
|
||||
memo = response.description ?: "",
|
||||
payee = response.payee?.takeIf { it.isNotBlank() },
|
||||
routeHints = response.routeHints?.flatten() ?: emptyList()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
override suspend fun scanLnurl(code: String): ScannedLnurl {
|
||||
val r = api.lnurlScan(secrets.invoiceKey, code)
|
||||
return ScannedLnurl(
|
||||
tag = r.tag,
|
||||
callback = r.callback,
|
||||
minSats = (r.minSendable ?: 0L) / WalletConstants.MSAT_PER_SAT,
|
||||
maxSats = (r.maxSendable ?: 0L) / WalletConstants.MSAT_PER_SAT,
|
||||
description = LnurlMetadataParser.parseDescription(r.metadata),
|
||||
domain = LnurlMetadataParser.extractDomain(r.callback),
|
||||
commentAllowed = r.commentAllowed ?: 0,
|
||||
rawRes = r
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun fetchLnurlDirect(httpsUrl: String): LnurlScanResponse {
|
||||
return api.fetchLnurlMetadata(httpsUrl)
|
||||
}
|
||||
|
||||
/**
|
||||
* Client-side LNURL/Lightning Address resolution.
|
||||
* - Lightning Address (user@domain) → GET https://domain/.well-known/lnurlp/user
|
||||
* - Bech32 LNURL → decode → GET decoded https URL
|
||||
* Throws on any failure so the caller can fall back to the server proxy.
|
||||
*/
|
||||
override suspend fun scanLnurlDirect(code: String): ScannedLnurl {
|
||||
val httpsUrl = when {
|
||||
// Lightning Address: user@domain.com
|
||||
'@' in code -> {
|
||||
val parts = code.trim().split("@")
|
||||
require(parts.size == 2) { "Invalid Lightning Address: $code" }
|
||||
val (user, domain) = parts
|
||||
"https://$domain/.well-known/lnurlp/$user"
|
||||
}
|
||||
// Bech32 LNURL: decode to https URL
|
||||
else -> {
|
||||
LnurlBech32.decode(code)
|
||||
?: throw IllegalArgumentException("Could not decode LNURL: $code")
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "LNURL [DIRECT SCAN] $code → $httpsUrl")
|
||||
val r = api.fetchLnurlMetadata(httpsUrl)
|
||||
|
||||
return ScannedLnurl(
|
||||
tag = r.tag,
|
||||
callback = r.callback,
|
||||
minSats = (r.minSendable ?: 0L) / WalletConstants.MSAT_PER_SAT,
|
||||
maxSats = (r.maxSendable ?: 0L) / WalletConstants.MSAT_PER_SAT,
|
||||
description = LnurlMetadataParser.parseDescription(r.metadata),
|
||||
domain = LnurlMetadataParser.extractDomain(r.callback),
|
||||
commentAllowed = r.commentAllowed ?: 0,
|
||||
rawRes = r
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Client-side LNURL-pay: fetches the invoice from the callback URL directly,
|
||||
* then pays it via the standard bolt11 path.
|
||||
* Throws on any failure so the caller can fall back to the server proxy.
|
||||
*/
|
||||
override suspend fun payLnurlDirect(
|
||||
rawRes : LnurlScanResponse,
|
||||
amountMsat: Long,
|
||||
comment : String?
|
||||
): PaymentConfirmation {
|
||||
val bolt11 = fetchLnurlCallbackInvoice(rawRes, amountMsat, comment)
|
||||
Log.d(TAG, "LNURL [DIRECT PAY ] got invoice, paying bolt11")
|
||||
return payBolt11(bolt11)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetches the bolt11 invoice from the LNURL callback URL without paying it.
|
||||
* Reuses the same URL-building and error-handling logic as [payLnurlDirect],
|
||||
* but returns the raw `pr` string instead of proceeding to payment.
|
||||
*/
|
||||
override suspend fun fetchLnurlCallbackInvoice(
|
||||
rawRes : LnurlScanResponse,
|
||||
amountMsat: Long,
|
||||
comment : String?
|
||||
): String {
|
||||
val callback = requireNotNull(rawRes.callback) { "LNURL callback is null" }
|
||||
val callbackUrl = buildString {
|
||||
append(callback)
|
||||
append(if ('?' in callback) '&' else '?')
|
||||
append("amount=")
|
||||
append(amountMsat)
|
||||
if (!comment.isNullOrBlank()) {
|
||||
append("&comment=")
|
||||
append(android.net.Uri.encode(comment))
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "LNURL [FETCH INVOICE] callback=$callbackUrl")
|
||||
val callbackResponse = api.fetchLnurlCallback(callbackUrl)
|
||||
if (callbackResponse.status == "ERROR") {
|
||||
throw RuntimeException(
|
||||
callbackResponse.reason ?: "LNURL callback returned an error"
|
||||
)
|
||||
}
|
||||
return callbackResponse.pr.ifBlank {
|
||||
throw RuntimeException("LNURL callback returned an empty invoice")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun payBolt11(bolt11: String): PaymentConfirmation {
|
||||
val response = api.payInvoice(
|
||||
secrets.adminKey,
|
||||
PayInvoiceRequest(out = true, bolt11 = bolt11)
|
||||
)
|
||||
return PaymentConfirmation(paymentHash = response.paymentHash)
|
||||
}
|
||||
|
||||
override suspend fun payLnurl(
|
||||
rawRes: LnurlScanResponse,
|
||||
lnurl: String,
|
||||
amountMsat: Long,
|
||||
comment: String?
|
||||
): PaymentConfirmation {
|
||||
val response = api.payLnurl(
|
||||
secrets.adminKey,
|
||||
PayLnurlRequest(
|
||||
res = LnurlPayRes(
|
||||
tag = rawRes.tag,
|
||||
callback = rawRes.callback,
|
||||
minSendable = rawRes.minSendable,
|
||||
maxSendable = rawRes.maxSendable,
|
||||
metadata = rawRes.metadata,
|
||||
commentAllowed = rawRes.commentAllowed,
|
||||
allowsNostr = rawRes.allowsNostr,
|
||||
nostrPubkey = rawRes.nostrPubkey
|
||||
),
|
||||
lnurl = lnurl,
|
||||
amount = amountMsat,
|
||||
comment = comment
|
||||
)
|
||||
)
|
||||
return PaymentConfirmation(paymentHash = response.paymentHash)
|
||||
}
|
||||
|
||||
override suspend fun executeWithdraw(
|
||||
callback: String,
|
||||
k1 : String,
|
||||
bolt11 : String
|
||||
): WithdrawCallbackResponse {
|
||||
val url = buildString {
|
||||
append(callback)
|
||||
append(if ('?' in callback) '&' else '?')
|
||||
append("k1=")
|
||||
append(Uri.encode(k1))
|
||||
append("&pr=")
|
||||
append(Uri.encode(bolt11))
|
||||
}
|
||||
Log.d(TAG, "LNURL-WITHDRAW [CALLBACK] $url")
|
||||
return api.withdrawCallback(url)
|
||||
}
|
||||
|
||||
override suspend fun getLightningAddress(): String? {
|
||||
val links = api.getLnurlpLinks(secrets.invoiceKey, allWallets = false)
|
||||
val username = links.firstOrNull { !it.username.isNullOrBlank() }?.username
|
||||
?: return null
|
||||
val domain = secrets.baseUrl
|
||||
.removePrefix("https://")
|
||||
.removePrefix("http://")
|
||||
.trimEnd('/')
|
||||
.substringBefore("/")
|
||||
return "$username@$domain"
|
||||
}
|
||||
|
||||
// ── Fiat ──────────────────────────────────────────────────────────────────
|
||||
|
||||
override suspend fun getFiatRate(currency: String): Double {
|
||||
Log.d(TAG, "FIAT [RATE FETCH ] currency=$currency")
|
||||
return api.getFiatRate(currency).rate
|
||||
}
|
||||
|
||||
override suspend fun getSupportedCurrencies(): List<String> {
|
||||
Log.d(TAG, "FIAT [CURRENCIES] fetching supported currency list")
|
||||
return api.getSupportedCurrencies()
|
||||
}
|
||||
|
||||
// ── History ───────────────────────────────────────────────────────────────
|
||||
|
||||
override suspend fun getPayments(offset: Int, limit: Int): List<PaymentRecord> {
|
||||
return api.getPayments(secrets.invoiceKey, limit, offset)
|
||||
}
|
||||
|
||||
override suspend fun getPaymentDetail(checkingId: String): PaymentDetailResponse {
|
||||
return api.getPaymentDetail(secrets.invoiceKey, checkingId)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.bitcointxoko.gudariwallet.data
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import com.bitcointxoko.gudariwallet.api.LnurlScanResponse
|
||||
import com.bitcointxoko.gudariwallet.util.WalletConstants
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
|
||||
private const val TAG = "LnurlCacheRepository"
|
||||
|
||||
data class LnurlCacheEntry(
|
||||
val key : String,
|
||||
val response : LnurlScanResponse,
|
||||
val savedAt : Long
|
||||
)
|
||||
|
||||
class LnurlCacheRepository(context: Context) {
|
||||
|
||||
private val prefs: SharedPreferences = context.getSharedPreferences(
|
||||
WalletConstants.LNURL_CACHE_PREFS_NAME, Context.MODE_PRIVATE
|
||||
)
|
||||
private val gson = Gson()
|
||||
|
||||
// In-memory map for fast access within a session
|
||||
private val memCache = mutableMapOf<String, LnurlCacheEntry>()
|
||||
|
||||
init { loadFromDisk() }
|
||||
|
||||
/**
|
||||
* Returns a cached [LnurlScanResponse] for [key], or null if:
|
||||
* - no entry exists
|
||||
* - the entry is older than [WalletConstants.LNURL_CACHE_TTL_MS]
|
||||
* - the server marked the endpoint as disposable (null is treated as true per LUD-06)
|
||||
*/
|
||||
fun get(key: String): LnurlScanResponse? {
|
||||
val entry = memCache[key] ?: return null
|
||||
val ageMs = System.currentTimeMillis() - entry.savedAt
|
||||
|
||||
if (ageMs > WalletConstants.LNURL_CACHE_TTL_MS) {
|
||||
Log.d(TAG, "LNURL [CACHE STALE] $key (age ${ageMs / 1000}s)")
|
||||
memCache.remove(key)
|
||||
persistToDisk()
|
||||
return null
|
||||
}
|
||||
|
||||
// LUD-06: disposable == null means treat as true — do not serve from cache
|
||||
val disposable = entry.response.disposable
|
||||
if (disposable == null || disposable == true) {
|
||||
Log.d(TAG, "LNURL [DISPOSABLE ] $key — not serving from cache (disposable=$disposable)")
|
||||
return null
|
||||
}
|
||||
|
||||
Log.d(TAG, "LNURL [CACHE HIT ] $key (age ${ageMs / 1000}s)")
|
||||
return entry.response
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores [response] under [key].
|
||||
* Silently skips if the response is marked disposable (or disposable is null).
|
||||
*/
|
||||
fun put(key: String, response: LnurlScanResponse) {
|
||||
val disposable = response.disposable
|
||||
if (disposable == null || disposable == true) {
|
||||
Log.d(TAG, "LNURL [SKIP CACHE ] $key — disposable=$disposable, not caching")
|
||||
return
|
||||
}
|
||||
val entry = LnurlCacheEntry(key, response, System.currentTimeMillis())
|
||||
memCache[key] = entry
|
||||
persistToDisk()
|
||||
Log.d(TAG, "LNURL [CACHE SET ] $key")
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the entry for [key] from both memory and disk.
|
||||
* Called when a payment attempt with cached data fails, so the next
|
||||
* scan will always fetch fresh metadata from the server.
|
||||
*/
|
||||
fun invalidate(key: String) {
|
||||
if (memCache.remove(key) != null) {
|
||||
persistToDisk()
|
||||
Log.d(TAG, "LNURL [INVALIDATED] $key")
|
||||
}
|
||||
}
|
||||
|
||||
fun clearAll() {
|
||||
memCache.clear()
|
||||
prefs.edit().clear().apply()
|
||||
Log.d(TAG, "LNURL [CLEARED ] all entries")
|
||||
}
|
||||
|
||||
// ── Persistence ───────────────────────────────────────────────────────────
|
||||
|
||||
private fun loadFromDisk() {
|
||||
val json = prefs.getString(WalletConstants.LNURL_CACHE_PREFS_KEY, null) ?: return
|
||||
runCatching {
|
||||
val type = object : TypeToken<List<LnurlCacheEntry>>() {}.type
|
||||
val list: List<LnurlCacheEntry> = gson.fromJson(json, type)
|
||||
val now = System.currentTimeMillis()
|
||||
var loaded = 0
|
||||
list.forEach { entry ->
|
||||
if (now - entry.savedAt < WalletConstants.LNURL_CACHE_TTL_MS) {
|
||||
memCache[entry.key] = entry
|
||||
loaded++
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "LNURL [LOADED ] $loaded valid entries from disk (${list.size - loaded} expired)")
|
||||
}.onFailure {
|
||||
Log.w(TAG, "LNURL [LOAD ERR ] ${it.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun persistToDisk() {
|
||||
runCatching {
|
||||
prefs.edit()
|
||||
.putString(WalletConstants.LNURL_CACHE_PREFS_KEY, gson.toJson(memCache.values.toList()))
|
||||
.apply()
|
||||
}.onFailure {
|
||||
Log.w(TAG, "LNURL [PERSIST ERR] ${it.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.bitcointxoko.gudariwallet.data
|
||||
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
* Alias resolution concern, separated from the payment/wallet domain.
|
||||
*
|
||||
* Implementations are expected to cache results (in-memory and/or on disk)
|
||||
* and expose the full resolved alias map as a [StateFlow] for observers.
|
||||
*/
|
||||
interface NodeAliasRepository {
|
||||
/** Live map of pubkey → resolved alias for all pubkeys resolved so far. */
|
||||
val aliases: StateFlow<Map<String, String>>
|
||||
|
||||
/**
|
||||
* Resolves the human-readable alias for [pubkey].
|
||||
* Returns the alias string, or null if resolution fails.
|
||||
* Results are cached — repeated calls for the same pubkey are cheap.
|
||||
*/
|
||||
suspend fun resolve(pubkey: String): String?
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.bitcointxoko.gudariwallet.data
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import com.bitcointxoko.gudariwallet.api.GsonProvider
|
||||
import com.bitcointxoko.gudariwallet.api.PaymentDetailResponse
|
||||
import com.bitcointxoko.gudariwallet.api.PaymentRecord
|
||||
import com.bitcointxoko.gudariwallet.util.WalletConstants
|
||||
import com.bitcointxoko.gudariwallet.api.SuccessAction
|
||||
import com.bitcointxoko.gudariwallet.api.SuccessActionAdapter
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
|
||||
private const val TAG = "PaymentCacheRepository"
|
||||
|
||||
class PaymentCacheRepository(context: Context) {
|
||||
|
||||
private val prefs: SharedPreferences = context.getSharedPreferences(
|
||||
WalletConstants.PAYMENTS_PREFS_NAME, Context.MODE_PRIVATE
|
||||
)
|
||||
private val gson = GsonProvider.gson
|
||||
// ── In-memory detail cache — immutable once confirmed, no TTL needed ──────
|
||||
private val detailCache = mutableMapOf<String, PaymentDetailResponse>()
|
||||
|
||||
// ── Payment list ──────────────────────────────────────────────────────────
|
||||
|
||||
/** Load the persisted first-page list. Returns empty list if nothing cached or cache is stale. */
|
||||
fun loadCachedPayments(): List<PaymentRecord> {
|
||||
val json = prefs.getString(WalletConstants.PAYMENTS_PREFS_KEY, null) ?: return emptyList()
|
||||
val savedAt = prefs.getLong(WalletConstants.PAYMENTS_PREFS_SAVED_AT, 0L)
|
||||
val ageMs = System.currentTimeMillis() - savedAt
|
||||
|
||||
if (ageMs > WalletConstants.PAYMENTS_CACHE_TTL_MS) {
|
||||
Log.d(TAG, "PAYMENTS [CACHE STALE] age ${ageMs / 1000}s — ignoring persisted list")
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return runCatching {
|
||||
val type: java.lang.reflect.Type = object : TypeToken<List<PaymentRecord>>() {}.type
|
||||
val list: List<PaymentRecord> = gson.fromJson(json, type)
|
||||
Log.d(TAG, "PAYMENTS [CACHE HIT ] loaded ${list.size} payments (age ${ageMs / 1000}s)")
|
||||
list
|
||||
}.getOrElse {
|
||||
Log.w(TAG, "PAYMENTS [CACHE ERR ] failed to deserialise: ${it.message}")
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
/** Persist the first page of payments to SharedPreferences. */
|
||||
fun savePayments(payments: List<PaymentRecord>) {
|
||||
runCatching {
|
||||
prefs.edit()
|
||||
.putString(WalletConstants.PAYMENTS_PREFS_KEY, gson.toJson(payments))
|
||||
.putLong(WalletConstants.PAYMENTS_PREFS_SAVED_AT, System.currentTimeMillis())
|
||||
.apply()
|
||||
Log.d(TAG, "PAYMENTS [PERSIST ] saved ${payments.size} payments")
|
||||
}.onFailure {
|
||||
Log.w(TAG, "PAYMENTS [PERSIST ] write failed: ${it.message}")
|
||||
}
|
||||
}
|
||||
|
||||
// ── Payment detail ────────────────────────────────────────────────────────
|
||||
|
||||
/** Returns a cached detail response, or null if not yet fetched this session. */
|
||||
fun getCachedDetail(checkingId: String): PaymentDetailResponse? {
|
||||
return detailCache[checkingId]?.also {
|
||||
Log.d(TAG, "PAYMENTS [DETAIL HIT] $checkingId")
|
||||
}
|
||||
}
|
||||
|
||||
/** Store a detail response in the in-memory cache. */
|
||||
fun saveDetail(checkingId: String, detail: PaymentDetailResponse) {
|
||||
detailCache[checkingId] = detail
|
||||
Log.d(TAG, "PAYMENTS [DETAIL SET] $checkingId")
|
||||
}
|
||||
|
||||
/** Clear everything — useful for testing and logout. */
|
||||
fun clearAll() {
|
||||
detailCache.clear()
|
||||
prefs.edit()
|
||||
.remove(WalletConstants.PAYMENTS_PREFS_KEY)
|
||||
.remove(WalletConstants.PAYMENTS_PREFS_SAVED_AT)
|
||||
.apply()
|
||||
Log.d(TAG, "PAYMENTS [CLEARED ] cache wiped")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.bitcointxoko.gudariwallet.data
|
||||
|
||||
import com.bitcointxoko.gudariwallet.api.LnurlScanResponse
|
||||
import com.bitcointxoko.gudariwallet.api.PaymentDetailResponse
|
||||
import com.bitcointxoko.gudariwallet.api.PaymentRecord
|
||||
import com.bitcointxoko.gudariwallet.api.WithdrawCallbackResponse
|
||||
|
||||
/**
|
||||
* Contract for all wallet operations.
|
||||
* Domain model types are defined in [DomainModels.kt].
|
||||
*/
|
||||
interface WalletRepository {
|
||||
suspend fun getBalance(): WalletBalance
|
||||
suspend fun createInvoice(amountSats: Long, memo: String): CreatedInvoice
|
||||
suspend fun isPaymentReceived(paymentHash: String): Boolean
|
||||
suspend fun decodeBolt11(bolt11: String): DecodedBolt11
|
||||
suspend fun scanLnurl(code: String): ScannedLnurl
|
||||
suspend fun fetchLnurlDirect(httpsUrl: String): LnurlScanResponse
|
||||
suspend fun scanLnurlDirect(code: String): ScannedLnurl
|
||||
suspend fun payLnurlDirect(
|
||||
rawRes : LnurlScanResponse,
|
||||
amountMsat: Long,
|
||||
comment : String?
|
||||
): PaymentConfirmation
|
||||
suspend fun fetchLnurlCallbackInvoice(
|
||||
rawRes: LnurlScanResponse,
|
||||
amountMsat: Long,
|
||||
comment: String?
|
||||
): String
|
||||
suspend fun payBolt11(bolt11: String): PaymentConfirmation
|
||||
suspend fun payLnurl(rawRes: LnurlScanResponse, lnurl: String, amountMsat: Long, comment: String?): PaymentConfirmation
|
||||
suspend fun executeWithdraw(
|
||||
callback: String,
|
||||
k1 : String,
|
||||
bolt11 : String
|
||||
): WithdrawCallbackResponse
|
||||
suspend fun getLightningAddress(): String?
|
||||
suspend fun getPayments(offset: Int, limit: Int): List<PaymentRecord>
|
||||
suspend fun getPaymentDetail(checkingId: String): PaymentDetailResponse
|
||||
suspend fun getFiatRate(currency: String): Double
|
||||
suspend fun getSupportedCurrencies(): List<String>
|
||||
}
|
||||
Reference in New Issue
Block a user