From 493a5cb0c39fb1a4efe3d3343cb3012124da0520 Mon Sep 17 00:00:00 2001 From: minibits-cash Date: Tue, 14 Jul 2026 23:01:02 +0200 Subject: [PATCH] Onchain pay: one card, and honour the payee's BIP21 description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address and description now share a single card, separated by a rule, instead of sitting in two cards that read as two unrelated decisions. The description is READ-ONLY and there is no field for the user to write their own. A NUT-30 melt request carries {quote, fee_index, inputs, outputs}, and a Bitcoin transaction has nowhere to put a message either, so nothing the user typed could ever reach the recipient — and everywhere else in this wallet a memo IS for the payee, so an editable one here would mean the same word promising something different on one screen. What the payee sends is a different thing entirely. BIP21 label/message is their own text arriving with the request, so the wallet honours it exactly as it honours a bolt11 invoice's description: shown on the pay screen, saved to transaction.memo, visible in history. The row is absent when the URI carried none. 87 tsc (unchanged baseline), 320/320, i18n clean. --- src/i18n_messages/en.json | 1 + src/i18n_messages/es.json | 1 + src/i18n_messages/pt.json | 1 + src/i18n_messages/sk.json | 1 + src/screens/OnchainTransferScreen.tsx | 70 ++++++++++++++++++--------- src/services/incomingParser.ts | 15 ++++-- 6 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/i18n_messages/en.json b/src/i18n_messages/en.json index 5b71d29c..b8151c3e 100644 --- a/src/i18n_messages/en.json +++ b/src/i18n_messages/en.json @@ -309,6 +309,7 @@ "onchainTransferScreen_feeTierBlocks": "About %{blocks} blocks", "onchainTransferScreen_feeTierReserve": "Up to %{amount} %{currency}", "onchainTransferScreen_feeTierSubtext": "About %{blocks} blocks · up to %{amount} %{currency}", + "onchainTransferScreen_memoFromPayee": "Payment description", "onchainTransferScreen_networkFee": "Network fee", "onchainTransferScreen_noFeeOptions": "The mint returned no fee options for this payment and it can not be paid.", "onchainTransferScreen_requestQuote": "Continue", diff --git a/src/i18n_messages/es.json b/src/i18n_messages/es.json index e8b5ae62..4b9ac339 100644 --- a/src/i18n_messages/es.json +++ b/src/i18n_messages/es.json @@ -308,6 +308,7 @@ "onchainTransferScreen_feeTierBlocks": "Unos %{blocks} bloques", "onchainTransferScreen_feeTierReserve": "Hasta %{amount} %{currency}", "onchainTransferScreen_feeTierSubtext": "Unos %{blocks} bloques · hasta %{amount} %{currency}", + "onchainTransferScreen_memoFromPayee": "Descripción del pago", "onchainTransferScreen_networkFee": "Tarifa de red", "onchainTransferScreen_noFeeOptions": "La casa de cambio no devolvió opciones de tarifa para este pago y no se puede pagar.", "onchainTransferScreen_requestQuote": "Continuar", diff --git a/src/i18n_messages/pt.json b/src/i18n_messages/pt.json index 5a2733aa..a1c8e944 100644 --- a/src/i18n_messages/pt.json +++ b/src/i18n_messages/pt.json @@ -309,6 +309,7 @@ "onchainTransferScreen_feeTierBlocks": "Cerca de %{blocks} blocos", "onchainTransferScreen_feeTierReserve": "Até %{amount} %{currency}", "onchainTransferScreen_feeTierSubtext": "Cerca de %{blocks} blocos · até %{amount} %{currency}", + "onchainTransferScreen_memoFromPayee": "Descrição do pagamento", "onchainTransferScreen_networkFee": "Taxa de rede", "onchainTransferScreen_noFeeOptions": "A casa da moeda não devolveu opções de taxa para este pagamento e não pode ser pago.", "onchainTransferScreen_requestQuote": "Continuar", diff --git a/src/i18n_messages/sk.json b/src/i18n_messages/sk.json index 27e58adb..edf19366 100644 --- a/src/i18n_messages/sk.json +++ b/src/i18n_messages/sk.json @@ -309,6 +309,7 @@ "onchainTransferScreen_feeTierBlocks": "Približne %{blocks} blokov", "onchainTransferScreen_feeTierReserve": "Až do %{amount} %{currency}", "onchainTransferScreen_feeTierSubtext": "Približne %{blocks} blokov · až do %{amount} %{currency}", + "onchainTransferScreen_memoFromPayee": "Popis platby", "onchainTransferScreen_networkFee": "Sieťový poplatok", "onchainTransferScreen_noFeeOptions": "Mincovňa nevrátila žiadne možnosti poplatku pre túto platbu, nedá sa zaplatiť.", "onchainTransferScreen_requestQuote": "Pokračovať", diff --git a/src/screens/OnchainTransferScreen.tsx b/src/screens/OnchainTransferScreen.tsx index 63190616..1170a222 100644 --- a/src/screens/OnchainTransferScreen.tsx +++ b/src/screens/OnchainTransferScreen.tsx @@ -19,7 +19,7 @@ import {StackActions, StaticScreenProps, useNavigation} from '@react-navigation/ import {TextInput, TextStyle, View, ViewStyle} from 'react-native' import numbro from 'numbro' import {MeltQuoteOnchainResponse} from '@cashu/cashu-ts' -import {colors, spacing, typography, useThemeColor} from '../theme' +import {colors, spacing, useThemeColor} from '../theme' import { AmountInput, BottomModal, @@ -33,7 +33,6 @@ import { Screen, Text, } from '../components' -import {MemoInputCard} from '../components/MemoInputCard' import {useStores} from '../models' import {MintBalance} from '../models/Mint' import {Transaction, TransactionStatus} from '../models/Transaction' @@ -60,6 +59,7 @@ type Props = StaticScreenProps<{ address: string /** BIP21 amount hint, in sats. The user may change it. */ amountSat?: number + /** BIP21 label/message: the PAYEE's description. Read-only. */ memo?: string unit: MintUnit mintUrl?: string @@ -255,8 +255,8 @@ export const OnchainTransferScreen = observer(function OnchainTransferScreen({ro unitRef.current = unit - // BIP21 hints. Both are the payee's suggestion, not a commitment: the user can - // change the amount, and the mint prices the payment from what they confirm. + // The BIP21 amount is the payee's suggestion, not a commitment: the user can + // change it, and the mint prices the payment from what they confirm. if (amountSat && amountSat > 0) { setAmountToTransfer( numbro(amountSat).format({thousandSeparated: true, mantissa: 0}), @@ -527,36 +527,60 @@ export const OnchainTransferScreen = observer(function OnchainTransferScreen({ro + {/* + * Destination, and the payee's description of the payment when the BIP21 URI + * carried one. + * + * The memo is READ-ONLY, and there is no field for the user to write their + * own. A NUT-30 melt request carries {quote, fee_index, inputs, outputs}, and + * a Bitcoin transaction has nowhere to put a message either — so nothing they + * typed could reach anyone. What the payee sent, though, is real: it is their + * text, arriving with the request, and the wallet honours it exactly as it + * honours a bolt11 invoice's description — displayed here, saved on the + * transaction, visible in history. + */} {!isSettled && ( + + } + bottomSeparator={!!memo} + style={$item} + /> + {!!memo && ( + + } + style={$item} /> - } - style={$item} - /> + )} + } /> )} {!hasQuote && !isSettled && ( <> - {}} - disabled={false} - />