package com.bitcointxoko.gudariwallet.api import com.google.gson.TypeAdapter import com.google.gson.annotations.SerializedName import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonWriter // ── Wallet ──────────────────────────────────────────────────────────────────── data class WalletResponse( val id: String, val name: String, val balance: Long // millisatoshis ) // ── Create invoice ──────────────────────────────────────────────────────────── data class CreateInvoiceRequest( val out: Boolean, val amount: Long, // satoshis val memo: String ) data class CreateInvoiceResponse( @SerializedName("payment_hash") val paymentHash: String, @SerializedName("payment_request") val paymentRequest: String, @SerializedName("expiry") val expiry : String? ) // ── Pay invoice ─────────────────────────────────────────────────────────────── data class PayInvoiceRequest( val out: Boolean, val bolt11: String ) data class PayInvoiceResponse( @SerializedName("payment_hash") val paymentHash: String, @SerializedName("checking_id") val checkingId: String ) // ── Check payment ───────────────────────────────────────────────────────────── data class PaymentStatusResponse( val paid: Boolean, val details: PaymentDetails? = null ) data class PaymentDetails( val amount: Long?, val fee: Long?, val memo: String? ) // ── Decode BOLT-11 ──────────────────────────────────────────────────────────── data class DecodeInvoiceRequest( val data: String ) data class DecodeInvoiceResponse( @SerializedName("payment_hash") val paymentHash : String? = null, @SerializedName("amount_msat") val amountMsat : Long? = null, @SerializedName("description") val description : String? = null, @SerializedName("payee") val payee : String? = null, @SerializedName("route_hints") val routeHints : List>? = null ) data class RouteHintHop( @SerializedName("public_key") val publicKey : String, @SerializedName("short_channel_id") val shortChannelId : String? = null, @SerializedName("base_fee") val baseFeeMsat : Long = 0, @SerializedName("ppm_fee") val ppmFee : Long = 0, @SerializedName("cltv_expiry_delta") val cltvExpiryDelta : Int = 0 ) // ── LNURL scan ──────────────────────────────────────────────────────────────── data class LnurlScanResponse( val tag: String?, // ── payRequest fields ──────────────────────────────────────────────────── val callback: String? = null, @SerializedName("minSendable") val minSendable: Long? = null, @SerializedName("maxSendable") val maxSendable: Long? = null, val metadata: String? = null, @SerializedName("commentAllowed") val commentAllowed: Int? = null, @SerializedName("allowsNostr") val allowsNostr: Boolean? = null, @SerializedName("nostrPubkey") val nostrPubkey: String? = null, val bolt11: String? = null, @SerializedName("amount_msat") val amountMsat: Long? = null, val disposable: Boolean? = null, // LUD-06: null means treat as true (do not cache) // ── withdrawRequest fields ─────────────────────────────────────────────── val k1 : String? = null, @SerializedName("defaultDescription") val defaultDescription: String? = null, @SerializedName("minWithdrawable") val minWithdrawable : Long? = null, @SerializedName("maxWithdrawable") val maxWithdrawable : Long? = null, @SerializedName("balanceCheck") val balanceCheck : String? = null, @SerializedName("currentBalance") val currentBalance : Long? = null, // ── Error fields ───────────────────────────────────────────────────────── val status : String? = null, // "OK" or "ERROR" val reason : String? = null // error message when status == "ERROR" ) // ── LNURL-pay callback response (LUD-06) ───────────────────────────────────── data class LnurlCallbackResponse( val pr : String, // BOLT-11 invoice to pay val routes : List? = null, @SerializedName("successAction") val successAction : SuccessAction? = null, // Error fields (server may return status=ERROR instead of pr) val status : String? = null, val reason : String? = null ) // ── LNURL pay ───────────────────────────────────────────────────────────────── data class LnurlPayRes( val tag: String?, val callback: String?, @SerializedName("minSendable") val minSendable: Long?, @SerializedName("maxSendable") val maxSendable: Long?, val metadata: String?, @SerializedName("commentAllowed") val commentAllowed: Int?, @SerializedName("allowsNostr") val allowsNostr: Boolean? = null, @SerializedName("nostrPubkey") val nostrPubkey: String? = null ) data class PayLnurlRequest( val res: LnurlPayRes, val lnurl: String, val amount: Long, val comment: String? = null ) data class PayLnurlResponse( @SerializedName("payment_hash") val paymentHash: String, @SerializedName("checking_id") val checkingId: String ) data class WithdrawCallbackResponse( val status : String, val reason : String? = null ) // ── LNURLp pay link (lightning address) ────────────────────────────────────── data class LnurlpLink( val id : String, val wallet : String, val username : String?, // the part before the @ in the lightning address val description : String?, val min : Long, val max : Long ) // ── Payment history ─────────────────────────────────────────────────────────── data class PaymentRecord( @SerializedName("payment_hash") val paymentHash: String, @SerializedName("checking_id") val checkingId: String, @SerializedName("amount") val amountMsat: Long, @SerializedName("fee") val feeMsat: Long, @SerializedName("memo") val memo: String?, @SerializedName("time") val time: String, @SerializedName("status") val status: String, @SerializedName("bolt11") val bolt11: String?, @SerializedName("pending") val pending: Boolean, @SerializedName("preimage") val preimage: String?, @SerializedName("extra") val extra: PaymentExtra?, // Not from the API — populated only when loaded from Room via toDomain() val createdAt: Long? = null, val pubkey: String? = null, val alias: String? = null ) { val amountSat: Long get() = kotlin.math.abs(amountMsat) / 1000L val feeSat: Long get() = kotlin.math.abs(feeMsat) / 1000L val isOutgoing: Boolean get() = amountMsat < 0 } data class PaymentExtra( @SerializedName("tag") val tag: String?, @SerializedName("comment") val comment: String?, @SerializedName("success_action") val successAction: SuccessAction?, @SerializedName("wallet_id") val walletId: String?, @SerializedName("destination") val destination: String?, @SerializedName("expires_at") val expiresAt: String? ) data class SuccessAction( @SerializedName("tag") val tag: String?, @SerializedName("message") val message: String?, @SerializedName("url") val url: String?, @SerializedName("description") val description: String? ) /** * LNbits returns `success_action` as either: * - a JSON object {"tag":"message","message":"Thanks!"} * - a plain string "Thanks!" * - null * * Gson's default deserialiser throws IllegalStateException when it sees a * string where it expects {. This adapter handles all three cases. * * Registered centrally in [GsonProvider] — applies to all deserialization * paths automatically. */ class SuccessActionAdapter : TypeAdapter() { override fun write(out: JsonWriter, value: SuccessAction?) { if (value == null) { out.nullValue(); return } out.beginObject() out.name("tag"); out.value(value.tag) out.name("message"); out.value(value.message) out.name("url"); out.value(value.url) out.name("description"); out.value(value.description) out.endObject() } override fun read(reader: JsonReader): SuccessAction? { return when (reader.peek()) { JsonToken.NULL -> { reader.nextNull(); null } // Plain string — treat as a message-type success action JsonToken.STRING -> { val text = reader.nextString() if (text.isBlank()) null else SuccessAction(tag = "message", message = text, url = null, description = null) } // Normal object JsonToken.BEGIN_OBJECT -> { reader.beginObject() var tag: String? = null var message: String? = null var url: String? = null var description: String? = null while (reader.hasNext()) { when (reader.nextName()) { "tag" -> tag = reader.nextString() "message" -> message = reader.nextString() "url" -> url = reader.nextString() "description" -> description = reader.nextString() else -> reader.skipValue() } } reader.endObject() SuccessAction(tag = tag, message = message, url = url, description = description) } else -> { reader.skipValue(); null } } } } data class PaymentDetailResponse( val paid: Boolean, val details: PaymentRecord? ) // ── WebSocket payment message ───────────────────────────────────────────────── data class WsPaymentMessage( val payment: WsPayment? ) data class WsPayment( @SerializedName("payment_hash") val paymentHash: String, @SerializedName("checking_id") val checkingId: String, @SerializedName("amount") val amount: Long, @SerializedName("fee") val fee: Long, @SerializedName("memo") val memo: String?, @SerializedName("time") val time: String, @SerializedName("status") val status: String, @SerializedName("bolt11") val bolt11: String?, @SerializedName("pending") val pending: Boolean, @SerializedName("preimage") val preimage: String?, @SerializedName("extra") val extra: PaymentExtra? ) // ── Fiat rate ──────────────────────────────────────────────────────────────── data class FiatRateResponse( val rate: Double, // sat to fiat rate val price : Double // BTC price in the requested fiat currency )