feat: surface on-chain zaps from the reactions zap button

Adds a second row of bitcoin-orange chips to the zap amount popup that
opens the existing OnchainZapSendDialog prefilled with the chosen amount
and the note's author + zappedEvent. The on-chain stack (kind 8333,
OnchainZapSender, Account.sendOnchainZap, OnchainZapSendDialog) already
existed end-to-end; only the entry point from the reactions row and the
on-chain preset amounts were missing.

Settings:
- AccountZapPreferencesInternal: new onchainZapAmountChoices (defaults
  10k/50k/250k sats); back-compat via kotlinx.serialization defaults
- AccountSyncedSettings / AccountSettings / Account.updateZapAmounts:
  wired through end-to-end alongside the existing Lightning list
- UpdateZapAmountDialog: new "Quick On-chain Zap Amounts" section with
  its own chip list and add-amount field; the existing zap-privacy
  dropdown stays Lightning-only since on-chain has no zap type

Reactions UI:
- ZapAmountChoicePopup now collects both choice lists and offers an
  on-chain row in addition to the Lightning one
- New on-chain callback opens OnchainZapSendDialog with the prefilled
  amount; the existing dialog gains a prefillAmountSats parameter and
  now reads the on-chain preset list for its own quick-pick chips
- Other zap entry points (ReusableZapButton, NestActionBar,
  FilteredZapAmountChoicePopup) are unchanged: on-chain row defaults
  off so they keep their existing Lightning-only behavior
