,
+): CashuAccount => {
+ const wallet = {
+ createMeltQuoteBolt11,
+ selectProofsToSend: () => ({
+ send: [{ secret: 'secret-1', amount: 1000 }],
+ }),
+ getFeesForProofs: () => 0,
+ };
+ return {
+ id: 'acc1',
+ name: 'test',
+ type: 'cashu',
+ purpose: 'transactional',
+ state: 'active',
+ isOnline: true,
+ currency: 'BTC',
+ createdAt: '2026-01-01T00:00:00Z',
+ version: 1,
+ expiresAt: null,
+ mintUrl: 'https://test.mint',
+ isTestMint: false,
+ keysetCounters: {},
+ proofs: [fakeProof] as never,
+ wallet: wallet as never,
+ };
+};
+
+const meltQuoteResponse = {
+ quote: 'q1',
+ amount: 100,
+ fee_reserve: 5,
+ state: 'UNPAID',
+ expiry: Math.floor(Date.now() / 1000) + 3600,
+ request: '',
+ unit: 'sat',
+};
+
+describe('CashuSendQuoteService.getLightningQuote', () => {
+ beforeEach(() => {
+ // The bolt11 test invoices are from 2017 (created 2017-06-01T10:57:38Z).
+ // The amounted variant has a 60-second expiry, so pin the clock just
+ // after creation so the service's expiry check does not flag it.
+ jest.useFakeTimers();
+ jest.setSystemTime(new Date('2017-06-01T10:58:00Z'));
+ });
+
+ afterEach(() => {
+ jest.useRealTimers();
+ });
+
+ test('passes amountInMsat when invoice is amountless + user supplies amount', async () => {
+ const createMeltQuoteBolt11 = mock(async () => meltQuoteResponse);
+ const account = buildAccount(createMeltQuoteBolt11);
+ const service = new CashuSendQuoteService({
+ // Repository is unused on this code path.
+ create: () => null,
+ } as never);
+
+ await service.getLightningQuote({
+ account,
+ paymentRequest: amountlessInvoice,
+ amount: new Money<'BTC'>({
+ amount: 100,
+ currency: 'BTC',
+ unit: 'sat',
+ }) as Money,
+ });
+
+ expect(createMeltQuoteBolt11).toHaveBeenCalledTimes(1);
+ expect(createMeltQuoteBolt11).toHaveBeenCalledWith(
+ amountlessInvoice,
+ 100_000, // 100 sat → 100_000 msat
+ );
+ });
+
+ test('does NOT pass amountInMsat when invoice already has an amount', async () => {
+ const createMeltQuoteBolt11 = mock(async () => meltQuoteResponse);
+ const account = buildAccount(createMeltQuoteBolt11);
+ const service = new CashuSendQuoteService({
+ create: () => null,
+ } as never);
+
+ await service.getLightningQuote({
+ account,
+ paymentRequest: amountedInvoice,
+ });
+
+ expect(createMeltQuoteBolt11).toHaveBeenCalledTimes(1);
+ expect(createMeltQuoteBolt11).toHaveBeenCalledWith(amountedInvoice);
+ });
+});
diff --git a/app/features/send/cashu-send-quote-service.ts b/app/features/send/cashu-send-quote-service.ts
index 24f6881c6..9d392a44a 100644
--- a/app/features/send/cashu-send-quote-service.ts
+++ b/app/features/send/cashu-send-quote-service.ts
@@ -137,17 +137,15 @@ export class CashuSendQuoteService {
throw new Error('Unknown send amount');
}
- // TODO: remove this once cashu-ts supports amountless lightning invoices
- if (!invoice.amountMsat) {
- throw new Error(
- 'Cashu accounts do not support amountless lightning invoices',
- );
- }
-
const cashuUnit = getCashuUnit(account.currency);
const wallet = account.wallet;
- const meltQuote = await wallet.createMeltQuoteBolt11(paymentRequest);
+ const meltQuote = invoice.amountMsat
+ ? await wallet.createMeltQuoteBolt11(paymentRequest)
+ : await wallet.createMeltQuoteBolt11(
+ paymentRequest,
+ amountRequestedInBtc.toNumber('msat'),
+ );
const amountWithLightningFee = meltQuote.amount + meltQuote.fee_reserve;
diff --git a/app/features/send/send-input.tsx b/app/features/send/send-input.tsx
index 253a6773b..348f8ff84 100644
--- a/app/features/send/send-input.tsx
+++ b/app/features/send/send-input.tsx
@@ -49,6 +49,7 @@ import type { Contact } from '../contacts/contact';
import { getDefaultUnit } from '../shared/currencies';
import { DomainError, getErrorMessage } from '../shared/error';
import { useSendStore } from './send-provider';
+import { canAccountPayAmountlessBolt11 } from './send-store';
export function SendInput() {
const { toast } = useToast();
@@ -71,6 +72,8 @@ export function SendInput() {
const continueSend = useSendStore((s) => s.proceedWithSend);
const status = useSendStore((s) => s.status);
+ const isAmountlessBolt11Allowed = canAccountPayAmountlessBolt11(sendAccount);
+
const sendAmountCurrencyUnit = sendAmount
? getDefaultUnit(sendAmount.currency)
: undefined;
@@ -258,7 +261,7 @@ export function SendInput() {