diff --git a/e2e/lnurl.e2e.js b/e2e/lnurl.e2e.js
index 1f852e719..ae2680664 100644
--- a/e2e/lnurl.e2e.js
+++ b/e2e/lnurl.e2e.js
@@ -199,6 +199,11 @@ d('LNURL', () => {
.toBeVisible()
.withTimeout(10000);
await element(by.id('Close')).tap();
+ // check if comment is displayed
+ await element(by.id('WalletsScrollView')).scrollTo('bottom', NaN, 0.85);
+ await element(by.id('ActivityShort-1')).tap();
+ await expect(element(by.id('InvoiceComment'))).toHaveText('test comment');
+ await element(by.id('NavigationClose')).tap();
// test lnurl-pay, with min == max amount, no comment
const payRequest2 = await lnurl.generateNewUrl('payRequest', {
diff --git a/src/screens/Activity/ActivityDetail.tsx b/src/screens/Activity/ActivityDetail.tsx
index df50eb531..2b5e79d21 100644
--- a/src/screens/Activity/ActivityDetail.tsx
+++ b/src/screens/Activity/ActivityDetail.tsx
@@ -72,7 +72,12 @@ import {
} from '../../store/slices/metadata';
import { getTransactions } from '../../utils/wallet/electrum';
import { ITransaction, ITxHash } from '../../utils/wallet';
-import { ellipsis, getDurationForBlocks, openURL } from '../../utils/helpers';
+import {
+ ellipsis,
+ getDurationForBlocks,
+ openURL,
+ vibrate,
+} from '../../utils/helpers';
import { getBoostedTransactionParents } from '../../utils/boost';
import { showToast } from '../../utils/notifications';
import {
@@ -81,6 +86,7 @@ import {
transferSelector,
} from '../../store/reselect/wallet';
import {
+ commentSelector,
slashTagsUrlSelector,
tagSelector,
} from '../../store/reselect/metadata';
@@ -684,6 +690,7 @@ const LightningActivityDetail = ({
const dispatch = useAppDispatch();
const tags = useAppSelector((state) => tagSelector(state, id));
+ const comment = useAppSelector((state) => commentSelector(state, id));
const slashTagsUrl = useAppSelector((state) => {
return slashTagsUrlSelector(state, id);
});
@@ -717,6 +724,7 @@ const LightningActivityDetail = ({
const onCopy = (text: string): void => {
Clipboard.setString(text);
+ vibrate();
showToast({
type: 'success',
title: t('copied'),
@@ -910,7 +918,9 @@ const LightningActivityDetail = ({
)}
{message ? (
-
+ onCopy(message)}>
{t('activity_invoice_note')}
@@ -922,7 +932,25 @@ const LightningActivityDetail = ({
{message}
-
+
+ ) : null}
+
+ {comment ? (
+ onCopy(comment)}>
+
+ {t('activity_invoice_comment')}
+
+
+
+
+ {comment}
+
+
+
) : null}
diff --git a/src/screens/Wallets/LNURLPay/Confirm.tsx b/src/screens/Wallets/LNURLPay/Confirm.tsx
index 720c95ac2..8c3a190c0 100644
--- a/src/screens/Wallets/LNURLPay/Confirm.tsx
+++ b/src/screens/Wallets/LNURLPay/Confirm.tsx
@@ -26,6 +26,7 @@ import {
settingsSelector,
} from '../../../store/reselect/settings';
import { addPendingPayment } from '../../../store/slices/lightning';
+import { updateMetaTxComment } from '../../../store/slices/metadata';
import { EActivityType } from '../../../store/types/activity';
import { AnimatedView, BottomSheetTextInput } from '../../../styles/components';
import { Checkmark, LightningHollow } from '../../../styles/icons';
@@ -111,6 +112,15 @@ const LNURLConfirm = ({
setIsLoading(false);
+ if (comment) {
+ dispatch(
+ updateMetaTxComment({
+ txId: decodedInvoice.payment_hash,
+ comment,
+ }),
+ );
+ }
+
if (payInvoiceResponse.isErr()) {
const errorMessage = payInvoiceResponse.error.message;
if (errorMessage === 'Timed Out.') {
diff --git a/src/store/index.ts b/src/store/index.ts
index 3209832e9..39fd4b371 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -32,7 +32,7 @@ const persistConfig = {
key: 'root',
storage: reduxStorage,
// increase version after store shape changes
- version: 46,
+ version: 47,
stateReconciler: autoMergeLevel2,
blacklist: ['receive', 'ui'],
migrate: createMigrate(migrations, { debug: __ENABLE_MIGRATION_DEBUG__ }),
diff --git a/src/store/migrations/index.ts b/src/store/migrations/index.ts
index 3f5b781fe..dd5f30988 100644
--- a/src/store/migrations/index.ts
+++ b/src/store/migrations/index.ts
@@ -45,6 +45,15 @@ const migrations = {
},
};
},
+ 47: (state): PersistedState => {
+ return {
+ ...state,
+ metadata: {
+ ...state.metadata,
+ comments: {},
+ },
+ };
+ },
};
export default migrations;
diff --git a/src/store/reselect/metadata.ts b/src/store/reselect/metadata.ts
index ce6e758df..65fb49de1 100644
--- a/src/store/reselect/metadata.ts
+++ b/src/store/reselect/metadata.ts
@@ -33,3 +33,7 @@ export const slashTagsUrlSelector = createSelector(
return metadata.slashTagsUrls[id];
},
);
+export const commentSelector = createSelector(
+ [metadataState, (_state, txId: string): string => txId],
+ (metadata, txId): string => metadata.comments[txId] ?? '',
+);
diff --git a/src/store/slices/metadata.ts b/src/store/slices/metadata.ts
index 70b93c9f3..58f7c6ffc 100644
--- a/src/store/slices/metadata.ts
+++ b/src/store/slices/metadata.ts
@@ -7,6 +7,7 @@ export const initialMetadataState: TMetadataState = {
lastUsedTags: [],
pendingInvoices: [],
slashTagsUrls: {},
+ comments: {},
};
export const metadataSlice = createSlice({
@@ -26,6 +27,16 @@ export const metadataSlice = createSlice({
state.tags[action.payload.txId] = action.payload.tags;
}
},
+ updateMetaTxComment: (
+ state,
+ action: PayloadAction<{ txId: string; comment: string }>,
+ ) => {
+ if (action.payload.comment.length === 0) {
+ delete state.comments[action.payload.txId];
+ } else {
+ state.comments[action.payload.txId] = action.payload.comment;
+ }
+ },
addMetaTxTag: (
state,
action: PayloadAction<{ txId: string; tag: string }>,
@@ -120,6 +131,7 @@ const { actions, reducer } = metadataSlice;
export const {
updateMetadata,
updateMetaTxTags,
+ updateMetaTxComment,
addMetaTxTag,
deleteMetaTxTag,
updatePendingInvoice,
diff --git a/src/store/types/metadata.ts b/src/store/types/metadata.ts
index f3ccf47bc..d5c36156f 100644
--- a/src/store/types/metadata.ts
+++ b/src/store/types/metadata.ts
@@ -1,6 +1,7 @@
export type TTags = { [txId: string]: string[] };
export type TLastUsedTags = string[];
export type TSlashTagsUrls = { [txId: string]: string | undefined };
+export type TTxComments = { [txId: string]: string };
export type TPendingInvoice = {
id: string; // uuid used to identify the invoice 'session'
@@ -16,4 +17,5 @@ export type TMetadataState = {
// Keep track of pending invoices, right now this is only used to map tags to incoming transactions
pendingInvoices: TPendingInvoice[];
slashTagsUrls: TSlashTagsUrls;
+ comments: TTxComments;
};
diff --git a/src/store/utils/backup.ts b/src/store/utils/backup.ts
index 692acc03a..fac222d98 100644
--- a/src/store/utils/backup.ts
+++ b/src/store/utils/backup.ts
@@ -363,6 +363,11 @@ const performMetadataRestore = async (): Promise<
return ok({ backupExists: false });
}
+ // apply migrations
+ if (backupRes.value.metadata.version < 47) {
+ backup.comments = {};
+ }
+
dispatch(updateMetadata({ ...expectedBackupShape, ...backup }));
dispatch(backupSuccess({ category: EBackupCategories.metadata }));
diff --git a/src/utils/i18n/locales/en/wallet.json b/src/utils/i18n/locales/en/wallet.json
index 16e36022d..851f76592 100644
--- a/src/utils/i18n/locales/en/wallet.json
+++ b/src/utils/i18n/locales/en/wallet.json
@@ -506,6 +506,9 @@
"activity_invoice_note": {
"string": "Invoice note"
},
+ "activity_invoice_comment": {
+ "string": "Comment"
+ },
"activity_invoice": {
"string": "Invoice"
},