feat: parse bolt11 locally with network fallback
This commit is contained in:
@@ -2,6 +2,7 @@ package com.bitcointxoko.gudariwallet.data
|
||||
|
||||
import com.bitcointxoko.gudariwallet.api.LnurlScanResponse
|
||||
import com.bitcointxoko.gudariwallet.api.RouteHintHop
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* Domain model classes for the wallet layer.
|
||||
@@ -24,7 +25,11 @@ data class DecodedBolt11(
|
||||
val amountSats : Long,
|
||||
val memo : String,
|
||||
val payee : String?,
|
||||
val routeHints : List<RouteHintHop> = emptyList()
|
||||
val routeHints : List<RouteHintHop> = emptyList(),
|
||||
val paymentHash : String, // hex; unique receipt ID (required field)
|
||||
val createdAt : Instant, // invoice creation timestamp
|
||||
val expiresAt : Instant, // createdAt + expiry (default +1h); show countdown
|
||||
val fallbackAddress : String? = null // on-chain fallback address, if present
|
||||
)
|
||||
|
||||
data class ScannedLnurl(
|
||||
|
||||
@@ -4,9 +4,11 @@ import android.net.Uri
|
||||
import android.util.Log
|
||||
import com.bitcointxoko.gudariwallet.api.*
|
||||
import com.bitcointxoko.gudariwallet.security.SecretStore
|
||||
import com.bitcointxoko.gudariwallet.util.Bolt11Decoder
|
||||
import com.bitcointxoko.gudariwallet.util.LnurlBech32
|
||||
import com.bitcointxoko.gudariwallet.util.LnurlMetadataParser
|
||||
import com.bitcointxoko.gudariwallet.util.WalletConstants
|
||||
import java.time.Instant
|
||||
|
||||
private const val TAG = "LNbitsWalletRepo"
|
||||
|
||||
@@ -47,22 +49,51 @@ class LNbitsWalletRepository(
|
||||
// ── Send ──────────────────────────────────────────────────────────────────
|
||||
|
||||
override suspend fun decodeBolt11(bolt11: String): DecodedBolt11 {
|
||||
// ── 1. Try local decode first (no network, no latency) ───────────────
|
||||
val local = runCatching { Bolt11Decoder.decode(bolt11) }.getOrNull()
|
||||
if (local != null) {
|
||||
Log.i("WalletRepository", "decodeBolt11: decoded locally ✓")
|
||||
return local
|
||||
}
|
||||
|
||||
// ── 2. Fall back to network API ───────────────────────────────────────
|
||||
Log.w("WalletRepository", "decodeBolt11: local decode failed, falling back to network")
|
||||
val response = api.decodeInvoice(secrets.invoiceKey(), DecodeInvoiceRequest(data = bolt11))
|
||||
Log.i("WalletRepository", "decodeBolt11: decoded via network ✓")
|
||||
|
||||
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."
|
||||
)
|
||||
|
||||
val paymentHash = response.paymentHash
|
||||
?: throw UnsupportedOperationException(
|
||||
"Invoice is missing a payment hash and cannot be paid."
|
||||
)
|
||||
|
||||
// createdAt / expiresAt / fallbackAddress are not returned by the API —
|
||||
// use safe defaults and warn so we know if this path is hit in practice.
|
||||
Log.w("WalletRepository", "decodeBolt11: network response has no timestamp/expiry — " +
|
||||
"using Instant.now() + 1h as fallback")
|
||||
val createdAt = Instant.now()
|
||||
val expiresAt = createdAt.plusSeconds(3600L)
|
||||
|
||||
return DecodedBolt11(
|
||||
amountSats = amountSats,
|
||||
memo = response.description ?: "",
|
||||
payee = response.payee?.takeIf { it.isNotBlank() },
|
||||
routeHints = response.routeHints?.flatten() ?: emptyList()
|
||||
amountSats = amountSats,
|
||||
memo = response.description ?: "",
|
||||
payee = response.payee?.takeIf { it.isNotBlank() },
|
||||
routeHints = response.routeHints?.flatten() ?: emptyList(),
|
||||
paymentHash = paymentHash,
|
||||
createdAt = createdAt,
|
||||
expiresAt = expiresAt,
|
||||
fallbackAddress = null
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
override suspend fun scanLnurl(code: String): ScannedLnurl {
|
||||
val r = api.lnurlScan(secrets.invoiceKey(), code)
|
||||
return ScannedLnurl(
|
||||
|
||||
Reference in New Issue
Block a user