refactor(wallet): remove on-chain send/receive, keep on-chain tx display
We are not supporting on-chain send/receive in-app. Detection and display of on-chain transactions in history remains so deposits/ withdrawals made from another app on the same Spark seed still show correctly (type, txId, mempool.space link).
This commit is contained in:
@@ -356,15 +356,6 @@ class NwcRepository(private val context: Context, private val relayPool: RelayPo
|
||||
return result.map { (it as Nip47.NwcResponse.MakeInvoiceResult).invoice }
|
||||
}
|
||||
|
||||
override suspend fun getDepositAddress(): Result<String> =
|
||||
Result.failure(UnsupportedOperationException("NWC does not support on-chain receive"))
|
||||
|
||||
override suspend fun prepareOnchainSend(address: String, amountSats: Long): Result<Pair<OnchainFeeQuote, Any>> =
|
||||
Result.failure(UnsupportedOperationException("NWC does not support on-chain send"))
|
||||
|
||||
override suspend fun sendOnchain(prepareData: Any, speed: String): Result<String> =
|
||||
Result.failure(UnsupportedOperationException("NWC does not support on-chain send"))
|
||||
|
||||
suspend fun listNwcTransactions(limit: Int = 50, offset: Int = 0): Result<List<Nip47.Transaction>> {
|
||||
val result = sendRequest(Nip47.NwcRequest.ListTransactions(limit = limit, offset = offset))
|
||||
return result.map { (it as Nip47.NwcResponse.ListTransactionsResult).transactions }
|
||||
|
||||
@@ -10,7 +10,6 @@ import breez_sdk_spark.EventListener
|
||||
import breez_sdk_spark.GetInfoRequest
|
||||
import breez_sdk_spark.ListPaymentsRequest
|
||||
import breez_sdk_spark.Network
|
||||
import breez_sdk_spark.OnchainConfirmationSpeed
|
||||
import breez_sdk_spark.PaymentDetails
|
||||
import breez_sdk_spark.PaymentType
|
||||
import breez_sdk_spark.PrepareSendPaymentRequest
|
||||
@@ -455,57 +454,6 @@ class SparkRepository(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getDepositAddress(): Result<String> = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val instance = sdk ?: return@withContext Result.failure(Exception("Not connected"))
|
||||
val response = instance.receivePayment(ReceivePaymentRequest(ReceivePaymentMethod.BitcoinAddress))
|
||||
Result.success(response.paymentRequest)
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun prepareOnchainSend(address: String, amountSats: Long): Result<Pair<OnchainFeeQuote, Any>> =
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val instance = sdk ?: return@withContext Result.failure(Exception("Not connected"))
|
||||
val prepareReq = PrepareSendPaymentRequest(
|
||||
paymentRequest = address,
|
||||
amount = java.math.BigInteger.valueOf(amountSats)
|
||||
)
|
||||
val prepareResponse = instance.prepareSendPayment(prepareReq)
|
||||
val method = prepareResponse.paymentMethod as? SendPaymentMethod.BitcoinAddress
|
||||
?: return@withContext Result.failure(Exception("Not a Bitcoin address payment"))
|
||||
val feeQuote = method.feeQuote
|
||||
val quote = OnchainFeeQuote(
|
||||
fastFeeSats = (feeQuote.speedFast.userFeeSat + feeQuote.speedFast.l1BroadcastFeeSat).toLong(),
|
||||
mediumFeeSats = (feeQuote.speedMedium.userFeeSat + feeQuote.speedMedium.l1BroadcastFeeSat).toLong(),
|
||||
slowFeeSats = (feeQuote.speedSlow.userFeeSat + feeQuote.speedSlow.l1BroadcastFeeSat).toLong()
|
||||
)
|
||||
Result.success(Pair(quote, prepareResponse as Any))
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sendOnchain(prepareData: Any, speed: String): Result<String> = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val instance = sdk ?: return@withContext Result.failure(Exception("Not connected"))
|
||||
val prepareResponse = prepareData as breez_sdk_spark.PrepareSendPaymentResponse
|
||||
val confirmationSpeed = when (speed) {
|
||||
"FAST" -> OnchainConfirmationSpeed.FAST
|
||||
"SLOW" -> OnchainConfirmationSpeed.SLOW
|
||||
else -> OnchainConfirmationSpeed.MEDIUM
|
||||
}
|
||||
val options = SendPaymentOptions.BitcoinAddress(confirmationSpeed = confirmationSpeed)
|
||||
val sendResponse = instance.sendPayment(SendPaymentRequest(prepareResponse, options))
|
||||
emitStatus("Bitcoin sent")
|
||||
Result.success(sendResponse.payment.id)
|
||||
} catch (e: Exception) {
|
||||
emitStatus("Bitcoin send failed: ${e.message}")
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Sync polling ---
|
||||
|
||||
|
||||
@@ -3,12 +3,6 @@ package com.darkwisp.app.repo
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
data class OnchainFeeQuote(
|
||||
val fastFeeSats: Long,
|
||||
val mediumFeeSats: Long,
|
||||
val slowFeeSats: Long
|
||||
)
|
||||
|
||||
interface WalletProvider {
|
||||
val balance: StateFlow<Long?>
|
||||
val isConnected: StateFlow<Boolean>
|
||||
@@ -27,15 +21,6 @@ interface WalletProvider {
|
||||
suspend fun payInvoice(bolt11: String): Result<String>
|
||||
suspend fun makeInvoice(amountMsats: Long, description: String, expirySecs: Int = 3600): Result<String>
|
||||
suspend fun listTransactions(limit: Int = 50, offset: Int = 0): Result<List<WalletTransaction>>
|
||||
|
||||
/** Spark only — returns an on-chain deposit address. NWC returns failure. */
|
||||
suspend fun getDepositAddress(): Result<String>
|
||||
|
||||
/** Spark only — prepares an on-chain send and returns fee quote + opaque prepare data. */
|
||||
suspend fun prepareOnchainSend(address: String, amountSats: Long): Result<Pair<OnchainFeeQuote, Any>>
|
||||
|
||||
/** Spark only — broadcasts a prepared on-chain send. */
|
||||
suspend fun sendOnchain(prepareData: Any, speed: String = "MEDIUM"): Result<String>
|
||||
}
|
||||
|
||||
data class WalletTransaction(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,7 +30,6 @@ import com.darkwisp.app.repo.NwcRepository
|
||||
import com.darkwisp.app.repo.SparkRepository
|
||||
import com.darkwisp.app.repo.WalletMode
|
||||
import com.darkwisp.app.repo.WalletModeRepository
|
||||
import com.darkwisp.app.repo.OnchainFeeQuote
|
||||
import com.darkwisp.app.repo.WalletProvider
|
||||
import android.util.Log
|
||||
import com.darkwisp.app.repo.WalletTransaction
|
||||
@@ -126,14 +125,6 @@ sealed class WalletPage {
|
||||
data class SendResult(val success: Boolean, val message: String) : WalletPage()
|
||||
object ReceiveAmount : WalletPage()
|
||||
data class ReceiveInvoice(val invoice: String, val amountSats: Long) : WalletPage()
|
||||
data class OnchainSendAmount(val address: String) : WalletPage()
|
||||
data class OnchainSendConfirm(
|
||||
val address: String,
|
||||
val amountSats: Long,
|
||||
val feeQuote: OnchainFeeQuote,
|
||||
val prepareData: Any
|
||||
) : WalletPage()
|
||||
data class OnchainSendResult(val success: Boolean, val paymentId: String?, val message: String) : WalletPage()
|
||||
data class ReceiveSuccess(val amountSats: Long) : WalletPage()
|
||||
object Transactions : WalletPage()
|
||||
object Settings : WalletPage()
|
||||
@@ -205,25 +196,6 @@ class WalletViewModel(
|
||||
private val _receiveAmount = MutableStateFlow("")
|
||||
val receiveAmount: StateFlow<String> = _receiveAmount
|
||||
|
||||
// On-chain deposit address (Spark only)
|
||||
private val _depositAddress = MutableStateFlow<String?>(null)
|
||||
val depositAddress: StateFlow<String?> = _depositAddress
|
||||
|
||||
private val _depositAddressLoading = MutableStateFlow(false)
|
||||
val depositAddressLoading: StateFlow<Boolean> = _depositAddressLoading
|
||||
|
||||
private val _depositAddressError = MutableStateFlow<String?>(null)
|
||||
val depositAddressError: StateFlow<String?> = _depositAddressError
|
||||
|
||||
// On-chain send (Spark only)
|
||||
private val _onchainFeeQuote = MutableStateFlow<OnchainFeeQuote?>(null)
|
||||
val onchainFeeQuote: StateFlow<OnchainFeeQuote?> = _onchainFeeQuote
|
||||
|
||||
private var _onchainPrepareData: Any? = null
|
||||
|
||||
private val _onchainSendLoading = MutableStateFlow(false)
|
||||
val onchainSendLoading: StateFlow<Boolean> = _onchainSendLoading
|
||||
|
||||
// Transactions
|
||||
private val _transactions = MutableStateFlow<List<WalletTransaction>>(emptyList())
|
||||
val transactions: StateFlow<List<WalletTransaction>> = _transactions
|
||||
@@ -346,11 +318,6 @@ class WalletViewModel(
|
||||
// would otherwise stay at the prior wallet's acknowledged value
|
||||
// until the next refreshState() call.
|
||||
_seedBackupAcked.value = false
|
||||
_depositAddress.value = null
|
||||
_depositAddressError.value = null
|
||||
_depositAddressLoading.value = false
|
||||
_onchainFeeQuote.value = null
|
||||
_onchainPrepareData = null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -577,8 +544,6 @@ class WalletViewModel(
|
||||
_deleteConfirmText.value = ""
|
||||
_lightningAddressError.value = null
|
||||
_addressAvailable.value = null
|
||||
_onchainFeeQuote.value = null
|
||||
_onchainPrepareData = null
|
||||
}
|
||||
|
||||
val isOnHome: Boolean get() = pageStack.size <= 1
|
||||
@@ -1301,14 +1266,6 @@ class WalletViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun isBtcAddress(s: String): Boolean {
|
||||
val lower = s.lowercase()
|
||||
if (lower.startsWith("bc1") && s.length >= 14) return true
|
||||
if ((s.startsWith("1") || s.startsWith("3")) && s.length in 26..35 &&
|
||||
s.all { it.isLetterOrDigit() && it != '0' && it != 'O' && it != 'I' && it != 'l' }) return true
|
||||
return false
|
||||
}
|
||||
|
||||
fun processInput(input: String = _sendInput.value) {
|
||||
val trimmed = input.trim()
|
||||
.removePrefix("lightning:").removePrefix("LIGHTNING:")
|
||||
@@ -1333,10 +1290,6 @@ class WalletViewModel(
|
||||
navigateTo(WalletPage.SendAmount(noffer.raw))
|
||||
}
|
||||
}
|
||||
_walletMode.value == WalletMode.SPARK && isBtcAddress(trimmed) -> {
|
||||
clearOnchainQuote()
|
||||
navigateTo(WalletPage.OnchainSendAmount(trimmed))
|
||||
}
|
||||
trimmed.lowercase().startsWith("lnbc") -> {
|
||||
val decoded = Bolt11.decode(trimmed)
|
||||
if (decoded == null) {
|
||||
@@ -1359,10 +1312,7 @@ class WalletViewModel(
|
||||
navigateTo(WalletPage.SendAmount(trimmed))
|
||||
}
|
||||
else -> {
|
||||
_sendError.value = if (_walletMode.value == WalletMode.SPARK)
|
||||
"Enter a lightning address, BOLT11 invoice, CLINK offer, or Bitcoin address"
|
||||
else
|
||||
"Enter a lightning address (user@domain), BOLT11 invoice, or CLINK offer"
|
||||
_sendError.value = "Enter a lightning address (user@domain), BOLT11 invoice, or CLINK offer"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1555,74 +1505,6 @@ class WalletViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
// --- On-chain receive (Spark only) ---
|
||||
|
||||
fun loadDepositAddress(force: Boolean = false) {
|
||||
if (!force && _depositAddress.value != null) return
|
||||
_depositAddressLoading.value = true
|
||||
_depositAddressError.value = null
|
||||
viewModelScope.launch {
|
||||
sparkRepo.getDepositAddress().fold(
|
||||
onSuccess = { address -> _depositAddress.value = address },
|
||||
onFailure = { e -> _depositAddressError.value = e.message ?: "Failed to load address" }
|
||||
)
|
||||
_depositAddressLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// --- On-chain send (Spark only) ---
|
||||
|
||||
fun clearOnchainQuote() {
|
||||
_onchainFeeQuote.value = null
|
||||
_onchainPrepareData = null
|
||||
}
|
||||
|
||||
fun prepareOnchainSend(address: String, amountSats: Long) {
|
||||
clearOnchainQuote()
|
||||
_onchainSendLoading.value = true
|
||||
_sendError.value = null
|
||||
viewModelScope.launch {
|
||||
sparkRepo.prepareOnchainSend(address, amountSats).fold(
|
||||
onSuccess = { (quote, prepareData) ->
|
||||
_onchainFeeQuote.value = quote
|
||||
_onchainPrepareData = prepareData
|
||||
},
|
||||
onFailure = { e -> _sendError.value = e.message ?: "Failed to estimate fee" }
|
||||
)
|
||||
_onchainSendLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
fun continueToOnchainConfirm(address: String, amountSats: Long) {
|
||||
val quote = _onchainFeeQuote.value ?: return
|
||||
val prepareData = _onchainPrepareData ?: return
|
||||
navigateTo(WalletPage.OnchainSendConfirm(address, amountSats, quote, prepareData))
|
||||
}
|
||||
|
||||
fun sendOnchain(prepareData: Any) {
|
||||
if (_isLoading.value) return
|
||||
_isLoading.value = true
|
||||
viewModelScope.launch {
|
||||
sparkRepo.sendOnchain(prepareData).fold(
|
||||
onSuccess = { paymentId ->
|
||||
pageStack.removeAt(pageStack.lastIndex)
|
||||
val resultPage = WalletPage.OnchainSendResult(true, paymentId, "Bitcoin sent!")
|
||||
pageStack.add(resultPage)
|
||||
_currentPage.value = resultPage
|
||||
clearOnchainQuote()
|
||||
refreshBalance()
|
||||
},
|
||||
onFailure = { e ->
|
||||
pageStack.removeAt(pageStack.lastIndex)
|
||||
val resultPage = WalletPage.OnchainSendResult(false, null, e.message ?: "Send failed")
|
||||
pageStack.add(resultPage)
|
||||
_currentPage.value = resultPage
|
||||
}
|
||||
)
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// --- Transactions ---
|
||||
|
||||
fun loadTransactions() {
|
||||
|
||||
@@ -776,9 +776,6 @@
|
||||
<string name="wallet_receive_note_label">NOTE (OPTIONAL)</string>
|
||||
<string name="wallet_receive_note_placeholder">For coffee, etc.</string>
|
||||
<string name="wallet_receive_create_invoice">Create invoice</string>
|
||||
<!-- Updated tab labels for Spark on-chain receive -->
|
||||
<string name="wallet_receive_lightning_tab">Lightning</string>
|
||||
<string name="wallet_receive_bitcoin_tab">Bitcoin</string>
|
||||
<!-- Invoice expiry selector -->
|
||||
<string name="wallet_receive_expires_label">EXPIRES</string>
|
||||
<string name="wallet_receive_expires_1h">1 hour</string>
|
||||
@@ -790,29 +787,7 @@
|
||||
<string name="wallet_copy_address">Copy address</string>
|
||||
<string name="wallet_share_address">Share address</string>
|
||||
<string name="wallet_share">Share</string>
|
||||
<!-- Bitcoin on-chain deposit address -->
|
||||
<string name="cd_bitcoin_address_qr">Bitcoin address QR code</string>
|
||||
<!-- On-chain send flow -->
|
||||
<string name="wallet_onchain_send_title">Send Payment</string>
|
||||
<string name="wallet_onchain_bitcoin_detected">Bitcoin address detected – on-chain payment</string>
|
||||
<string name="wallet_onchain_amount_sats">Amount (sats)</string>
|
||||
<string name="wallet_onchain_use_all" formatted="false">Use All (%,d sats)</string>
|
||||
<string name="wallet_onchain_fetching_fee">Estimating fee…</string>
|
||||
<string name="wallet_onchain_get_fee_quote">Get Fee Quote</string>
|
||||
<string name="wallet_onchain_amount">Amount</string>
|
||||
<string name="wallet_onchain_fee">Network Fee</string>
|
||||
<string name="wallet_onchain_total">Total</string>
|
||||
<string name="wallet_onchain_continue">Continue</string>
|
||||
<string name="wallet_onchain_plus_fee" formatted="false">+ %,d sats fee</string>
|
||||
<string name="wallet_onchain_sending_to">Sending to:</string>
|
||||
<string name="wallet_onchain_irreversible_warning">Bitcoin transactions cannot be reversed. Please verify the address is correct.</string>
|
||||
<string name="wallet_onchain_send_confirm">Confirm Send</string>
|
||||
<string name="wallet_onchain_payment_sent">Sent</string>
|
||||
<string name="wallet_onchain_payment_failed">Send Failed</string>
|
||||
<string name="wallet_onchain_view_mempool">View on mempool.space</string>
|
||||
<!-- Spark send placeholder (accepts Lightning + Bitcoin addresses) -->
|
||||
<string name="placeholder_send_spark">Lightning invoice or Bitcoin address</string>
|
||||
<string name="wallet_send_input_hint_onchain">Lightning or Bitcoin address, invoice, or CLINK offer</string>
|
||||
<string name="wallet_send_input_hint_onchain">Lightning address, invoice, or CLINK offer</string>
|
||||
<!-- Generic actions -->
|
||||
<string name="retry">Retry</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
|
||||
Reference in New Issue
Block a user