This commit is contained in:
Claude
2026-05-20 19:24:10 +00:00
parent 872bb4f245
commit 360dea9de9
10 changed files with 291 additions and 36 deletions
@@ -603,12 +603,14 @@ class Account(
suspend fun updateZapAmounts(
amountSet: List<Long>,
onchainAmountSet: List<Long>,
selectedZapType: LnZapEvent.ZapType,
nip47Update: Nip47WalletConnect.Nip47URINorm?,
) {
var changed = false
if (settings.changeZapAmounts(amountSet)) changed = true
if (settings.changeOnchainZapAmounts(onchainAmountSet)) changed = true
if (settings.changeDefaultZapType(selectedZapType)) changed = true
if (settings.changeZapPaymentRequest(nip47Update)) changed = true
@@ -267,6 +267,15 @@ class AccountSettings(
return false
}
fun changeOnchainZapAmounts(newAmounts: List<Long>): Boolean {
if (syncedSettings.zaps.onchainZapAmountChoices.value != newAmounts) {
syncedSettings.zaps.onchainZapAmountChoices.tryEmit(newAmounts.toImmutableList())
saveAccountSettings()
return true
}
return false
}
fun changeReactionTypes(newTypes: List<String>): Boolean {
if (syncedSettings.reactions.reactionChoices.value != newTypes) {
syncedSettings.reactions.reactionChoices.tryEmit(newTypes.toImmutableList())
@@ -40,6 +40,7 @@ class AccountSyncedSettings(
val zaps =
AccountZapPreferences(
MutableStateFlow(internalSettings.zaps.zapAmountChoices.toImmutableList()),
MutableStateFlow(internalSettings.zaps.onchainZapAmountChoices.toImmutableList()),
MutableStateFlow(internalSettings.zaps.defaultZapType),
)
val languages =
@@ -69,6 +70,7 @@ class AccountSyncedSettings(
zaps =
AccountZapPreferencesInternal(
zaps.zapAmountChoices.value,
zaps.onchainZapAmountChoices.value,
zaps.defaultZapType.value,
),
languages =
@@ -107,6 +109,11 @@ class AccountSyncedSettings(
zaps.zapAmountChoices.tryEmit(newZapChoices)
}
val newOnchainZapChoices = syncedSettingsInternal.zaps.onchainZapAmountChoices.toImmutableList()
if (!equalImmutableLists(zaps.onchainZapAmountChoices.value, newOnchainZapChoices)) {
zaps.onchainZapAmountChoices.tryEmit(newOnchainZapChoices)
}
if (zaps.defaultZapType.value != syncedSettingsInternal.zaps.defaultZapType) {
zaps.defaultZapType.tryEmit(syncedSettingsInternal.zaps.defaultZapType)
}
@@ -175,6 +182,7 @@ class AccountVideoPlayerPreferences(
@Stable
class AccountZapPreferences(
var zapAmountChoices: MutableStateFlow<ImmutableList<Long>>,
var onchainZapAmountChoices: MutableStateFlow<ImmutableList<Long>>,
val defaultZapType: MutableStateFlow<LnZapEvent.ZapType>,
)
@@ -38,6 +38,7 @@ val DefaultReactions =
)
val DefaultZapAmounts = listOf(100L, 500L, 1000L)
val DefaultOnchainZapAmounts = listOf(10_000L, 50_000L, 250_000L)
val DefaultReportWarningThreshold = 5
@Serializable
@@ -153,6 +154,7 @@ class AccountReactionPreferencesInternal(
@Serializable
class AccountZapPreferencesInternal(
var zapAmountChoices: List<Long> = DefaultZapAmounts,
var onchainZapAmountChoices: List<Long> = DefaultOnchainZapAmounts,
val defaultZapType: LnZapEvent.ZapType = LnZapEvent.ZapType.PUBLIC,
)
@@ -136,6 +136,7 @@ import com.vitorpamplona.amethyst.ui.navigation.routes.routeReplyTo
import com.vitorpamplona.amethyst.ui.note.types.EditState
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.PaymentTargetsDialog
import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.OnchainZapSendDialog
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
@@ -172,6 +173,7 @@ import com.vitorpamplona.amethyst.ui.theme.reactionBox
import com.vitorpamplona.amethyst.ui.theme.ripple24dp
import com.vitorpamplona.amethyst.ui.theme.selectedReactionBoxModifier
import com.vitorpamplona.quartz.experimental.nipA3.PaymentTargetsEvent
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable
@@ -1121,6 +1123,11 @@ private fun likeClick(
}
}
@Immutable
private data class OnchainZapRequest(
val amountSats: Long?,
)
@Composable
@OptIn(ExperimentalFoundationApi::class, ExperimentalUuidApi::class)
fun ZapReaction(
@@ -1135,6 +1142,9 @@ fun ZapReaction(
) {
var wantsToZap by remember { mutableStateOf(false) }
var wantsToSetCustomZap by remember { mutableStateOf(false) }
// null = closed; OnchainZapRequest(amount=null) = open with no prefill;
// OnchainZapRequest(amount=N) = open prefilled to N sats.
var onchainZapRequest by remember { mutableStateOf<OnchainZapRequest?>(null) }
val context = LocalContext.current
val scope = rememberCoroutineScope()
@@ -1207,6 +1217,10 @@ fun ZapReaction(
nav.nav(Route.UpdateZapAmount())
}
},
onOnchainAmount = { amount ->
wantsToZap = false
onchainZapRequest = OnchainZapRequest(amount)
},
onError = { _, message, user ->
scope.launch {
zappingProgress = 0f
@@ -1230,6 +1244,16 @@ fun ZapReaction(
)
}
onchainZapRequest?.let { request ->
OnchainZapSendDialog(
accountViewModel = accountViewModel,
onDismiss = { onchainZapRequest = null },
recipientPubKey = baseNote.author?.pubkeyHex,
zappedEvent = baseNote.toEventHint<Event>(),
prefillAmountSats = request.amountSats,
)
}
if (wantsToSetCustomZap) {
ZapCustomDialog(
onZapStarts = { zapStartingTime = TimeUtils.now() },
@@ -1834,12 +1858,29 @@ fun ZapAmountChoicePopup(
onError: (title: String, text: String, user: User?) -> Unit,
onProgress: (percent: Float) -> Unit,
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
onOnchainAmount: ((Long?) -> Unit)? = null,
) {
val zapAmountChoices by
accountViewModel.account.settings.syncedSettings.zaps.zapAmountChoices
.collectAsStateWithLifecycle()
val onchainZapAmountChoices by
accountViewModel.account.settings.syncedSettings.zaps.onchainZapAmountChoices
.collectAsStateWithLifecycle()
ZapAmountChoicePopup(baseNote, zapAmountChoices, accountViewModel, popupYOffset, onZapStarts, onDismiss, onChangeAmount, onError, onProgress, onPayViaIntent)
ZapAmountChoicePopup(
baseNote = baseNote,
zapAmountChoices = zapAmountChoices,
accountViewModel = accountViewModel,
popupYOffset = popupYOffset,
onZapStarts = onZapStarts,
onDismiss = onDismiss,
onChangeAmount = onChangeAmount,
onError = onError,
onProgress = onProgress,
onPayViaIntent = onPayViaIntent,
onchainZapAmountChoices = if (onOnchainAmount != null) onchainZapAmountChoices else persistentListOf(),
onOnchainAmount = onOnchainAmount ?: {},
)
}
@Composable
@@ -1854,9 +1895,11 @@ fun ZapAmountChoicePopup(
onError: (title: String, text: String, user: User?) -> Unit,
onProgress: (percent: Float) -> Unit,
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
onchainZapAmountChoices: ImmutableList<Long> = persistentListOf(),
onOnchainAmount: (Long?) -> Unit = {},
) {
val visibilityState = rememberVisibilityState(onDismiss)
ZapAmountChoicePopup(baseNote, zapAmountChoices, accountViewModel, popupYOffset, visibilityState, onZapStarts, onChangeAmount, onError, onProgress, onPayViaIntent)
ZapAmountChoicePopup(baseNote, zapAmountChoices, onchainZapAmountChoices, accountViewModel, popupYOffset, visibilityState, onZapStarts, onChangeAmount, onOnchainAmount, onError, onProgress, onPayViaIntent)
}
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
@@ -1864,11 +1907,13 @@ fun ZapAmountChoicePopup(
fun ZapAmountChoicePopup(
baseNote: Note,
zapAmountChoices: ImmutableList<Long>,
onchainZapAmountChoices: ImmutableList<Long>,
accountViewModel: AccountViewModel,
popupYOffset: Dp,
visibilityState: MutableTransitionState<Boolean>,
onZapStarts: () -> Unit,
onChangeAmount: () -> Unit,
onOnchainAmount: (Long?) -> Unit,
onError: (title: String, text: String, user: User?) -> Unit,
onProgress: (percent: Float) -> Unit,
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
@@ -1889,6 +1934,7 @@ fun ZapAmountChoicePopup(
) {
ZapAmountChoicePopupContent(
zapAmountChoices = zapAmountChoices,
onchainZapAmountChoices = onchainZapAmountChoices,
onZap = { amountInSats ->
onZapStarts()
accountViewModel.zap(
@@ -1905,6 +1951,10 @@ fun ZapAmountChoicePopup(
visibilityState.targetState = false
},
onChangeAmount = onChangeAmount,
onOnchainAmount = { amount ->
onOnchainAmount(amount)
visibilityState.targetState = false
},
)
}
}
@@ -1916,6 +1966,8 @@ fun ZapAmountChoicePopupContent(
zapAmountChoices: ImmutableList<Long>,
onZap: (Long) -> Unit,
onChangeAmount: () -> Unit,
onchainZapAmountChoices: ImmutableList<Long> = persistentListOf(),
onOnchainAmount: (Long?) -> Unit = {},
) {
Box(HalfPadding, contentAlignment = Center) {
ElevatedCard(
@@ -1923,33 +1975,66 @@ fun ZapAmountChoicePopupContent(
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 8.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
) {
FlowRow(
modifier = Modifier.padding(horizontal = 5.dp, vertical = 5.dp),
horizontalArrangement = Arrangement.Center,
verticalArrangement = Arrangement.Center,
itemVerticalAlignment = CenterVertically,
) {
zapAmountChoices.forEach { amountInSats ->
ZapAmountChip(
amountInSats = amountInSats,
onClick = { onZap(amountInSats) },
onLongClick = onChangeAmount,
)
}
ClickableBox(
modifier =
Modifier
.padding(horizontal = 4.dp, vertical = 6.dp)
.size(32.dp)
.padding(7.dp),
onClick = onChangeAmount,
Column {
FlowRow(
modifier = Modifier.padding(horizontal = 5.dp, vertical = 5.dp),
horizontalArrangement = Arrangement.Center,
verticalArrangement = Arrangement.Center,
itemVerticalAlignment = CenterVertically,
) {
Icon(
symbol = MaterialSymbols.Tune,
contentDescription = stringRes(R.string.quick_zap_amounts),
modifier = Size18Modifier,
tint = MaterialTheme.colorScheme.placeholderText,
)
zapAmountChoices.forEach { amountInSats ->
ZapAmountChip(
amountInSats = amountInSats,
onClick = { onZap(amountInSats) },
onLongClick = onChangeAmount,
)
}
ClickableBox(
modifier =
Modifier
.padding(horizontal = 4.dp, vertical = 6.dp)
.size(32.dp)
.padding(7.dp),
onClick = onChangeAmount,
) {
Icon(
symbol = MaterialSymbols.Tune,
contentDescription = stringRes(R.string.quick_zap_amounts),
modifier = Size18Modifier,
tint = MaterialTheme.colorScheme.placeholderText,
)
}
}
if (onchainZapAmountChoices.isNotEmpty()) {
FlowRow(
modifier = Modifier.padding(horizontal = 5.dp, vertical = 5.dp),
horizontalArrangement = Arrangement.Center,
verticalArrangement = Arrangement.Center,
itemVerticalAlignment = CenterVertically,
) {
onchainZapAmountChoices.forEach { amountInSats ->
OnchainZapAmountChip(
amountInSats = amountInSats,
onClick = { onOnchainAmount(amountInSats) },
onLongClick = onChangeAmount,
)
}
ClickableBox(
modifier =
Modifier
.padding(horizontal = 4.dp, vertical = 6.dp)
.size(32.dp)
.padding(7.dp),
onClick = { onOnchainAmount(null) },
) {
Icon(
symbol = MaterialSymbols.Tune,
contentDescription = stringRes(R.string.quick_zap_amounts_onchain),
modifier = Size18Modifier,
tint = MaterialTheme.colorScheme.placeholderText,
)
}
}
}
}
}
@@ -1992,14 +2077,52 @@ private fun ZapAmountChip(
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun OnchainZapAmountChip(
amountInSats: Long,
onClick: () -> Unit,
onLongClick: () -> Unit,
) {
Surface(
shape = ButtonBorder,
color = BitcoinOrange,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 6.dp),
) {
Row(
modifier =
Modifier
.combinedClickable(onClick = onClick, onLongClick = onLongClick)
.padding(horizontal = 12.dp, vertical = 6.dp),
verticalAlignment = CenterVertically,
) {
Icon(
symbol = MaterialSymbols.CurrencyBitcoin,
contentDescription = null,
modifier = Size18Modifier,
tint = Color.White,
)
Spacer(Modifier.width(2.dp))
Text(
text = showAmount(amountInSats.toBigDecimal().setScale(1)),
color = Color.White,
fontWeight = FontWeight.SemiBold,
textAlign = TextAlign.Center,
)
}
}
}
@Preview
@Composable
fun ZapAmountChoicePopupPreview() {
ThemeComparisonColumn {
ZapAmountChoicePopupContent(
zapAmountChoices = persistentListOf(50L, 100L, 500L, 1_000L, 5_000L, 10_000L, 100_000L),
onchainZapAmountChoices = persistentListOf(10_000L, 50_000L, 250_000L),
onZap = {},
onChangeAmount = {},
onOnchainAmount = {},
)
}
}
@@ -306,7 +306,96 @@ fun UpdateZapAmountContent(
)
}
// ── Section 2: Zap Privacy ────────────────────────────────────────────
// ── Section 2: Quick On-chain Zap Amounts ─────────────────────────────
Text(
text = stringRes(R.string.quick_zap_amounts_onchain),
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.titleSmall,
modifier = SettingsCategorySpacingModifier,
)
Text(
text = stringRes(R.string.quick_zap_amounts_onchain_explainer),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.placeholderText,
modifier = Modifier.padding(bottom = 6.dp),
)
FlowRow(
modifier =
Modifier
.fillMaxWidth()
.animateContentSize(animationSpec = spring(stiffness = Spring.StiffnessMediumLow)),
horizontalArrangement = Arrangement.spacedBy(6.dp),
verticalArrangement = Arrangement.spacedBy(6.dp),
) {
postViewModel.onchainAmountSet.forEach { amountInSats ->
InputChip(
selected = false,
onClick = { postViewModel.removeOnchainAmount(amountInSats) },
label = {
Text(
text = "${showAmount(amountInSats.toBigDecimal().setScale(1))}",
)
},
trailingIcon = {
Icon(
symbol = MaterialSymbols.Close,
contentDescription = stringRes(R.string.remove),
modifier = Modifier.size(InputChipDefaults.AvatarSize),
)
},
colors =
InputChipDefaults.inputChipColors(
containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.12f),
labelColor = MaterialTheme.colorScheme.primary,
trailingIconColor = MaterialTheme.colorScheme.primary,
),
)
}
}
Spacer(modifier = Modifier.height(6.dp))
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
OutlinedTextField(
label = { Text(text = stringRes(R.string.new_amount_in_sats_onchain)) },
value = postViewModel.nextOnchainAmount,
onValueChange = { postViewModel.nextOnchainAmount = it },
keyboardOptions =
KeyboardOptions.Default.copy(
capitalization = KeyboardCapitalization.None,
keyboardType = KeyboardType.Number,
),
placeholder = {
Text(
text = "10000, 50000, 250000",
color = MaterialTheme.colorScheme.placeholderText,
)
},
trailingIcon = {
IconButton(
onClick = postViewModel::addOnchainAmount,
shape = ButtonBorder,
enabled = postViewModel.nextOnchainAmount.text.isNotBlank(),
) {
Icon(
symbol = MaterialSymbols.AddCircle,
contentDescription = stringRes(R.string.add),
modifier = Size20Modifier,
)
}
},
singleLine = true,
modifier = Modifier.weight(1f),
)
}
// ── Section 3: Zap Privacy ────────────────────────────────────────────
Text(
text = stringRes(R.string.zap_privacy_section),
@@ -41,6 +41,8 @@ class UpdateZapAmountViewModel : ViewModel() {
var nextAmount by mutableStateOf(TextFieldValue(""))
var amountSet by mutableStateOf(listOf<Long>())
var nextOnchainAmount by mutableStateOf(TextFieldValue(""))
var onchainAmountSet by mutableStateOf(listOf<Long>())
var walletConnectRelay by mutableStateOf(TextFieldValue(""))
var walletConnectPubkey by mutableStateOf(TextFieldValue(""))
var walletConnectSecret by mutableStateOf(TextFieldValue(""))
@@ -59,6 +61,7 @@ class UpdateZapAmountViewModel : ViewModel() {
fun load() {
this.amountSet = accountViewModel.account.settings.syncedSettings.zaps.zapAmountChoices.value
this.onchainAmountSet = accountViewModel.account.settings.syncedSettings.zaps.onchainZapAmountChoices.value
this.selectedZapType = accountViewModel.account.settings.syncedSettings.zaps.defaultZapType.value
val nip47 = accountViewModel.account.settings.defaultZapPaymentRequest()
@@ -83,6 +86,19 @@ class UpdateZapAmountViewModel : ViewModel() {
amountSet = amountSet - amount
}
fun addOnchainAmount() {
val newValue = nextOnchainAmount.text.trim().toLongOrNull()
if (newValue != null) {
onchainAmountSet = onchainAmountSet + newValue
}
nextOnchainAmount = TextFieldValue("")
}
fun removeOnchainAmount(amount: Long) {
onchainAmountSet = onchainAmountSet - amount
}
fun sendPost() {
accountViewModel.launchSigner {
sendPostSuspend()
@@ -116,13 +132,15 @@ class UpdateZapAmountViewModel : ViewModel() {
null
}
accountViewModel.account.updateZapAmounts(amountSet, selectedZapType, nip47Update)
accountViewModel.account.updateZapAmounts(amountSet, onchainAmountSet, selectedZapType, nip47Update)
nextAmount = TextFieldValue("")
nextOnchainAmount = TextFieldValue("")
}
fun cancel() {
nextAmount = TextFieldValue("")
nextOnchainAmount = TextFieldValue("")
}
fun hasChanged(): Boolean {
@@ -130,6 +148,7 @@ class UpdateZapAmountViewModel : ViewModel() {
return (
selectedZapType != accountViewModel.account.settings.syncedSettings.zaps.defaultZapType.value ||
amountSet != accountViewModel.account.settings.syncedSettings.zaps.zapAmountChoices.value ||
onchainAmountSet != accountViewModel.account.settings.syncedSettings.zaps.onchainZapAmountChoices.value ||
walletConnectPubkey.text != (defaultUri?.pubKeyHex ?: "") ||
walletConnectRelay.text != (defaultUri?.relayUri?.url ?: "") ||
walletConnectSecret.text != (defaultUri?.secret ?: "")
@@ -1236,9 +1236,10 @@ class AccountViewModel(
fun updateZapAmounts(
amountSet: List<Long>,
onchainAmountSet: List<Long>,
selectedZapType: LnZapEvent.ZapType,
nip47Update: Nip47WalletConnect.Nip47URINorm?,
) = launchSigner { account.updateZapAmounts(amountSet, selectedZapType, nip47Update) }
) = launchSigner { account.updateZapAmounts(amountSet, onchainAmountSet, selectedZapType, nip47Update) }
fun toggleDontTranslateFrom(languageCode: String) = launchSigner { account.toggleDontTranslateFrom(languageCode) }
@@ -127,6 +127,7 @@ fun OnchainZapSendDialog(
onDismiss: () -> Unit,
recipientPubKey: HexKey? = null,
zappedEvent: EventHintBundle<out Event>? = null,
prefillAmountSats: Long? = null,
) {
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val scope = rememberCoroutineScope()
@@ -141,7 +142,7 @@ fun OnchainZapSendDialog(
var searchInput by remember { mutableStateOf("") }
var selectedUser by remember { mutableStateOf<User?>(null) }
var amountInput by remember { mutableStateOf("") }
var amountInput by remember { mutableStateOf(prefillAmountSats?.toString().orEmpty()) }
var comment by remember { mutableStateOf("") }
var feeTier by remember { mutableStateOf(FeeTier.NORMAL) }
var fees by remember { mutableStateOf<FeeEstimates?>(null) }
@@ -155,10 +156,8 @@ fun OnchainZapSendDialog(
runCatching { withContext(Dispatchers.IO) { backend.feeEstimates() } }.getOrNull()
}
val presetAmounts =
remember(accountViewModel) {
accountViewModel.zapAmountChoices()
}
val presetAmounts by accountViewModel.account.settings.syncedSettings.zaps.onchainZapAmountChoices
.collectAsStateWithLifecycle()
// Mirror the dropdown's NIP-05 / Namecoin (.bit) resolution so Send can
// enable as soon as the typed name resolves, without forcing the user to
+3
View File
@@ -821,6 +821,9 @@
<string name="wallet_connect_manual_config">Advanced: enter connection details manually</string>
<string name="quick_zap_amounts">Quick Zap Amounts</string>
<string name="quick_zap_amounts_explainer">Shown when pressing the zap button. Tap an amount to remove it. If you leave it empty, it will open the dialog to insert an amount every time.</string>
<string name="quick_zap_amounts_onchain">Quick On-chain Zap Amounts</string>
<string name="quick_zap_amounts_onchain_explainer">On-chain zaps pay miner fees, so amounts are usually larger than Lightning. Tap an amount to remove it.</string>
<string name="new_amount_in_sats_onchain">New on-chain amount in sats</string>
<string name="zap_privacy_section">Zap Privacy</string>
<string name="zap_type_section_explainer">Controls how your identity is shown when you send a zap.</string>
<string name="wallet_connect_connect_app">Connect Wallet</string>