Code review:

- Account.kt: collapse three identical backend-not-configured Failure
  constructions into one helper
- OnchainZapSendError: declare causeIsUserFacing on the enum so the
  sender owns which failures carry a human-readable cause, instead of
  the UI mapper hardcoding the list
- SendPaymentScreen: fee chip label is now a single format resource
  instead of manual string concatenation
This commit is contained in:
davotoula
2026-06-12 14:05:12 +02:00
parent 97f9cca43e
commit a593ec35f4
5 changed files with 37 additions and 37 deletions
@@ -892,6 +892,13 @@ class Account(
return zapRequest
}
private fun onchainBackendNotConfigured() =
OnchainZapSendResult.Failure(
OnchainZapSendStage.LOADING_UTXOS,
OnchainZapSendError.BACKEND_NOT_CONFIGURED,
ONCHAIN_BACKEND_NOT_CONFIGURED,
)
/**
* Send a NIP-BC onchain zap: build a Bitcoin transaction paying the recipient's
* derived Taproot address, sign it, broadcast it, and publish the kind:8333
@@ -907,11 +914,7 @@ class Account(
): OnchainZapSendResult {
val backend =
cache.onchainBackend
?: return OnchainZapSendResult.Failure(
OnchainZapSendStage.LOADING_UTXOS,
OnchainZapSendError.BACKEND_NOT_CONFIGURED,
ONCHAIN_BACKEND_NOT_CONFIGURED,
)
?: return onchainBackendNotConfigured()
return OnchainZapSender.send(
backend = backend,
signer = signer,
@@ -936,11 +939,7 @@ class Account(
): OnchainZapSendResult {
val backend =
cache.onchainBackend
?: return OnchainZapSendResult.Failure(
OnchainZapSendStage.LOADING_UTXOS,
OnchainZapSendError.BACKEND_NOT_CONFIGURED,
ONCHAIN_BACKEND_NOT_CONFIGURED,
)
?: return onchainBackendNotConfigured()
return OnchainZapSender.sendToAddress(
backend = backend,
signer = signer,
@@ -964,11 +963,7 @@ class Account(
): OnchainZapSendResult {
val backend =
cache.onchainBackend
?: return OnchainZapSendResult.Failure(
OnchainZapSendStage.LOADING_UTXOS,
OnchainZapSendError.BACKEND_NOT_CONFIGURED,
ONCHAIN_BACKEND_NOT_CONFIGURED,
)
?: return onchainBackendNotConfigured()
return OnchainZapSender.sendSplit(
backend = backend,
signer = signer,
@@ -749,8 +749,12 @@ private fun OnchainFeeSection(
label = {
Text(
if (rate != null) {
"${stringRes(tier.labelRes)} · " +
stringRes(R.string.onchain_send_fee_rate_eta, "%.1f".format(rate), stringRes(tier.etaLabelRes))
stringRes(
R.string.onchain_send_fee_tier_label_rate_eta,
stringRes(tier.labelRes),
"%.1f".format(rate),
stringRes(tier.etaLabelRes),
)
} else {
stringRes(tier.labelRes)
},
@@ -49,19 +49,11 @@ fun OnchainZapSendResult.Failure.userMessage(context: Context): String {
}
/**
* Untranslated diagnostic detail worth showing under the localized headline:
* build/sign/dust failures carry a crafted, specific reason (e.g. insufficient
* funds, signer tampering, which share is below dust). Other stages only carry
* low-level exception text, which we keep out of the UI.
* Untranslated diagnostic detail worth showing under the localized headline.
* Which errors carry a human-readable cause is a fact about the sender,
* declared on [OnchainZapSendError.causeIsUserFacing].
*/
fun OnchainZapSendResult.Failure.technicalDetail(): String? =
when (error) {
OnchainZapSendError.BUILD_FAILED,
OnchainZapSendError.SIGN_FAILED,
OnchainZapSendError.RECIPIENT_BELOW_DUST,
-> cause?.message
else -> null
}
fun OnchainZapSendResult.Failure.technicalDetail(): String? = if (error.causeIsUserFacing) cause?.message else null
@StringRes
private fun OnchainZapSendError.messageRes(): Int =
+1
View File
@@ -2240,6 +2240,7 @@
<string name="onchain_send_fee_eta_normal">~30 min</string>
<string name="onchain_send_fee_eta_fast">~10 min</string>
<string name="onchain_send_fee_rate_eta">%1$s sat/vB · %2$s</string>
<string name="onchain_send_fee_tier_label_rate_eta">%1$s · %2$s sat/vB · %3$s</string>
<string name="onchain_send_loading_fees">Loading fee estimates…</string>
<string name="onchain_send_button_amount">Send %1$s sats</string>
<plurals name="onchain_send_button_amount_split">
@@ -61,27 +61,35 @@ enum class OnchainZapSendStage {
* [OnchainZapSendResult.Failure.message] stays available for logs, tests, and
* the CLI.
*/
enum class OnchainZapSendError {
enum class OnchainZapSendError(
/**
* Whether [OnchainZapSendResult.Failure.cause] carries a message crafted
* for humans (insufficient funds, signer tampering, which share is below
* dust) as opposed to low-level exception text. UIs may surface the cause
* message as a diagnostic detail when this is true.
*/
val causeIsUserFacing: Boolean,
) {
/** No [OnchainBackend] is configured for the account. */
BACKEND_NOT_CONFIGURED,
BACKEND_NOT_CONFIGURED(causeIsUserFacing = false),
/** The chain backend could not return the sender's UTXOs. */
LOAD_UTXOS_FAILED,
LOAD_UTXOS_FAILED(causeIsUserFacing = false),
/** Coin selection / PSBT assembly failed (e.g. insufficient funds). */
BUILD_FAILED,
BUILD_FAILED(causeIsUserFacing = true),
/** A recipient's split share is below the dust threshold. */
RECIPIENT_BELOW_DUST,
RECIPIENT_BELOW_DUST(causeIsUserFacing = true),
/** Signing, signature verification, or finalization failed. */
SIGN_FAILED,
SIGN_FAILED(causeIsUserFacing = true),
/** The signed transaction could not be broadcast. */
BROADCAST_FAILED,
BROADCAST_FAILED(causeIsUserFacing = false),
/** The payment broadcast, but a kind:8333 receipt could not be published. */
RECEIPT_PUBLISH_FAILED,
RECEIPT_PUBLISH_FAILED(causeIsUserFacing = false),
}
/** Outcome of an [OnchainZapSender.send] attempt. */