From 6bc1dd969ac0384918f4e1bbcc68e4dfcb64edcd Mon Sep 17 00:00:00 2001 From: pretyflaco Date: Sun, 3 May 2026 23:18:18 +0300 Subject: [PATCH] fix: preserve invoice memos for internal lightning payments --- .../get-account-transactions-for-contact.ts | 3 +- core/api/src/app/payments/send-lightning.ts | 2 +- .../src/app/wallets/get-transaction-by-id.ts | 3 -- .../wallets/get-transaction-by-journal-id.ts | 2 - .../wallets/get-transactions-by-addresses.ts | 3 +- .../app/wallets/get-transactions-by-hash.ts | 3 -- .../wallets/get-transactions-for-wallet.ts | 3 +- .../domain/wallet-invoices/index.types.d.ts | 1 + .../wallet-invoices/wallet-invoice-builder.ts | 1 + core/api/src/domain/wallets/tx-history.ts | 38 +-------------- core/api/src/services/mongoose/schema.ts | 4 ++ .../src/services/mongoose/schema.types.d.ts | 1 + .../src/services/mongoose/wallet-invoices.ts | 3 ++ core/api/test/helpers/wallet-invoices.ts | 1 + .../app/wallets/send-lightning.spec.ts | 4 ++ .../wallet-invoice-builder.spec.ts | 3 +- .../unit/domain/wallets/tx-history.spec.ts | 48 +++---------------- 17 files changed, 28 insertions(+), 95 deletions(-) diff --git a/core/api/src/app/accounts/get-account-transactions-for-contact.ts b/core/api/src/app/accounts/get-account-transactions-for-contact.ts index 7e1aed4059..433847902d 100644 --- a/core/api/src/app/accounts/get-account-transactions-for-contact.ts +++ b/core/api/src/app/accounts/get-account-transactions-for-contact.ts @@ -1,4 +1,4 @@ -import { MAX_PAGINATION_PAGE_SIZE, memoSharingConfig } from "@/config" +import { MAX_PAGINATION_PAGE_SIZE } from "@/config" import { LedgerError } from "@/domain/ledger" import { checkedToPaginatedQueryArgs } from "@/domain/primitives" import { WalletTransactionHistory } from "@/domain/wallets" @@ -40,7 +40,6 @@ export const getAccountTransactionsForContact = async ({ const transaction = WalletTransactionHistory.fromLedger({ txn: edge.node, nonEndUserWalletIds, - memoSharingConfig, }) return { diff --git a/core/api/src/app/payments/send-lightning.ts b/core/api/src/app/payments/send-lightning.ts index 70974f2efe..911234be8d 100644 --- a/core/api/src/app/payments/send-lightning.ts +++ b/core/api/src/app/payments/send-lightning.ts @@ -714,7 +714,7 @@ const lockedPaymentViaIntraledgerSteps = async ({ } const journal = await LedgerFacade.recordIntraledger({ - description: paymentFlow.descriptionFromInvoice, + description: walletInvoice.description || paymentFlow.descriptionFromInvoice, amount: { btc: paymentFlow.btcPaymentAmount, usd: paymentFlow.usdPaymentAmount, diff --git a/core/api/src/app/wallets/get-transaction-by-id.ts b/core/api/src/app/wallets/get-transaction-by-id.ts index 1a8a820531..b621e8b81e 100644 --- a/core/api/src/app/wallets/get-transaction-by-id.ts +++ b/core/api/src/app/wallets/get-transaction-by-id.ts @@ -1,4 +1,3 @@ -import { memoSharingConfig } from "@/config" import { WalletTransactionHistory } from "@/domain/wallets" import { checkedToLedgerTransactionId } from "@/domain/ledger" @@ -25,7 +24,6 @@ export const getTransactionForWalletById = async ({ return WalletTransactionHistory.fromLedger({ txn: ledgerTransaction, nonEndUserWalletIds: Object.values(await getNonEndUserWalletIds()), - memoSharingConfig, }) } @@ -43,6 +41,5 @@ export const getTransactionById = async ( return WalletTransactionHistory.fromLedger({ txn: ledgerTransaction, nonEndUserWalletIds: Object.values(await getNonEndUserWalletIds()), - memoSharingConfig, }) } diff --git a/core/api/src/app/wallets/get-transaction-by-journal-id.ts b/core/api/src/app/wallets/get-transaction-by-journal-id.ts index 934d193961..f264d1ac23 100644 --- a/core/api/src/app/wallets/get-transaction-by-journal-id.ts +++ b/core/api/src/app/wallets/get-transaction-by-journal-id.ts @@ -1,4 +1,3 @@ -import { memoSharingConfig } from "@/config" import { WalletTransactionHistory } from "@/domain/wallets" import { getNonEndUserWalletIds, LedgerService } from "@/services/ledger" @@ -20,6 +19,5 @@ export const getTransactionForWalletByJournalId = async ({ return WalletTransactionHistory.fromLedger({ txn: ledgerTransaction, nonEndUserWalletIds: Object.values(await getNonEndUserWalletIds()), - memoSharingConfig, }) } diff --git a/core/api/src/app/wallets/get-transactions-by-addresses.ts b/core/api/src/app/wallets/get-transactions-by-addresses.ts index 4c1c6c4f72..2590973b5b 100644 --- a/core/api/src/app/wallets/get-transactions-by-addresses.ts +++ b/core/api/src/app/wallets/get-transactions-by-addresses.ts @@ -1,4 +1,4 @@ -import { MAX_PAGINATION_PAGE_SIZE, memoSharingConfig } from "@/config" +import { MAX_PAGINATION_PAGE_SIZE } from "@/config" import { LedgerError } from "@/domain/ledger" import { WalletTransactionHistory } from "@/domain/wallets" @@ -42,7 +42,6 @@ export const getTransactionsForWalletsByAddresses = async ({ const transaction = WalletTransactionHistory.fromLedger({ txn: edge.node, nonEndUserWalletIds, - memoSharingConfig, }) return { diff --git a/core/api/src/app/wallets/get-transactions-by-hash.ts b/core/api/src/app/wallets/get-transactions-by-hash.ts index a678a078e5..eb74ac0a7b 100644 --- a/core/api/src/app/wallets/get-transactions-by-hash.ts +++ b/core/api/src/app/wallets/get-transactions-by-hash.ts @@ -1,4 +1,3 @@ -import { memoSharingConfig } from "@/config" import { WalletTransactionHistory } from "@/domain/wallets" import { getNonEndUserWalletIds, LedgerService } from "@/services/ledger" @@ -24,7 +23,6 @@ export const getTransactionsForWalletByPaymentHash = async ({ WalletTransactionHistory.fromLedger({ txn, nonEndUserWalletIds, - memoSharingConfig, }), ) } @@ -41,7 +39,6 @@ export const getTransactionsByHash = async ( WalletTransactionHistory.fromLedger({ txn, nonEndUserWalletIds, - memoSharingConfig, }), ) } diff --git a/core/api/src/app/wallets/get-transactions-for-wallet.ts b/core/api/src/app/wallets/get-transactions-for-wallet.ts index 96fd77f73b..7354aa2c4c 100644 --- a/core/api/src/app/wallets/get-transactions-for-wallet.ts +++ b/core/api/src/app/wallets/get-transactions-for-wallet.ts @@ -1,4 +1,4 @@ -import { MAX_PAGINATION_PAGE_SIZE, memoSharingConfig } from "@/config" +import { MAX_PAGINATION_PAGE_SIZE } from "@/config" import { LedgerError } from "@/domain/ledger" import { WalletTransactionHistory } from "@/domain/wallets" @@ -35,7 +35,6 @@ export const getTransactionsForWallets = async ({ const transaction = WalletTransactionHistory.fromLedger({ txn: edge.node, nonEndUserWalletIds, - memoSharingConfig, }) return { diff --git a/core/api/src/domain/wallet-invoices/index.types.d.ts b/core/api/src/domain/wallet-invoices/index.types.d.ts index 2ee1deacb5..edbd1c135e 100644 --- a/core/api/src/domain/wallet-invoices/index.types.d.ts +++ b/core/api/src/domain/wallet-invoices/index.types.d.ts @@ -103,6 +103,7 @@ type WalletInvoiceWithOptionalLnInvoice = { createdAt: Date processingCompleted: boolean externalId: LedgerExternalId + description?: string lnInvoice?: LnInvoice // LnInvoice is optional because some older invoices don't have it } diff --git a/core/api/src/domain/wallet-invoices/wallet-invoice-builder.ts b/core/api/src/domain/wallet-invoices/wallet-invoice-builder.ts index f93b93ae97..ed3a0605e9 100644 --- a/core/api/src/domain/wallet-invoices/wallet-invoice-builder.ts +++ b/core/api/src/domain/wallet-invoices/wallet-invoice-builder.ts @@ -173,6 +173,7 @@ export const WIBWithAmount = (state: WIBWithAmountState): WIBWithAmount => { lnInvoice: registeredInvoice.invoice, processingCompleted: false, externalId, + description: state.description, } return walletInvoice } diff --git a/core/api/src/domain/wallets/tx-history.ts b/core/api/src/domain/wallets/tx-history.ts index 32844970be..11031e5838 100644 --- a/core/api/src/domain/wallets/tx-history.ts +++ b/core/api/src/domain/wallets/tx-history.ts @@ -14,15 +14,12 @@ import { AdminLedgerTransactionType, LedgerTransactionType } from "@/domain/ledg const translateLedgerTxnToWalletTxn = ({ txn, nonEndUserWalletIds, - memoSharingConfig, }: { txn: LedgerTransaction nonEndUserWalletIds: WalletId[] - memoSharingConfig: MemoSharingConfig }): WalletTransaction => { const { type, - credit, currency, satsAmount: satsAmountRaw, satsFee: satsFeeRaw, @@ -65,12 +62,9 @@ const translateLedgerTxnToWalletTxn = ({ const memo = translateMemo({ memoFromPayer, lnMemo, - credit, - currency, walletId, nonEndUserWalletIds, journalId, - memoSharingConfig, }) const baseTransaction: BaseWalletTransaction = { @@ -205,26 +199,6 @@ const translateLedgerTxnToWalletTxn = ({ return walletTransaction } -const shouldDisplayMemo = ({ - memo, - credit, - currency, - memoSharingConfig, -}: { - memo: string | undefined - credit: CurrencyBaseAmount - currency: WalletCurrency - memoSharingConfig: MemoSharingConfig -}) => { - if ((!!memo && memoSharingConfig.authorizedMemos.includes(memo)) || credit === 0) - return true - - if (currency === WalletCurrency.Btc) - return credit >= memoSharingConfig.memoSharingSatsThreshold - - return credit >= memoSharingConfig.memoSharingCentsThreshold -} - const statusFromTxn = (txn: LedgerTransaction): TxStatus => { switch (txn.lnPaymentState) { case undefined: @@ -251,32 +225,22 @@ const statusFromTxn = (txn: LedgerTransaction): TxStatus => { export const translateMemo = ({ memoFromPayer, lnMemo, - credit, - currency, walletId, nonEndUserWalletIds, journalId, - memoSharingConfig, }: { memoFromPayer?: string lnMemo?: string - credit: CurrencyBaseAmount - currency: WalletCurrency walletId: WalletId | undefined nonEndUserWalletIds: WalletId[] journalId: LedgerJournalId - memoSharingConfig: MemoSharingConfig }): string | null => { if (walletId && nonEndUserWalletIds.includes(walletId)) { return `JournalId:${journalId}` } const memo = memoFromPayer || lnMemo - if (shouldDisplayMemo({ memo, credit, currency, memoSharingConfig })) { - return memo || null - } - - return null + return memo || null } export const WalletTransactionHistory = { diff --git a/core/api/src/services/mongoose/schema.ts b/core/api/src/services/mongoose/schema.ts index 7ee8e71af2..d4255b404e 100644 --- a/core/api/src/services/mongoose/schema.ts +++ b/core/api/src/services/mongoose/schema.ts @@ -98,6 +98,10 @@ const walletInvoiceSchema = new Schema({ type: String, }, + description: { + type: String, + }, + externalId: { required: true, type: String, diff --git a/core/api/src/services/mongoose/schema.types.d.ts b/core/api/src/services/mongoose/schema.types.d.ts index 9199b9c0c7..1b655b0738 100644 --- a/core/api/src/services/mongoose/schema.types.d.ts +++ b/core/api/src/services/mongoose/schema.types.d.ts @@ -72,6 +72,7 @@ interface WalletInvoiceRecord { pubkey: string paid: boolean paymentRequest?: string // optional because we historically did not store it + description?: string // optional because we historically did not store it externalId: string } diff --git a/core/api/src/services/mongoose/wallet-invoices.ts b/core/api/src/services/mongoose/wallet-invoices.ts index f9d8735e5e..6ed85860a0 100644 --- a/core/api/src/services/mongoose/wallet-invoices.ts +++ b/core/api/src/services/mongoose/wallet-invoices.ts @@ -138,6 +138,7 @@ export const WalletInvoicesRepository = (): IWalletInvoicesRepository => { paid, usdAmount, externalId, + description, lnInvoice, }: WalletInvoicesPersistNewArgs): Promise => { try { @@ -152,6 +153,7 @@ export const WalletInvoicesRepository = (): IWalletInvoicesRepository => { cents: usdAmount ? Number(usdAmount.amount) : undefined, currency: recipientWalletDescriptor.currency, paymentRequest: lnInvoice.paymentRequest, + ...(description !== undefined ? { description } : {}), externalId, }).save() return ensureWalletInvoiceHasLnInvoice(walletInvoiceFromRaw(walletInvoice)) @@ -307,6 +309,7 @@ const walletInvoiceFromRaw = ( createdAt: new Date(result.timestamp.getTime()), processingCompleted: result.processingCompleted, externalId: result.externalId as LedgerExternalId, + ...(result.description !== undefined ? { description: result.description } : {}), lnInvoice, } } diff --git a/core/api/test/helpers/wallet-invoices.ts b/core/api/test/helpers/wallet-invoices.ts index 5035a14286..e8f0024f22 100644 --- a/core/api/test/helpers/wallet-invoices.ts +++ b/core/api/test/helpers/wallet-invoices.ts @@ -25,5 +25,6 @@ export const createMockWalletInvoice = ( createdAt: new Date(), processingCompleted: false, externalId, + description: "mock invoice description", } } diff --git a/core/api/test/integration/app/wallets/send-lightning.spec.ts b/core/api/test/integration/app/wallets/send-lightning.spec.ts index f211908fb3..3606dd36bd 100644 --- a/core/api/test/integration/app/wallets/send-lightning.spec.ts +++ b/core/api/test/integration/app/wallets/send-lightning.spec.ts @@ -1092,6 +1092,7 @@ describe("initiated via lightning", () => { const externalId = randomLedgerExternalId() if (externalId instanceof Error) throw externalId + const invoiceDescription = `lnurl comment ${randomUUID()}` // Persist invoice as self-invoice const persisted = await WalletInvoicesRepository().persistNew({ @@ -1104,6 +1105,7 @@ describe("initiated via lightning", () => { lnInvoice, processingCompleted: false, externalId, + description: invoiceDescription, }) if (persisted instanceof Error) throw persisted @@ -1131,6 +1133,8 @@ describe("initiated via lightning", () => { expect(lnIntraledgerLedgerMetadataSpy).toHaveBeenCalledTimes(1) const args = recordIntraledgerSpy.mock.calls[0][0] expect(args.metadata.type).toBe(LedgerTransactionType.LnIntraLedger) + expect(args.description).toBe(invoiceDescription) + expect(args.description).not.toBe(memo) }) }) }) diff --git a/core/api/test/unit/domain/wallet-invoices/wallet-invoice-builder.spec.ts b/core/api/test/unit/domain/wallet-invoices/wallet-invoice-builder.spec.ts index 133ade3992..a66618477a 100644 --- a/core/api/test/unit/domain/wallet-invoices/wallet-invoice-builder.spec.ts +++ b/core/api/test/unit/domain/wallet-invoices/wallet-invoice-builder.spec.ts @@ -99,7 +99,8 @@ describe("WalletInvoiceBuilder", () => { const WIBWithDescription = WIBWithExternalId.withDescription({ description: testDescription, }) - const checkDescription = ({ lnInvoice }: WalletInvoice) => { + const checkDescription = ({ description, lnInvoice }: WalletInvoice) => { + expect(description).toEqual(testDescription) expect(lnInvoice.description).toEqual(testDescription) } const WIBWithDescriptionAndNoExternalId = WIBWithNoExternalId.withDescription({ diff --git a/core/api/test/unit/domain/wallets/tx-history.spec.ts b/core/api/test/unit/domain/wallets/tx-history.spec.ts index 6ad43eec0b..ab51ca553b 100644 --- a/core/api/test/unit/domain/wallets/tx-history.spec.ts +++ b/core/api/test/unit/domain/wallets/tx-history.spec.ts @@ -8,11 +8,6 @@ import { WalletTransactionHistory, } from "@/domain/wallets/tx-history" import { toSats } from "@/domain/bitcoin" -import { - memoSharingConfig, - MEMO_SHARING_CENTS_THRESHOLD, - MEMO_SHARING_SATS_THRESHOLD, -} from "@/config" import { WalletCurrency } from "@/domain/shared" import { UsdDisplayCurrency, priceAmountFromNumber, toCents } from "@/domain/fiat" import { LnPaymentState } from "@/domain/ledger/ln-payment-state" @@ -243,7 +238,6 @@ describe("translates ledger txs to wallet txs", () => { const result = WalletTransactionHistory.fromLedger({ txn: ledgerTransactions[i], nonEndUserWalletIds: [], - memoSharingConfig, }) expect(result).toEqual(expected[i]) @@ -272,7 +266,6 @@ describe("translates ledger txs to wallet txs", () => { const result = WalletTransactionHistory.fromLedger({ txn: ledgerTransactions[i], nonEndUserWalletIds: [], - memoSharingConfig, }) expect(result).toEqual(expected[i]) @@ -332,7 +325,6 @@ describe("translates ledger txs to wallet txs", () => { const result = WalletTransactionHistory.fromLedger({ txn: ledgerTransactionsModified[i], nonEndUserWalletIds: [], - memoSharingConfig, }) expect(result).toEqual(expectedTransactionsModified[i]) @@ -353,12 +345,9 @@ describe("translateDescription", () => { const result = translateMemo({ memoFromPayer: "some memo", - credit: MEMO_SHARING_SATS_THRESHOLD, - currency: WalletCurrency.Btc, walletId: journalIdMemoArgs.nonEndUserWalletIds[0], journalId, nonEndUserWalletIds: journalIdMemoArgs.nonEndUserWalletIds, - memoSharingConfig, }) expect(result).toEqual(`JournalId:${journalId}`) }) @@ -366,9 +355,6 @@ describe("translateDescription", () => { it("returns the memoFromPayer for BTC wallet", () => { const result = translateMemo({ memoFromPayer: "some memo", - credit: MEMO_SHARING_SATS_THRESHOLD, - currency: WalletCurrency.Btc, - memoSharingConfig, ...journalIdMemoArgs, }) expect(result).toEqual("some memo") @@ -377,31 +363,22 @@ describe("translateDescription", () => { it("returns memo if there is no memoFromPayer for BTC wallet", () => { const result = translateMemo({ lnMemo: "some memo", - credit: MEMO_SHARING_SATS_THRESHOLD, - currency: WalletCurrency.Btc, - memoSharingConfig, ...journalIdMemoArgs, }) expect(result).toEqual("some memo") }) - it("returns null under spam thresh for BTC wallet", () => { + it("returns memo under old spam threshold for BTC wallet", () => { const result = translateMemo({ memoFromPayer: "some memo", - credit: 1 as Satoshis, - currency: WalletCurrency.Btc, - memoSharingConfig, ...journalIdMemoArgs, }) - expect(result).toBeNull() + expect(result).toEqual("some memo") }) - it("returns memo for debit under spam threshold for BTC wallet", () => { + it("returns memo for debit under old spam threshold for BTC wallet", () => { const result = translateMemo({ memoFromPayer: "some memo", - credit: 0 as Satoshis, - currency: WalletCurrency.Btc, - memoSharingConfig, ...journalIdMemoArgs, }) expect(result).toEqual("some memo") @@ -410,9 +387,6 @@ describe("translateDescription", () => { it("returns the memoFromPayer for USD wallet", () => { const result = translateMemo({ memoFromPayer: "some memo", - credit: MEMO_SHARING_CENTS_THRESHOLD, - currency: WalletCurrency.Usd, - memoSharingConfig, ...journalIdMemoArgs, }) expect(result).toEqual("some memo") @@ -421,31 +395,22 @@ describe("translateDescription", () => { it("returns memo if there is no memoFromPayer for USD wallet", () => { const result = translateMemo({ lnMemo: "some memo", - credit: MEMO_SHARING_CENTS_THRESHOLD, - currency: WalletCurrency.Usd, - memoSharingConfig, ...journalIdMemoArgs, }) expect(result).toEqual("some memo") }) - it("returns null under spam thresh for USD wallet", () => { + it("returns memo under old spam threshold for USD wallet", () => { const result = translateMemo({ memoFromPayer: "some memo", - credit: 1 as UsdCents, - currency: WalletCurrency.Usd, - memoSharingConfig, ...journalIdMemoArgs, }) - expect(result).toBeNull() + expect(result).toEqual("some memo") }) - it("returns memo for debit under spam threshold for USD wallet", () => { + it("returns memo for debit under old spam threshold for USD wallet", () => { const result = translateMemo({ memoFromPayer: "some memo", - credit: 0 as UsdCents, - currency: WalletCurrency.Usd, - memoSharingConfig, ...journalIdMemoArgs, }) expect(result).toEqual("some memo") @@ -466,7 +431,6 @@ describe("WalletTransactionHistory.fromLedger", () => { const result = WalletTransactionHistory.fromLedger({ txn, nonEndUserWalletIds: [], - memoSharingConfig, }) expect(result.status).toEqual(TxStatus.Failure) })