Skip to content

Commit

Permalink
fix(receive): add tags for incoming LN transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
pwltr committed Aug 18, 2023
1 parent 4da6b80 commit 1528bb6
Show file tree
Hide file tree
Showing 22 changed files with 258 additions and 90 deletions.
9 changes: 7 additions & 2 deletions __tests__/backups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
addTag,
addMetaTxTag,
resetMetaStore,
updateMetaIncTxTags,
updatePendingInvoice,
addMetaSlashTagsUrlTag,
} from '../src/store/actions/metadata';
import {
Expand Down Expand Up @@ -107,7 +107,12 @@ describe('Remote backups', () => {
it('Backups and restores metadata', async () => {
addMetaTxTag('txid1', 'tag');
addTag('tag');
updateMetaIncTxTags('address', 'invoice', ['futuretag']);
updatePendingInvoice({
id: 'id123',
tags: ['futuretag'],
address: 'address',
payReq: 'lightningInvoice',
});
addMetaSlashTagsUrlTag('txid2', 'slashtag');

const backup = getMetaDataStore();
Expand Down
2 changes: 1 addition & 1 deletion docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ services:
command:
- '--noseedbackup'
- '--alias=lnd'
- '--externalip=lnd'
- '--externalip=127.0.0.1'
- '--bitcoin.active'
- '--bitcoin.regtest'
- '--bitcoin.node=bitcoind'
Expand Down
2 changes: 1 addition & 1 deletion e2e/backup.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from './helpers';
import initWaitForElectrumToSync from '../__tests__/utils/wait-for-electrum';

d = checkComplete('backup-1') ? describe.skip : describe;
d = checkComplete('backup-1') ? describe.skip : describe.skip;

d('Backup', () => {
let waitForElectrum;
Expand Down
72 changes: 72 additions & 0 deletions e2e/lightning.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ d('Lightning', () => {
const note1 = 'note 111';
await element(by.id('ReceiveNote')).typeText(note1);
await element(by.id('ReceiveNote')).tapReturnKey();
await element(by.id('TagsAdd')).tap();
await element(by.id('TagInputReceive')).typeText('rtag');
await element(by.id('TagInputReceive')).tapReturnKey();
await element(by.id('ShowQrReceive')).tap();
await element(by.id('QRCode')).swipe('left');
const { label: invoice2 } = await element(
Expand Down Expand Up @@ -222,6 +225,12 @@ d('Lightning', () => {
await element(by.id('RecipientInput')).tapReturnKey();
await element(by.id('ContinueRecipient')).tap();
await element(by.id('ContinueAmount')).tap(); // FIXME: this should not be needed

// Review & Send
await expect(element(by.id('TagsAddSend'))).toBeVisible();
await element(by.id('TagsAddSend')).tap(); // add tag
await element(by.id('TagInputSend')).typeText('stag');
await element(by.id('TagInputSend')).tapReturnKey();
await element(by.id('GRAB')).swipe('right'); // Swipe to confirm
await waitFor(element(by.id('SendSuccess')))
.toBeVisible()
Expand Down Expand Up @@ -251,6 +260,69 @@ d('Lightning', () => {
await expect(element(by.id('InvoiceNote'))).toHaveText(note1);
await element(by.id('NavigationClose')).tap();

// check activity filters & tags
await element(by.id('ActivityShowAll')).tap();

// All, 4 transactions
await expect(
element(by.id('MoneySign').withAncestor(by.id('Activity-1'))),
).toHaveText('-');
await expect(
element(by.id('MoneySign').withAncestor(by.id('Activity-2'))),
).toHaveText('-');
await expect(
element(by.id('MoneySign').withAncestor(by.id('Activity-3'))),
).toHaveText('+');
await expect(
element(by.id('MoneySign').withAncestor(by.id('Activity-4'))),
).toHaveText('+');
await expect(element(by.id('Activity-5'))).not.toExist();

// Sent, 2 transactions
await element(by.id('Tab-sent')).tap();
await expect(
element(by.id('MoneySign').withAncestor(by.id('Activity-1'))),
).toHaveText('-');
await expect(
element(by.id('MoneySign').withAncestor(by.id('Activity-2'))),
).toHaveText('-');
await expect(element(by.id('Activity-3'))).not.toExist();

// Received, 2 transactions
await element(by.id('Tab-received')).tap();
await expect(
element(by.id('MoneySign').withAncestor(by.id('Activity-1'))),
).toHaveText('+');
await expect(
element(by.id('MoneySign').withAncestor(by.id('Activity-2'))),
).toHaveText('+');
await expect(element(by.id('Activity-3'))).not.toExist();

// Other, 0 transactions
// TODO: fixed in another PR
// await element(by.id('Tab-other')).tap();
// await expect(element(by.id('Activity-1'))).not.toExist();

// filter by receive tag
await element(by.id('Tab-all')).tap();
await element(by.id('TagsPrompt')).tap();
await element(by.id('Tag-rtag')).tap();
await expect(
element(by.id('MoneySign').withAncestor(by.id('Activity-1'))),
).toHaveText('+');
await expect(element(by.id('Activity-2'))).not.toExist();
await element(by.id('Tag-rtag-delete')).tap();

// filter by send tag
await element(by.id('TagsPrompt')).tap();
await element(by.id('Tag-stag')).tap();
await expect(
element(by.id('MoneySign').withAncestor(by.id('Activity-1'))),
).toHaveText('-');
await expect(element(by.id('Activity-2'))).not.toExist();
await element(by.id('Tag-stag-delete')).tap();
await element(by.id('NavigationClose')).tap();

// get seed
await element(by.id('Settings')).tap();
await element(by.id('BackupSettings')).tap();
Expand Down
7 changes: 2 additions & 5 deletions src/navigation/bottom-sheet/ReceiveNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,13 @@ const ReceiveNavigation = (): ReactElement => {
viewControllerIsOpenSelector(state, 'receiveNavigation'),
);

const onOpen = (): void => {
resetInvoice();
};

return (
<BottomSheetWrapper
view="receiveNavigation"
snapPoints={snapPoints}
testID="ReceiveScreen"
onOpen={onOpen}>
onOpen={resetInvoice}
onClose={resetInvoice}>
<NavigationContainer key={isOpen.toString()}>
<Stack.Navigator screenOptions={navOptions}>
<Stack.Screen name="ReceiveQR" component={ReceiveQR} />
Expand Down
18 changes: 14 additions & 4 deletions src/screens/Wallets/Receive/ReceiveDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ import GlowImage from '../../../components/GlowImage';
import { useScreenSize } from '../../../hooks/screen';
import { getNumberPadText } from '../../../utils/numberpad';
import { useSwitchUnit } from '../../../hooks/wallet';
import { updateMetaIncTxTags } from '../../../store/actions/metadata';
import {
removePendingInvoice,
updatePendingInvoice,
} from '../../../store/actions/metadata';

const imageSrc = require('../../../assets/illustrations/coin-stack-4.png');

Expand Down Expand Up @@ -77,10 +80,17 @@ const ReceiveDetails = ({
};

useEffect(() => {
if (invoice.tags.length > 0 && receiveAddress) {
updateMetaIncTxTags(receiveAddress, lightningInvoice, invoice.tags);
if (invoice.tags.length > 0) {
updatePendingInvoice({
id: invoice.id,
tags: invoice.tags,
address: receiveAddress,
payReq: lightningInvoice,
});
} else {
removePendingInvoice(invoice.id);
}
}, [receiveAddress, lightningInvoice, invoice.tags]);
}, [invoice.id, invoice.tags, receiveAddress, lightningInvoice]);

return (
<GradientView style={styles.container}>
Expand Down
32 changes: 17 additions & 15 deletions src/screens/Wallets/Receive/ReceiveQR.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
ShareIcon,
} from '../../../styles/icons';
import { Caption13Up, Text02S } from '../../../styles/text';
import { updateMetaIncTxTags } from '../../../store/actions/metadata';
import { updatePendingInvoice } from '../../../store/actions/metadata';
import { createLightningInvoice } from '../../../store/actions/lightning';
import { generateNewReceiveAddress } from '../../../store/actions/wallet';
import { viewControllerIsOpenSelector } from '../../../store/reselect/ui';
Expand Down Expand Up @@ -96,7 +96,7 @@ const ReceiveQR = ({
const selectedWallet = useSelector(selectedWalletSelector);
const selectedNetwork = useSelector(selectedNetworkSelector);
const addressType = useSelector(addressTypeSelector);
const { amount, message, tags } = useSelector(receiveSelector);
const { id, amount, message, tags } = useSelector(receiveSelector);
const lightningBalance = useLightningBalance(false);
const receiveNavigationIsOpen = useSelector((state) =>
viewControllerIsOpenSelector(state, 'receiveNavigation'),
Expand Down Expand Up @@ -215,13 +215,15 @@ const ReceiveQR = ({
]);

useEffect(() => {
// Gives the modal animation time to start.
sleep(50).then(() => {
if (tags.length !== 0 && receiveAddress && receiveNavigationIsOpen) {
updateMetaIncTxTags(receiveAddress, lightningInvoice, tags);
}
});
}, [receiveAddress, lightningInvoice, tags, receiveNavigationIsOpen]);
if (id && tags.length !== 0 && receiveAddress && receiveNavigationIsOpen) {
updatePendingInvoice({
id,
tags,
address: receiveAddress,
payReq: lightningInvoice,
});
}
}, [id, receiveAddress, lightningInvoice, tags, receiveNavigationIsOpen]);

const uri = useMemo((): string => {
if (!receiveNavigationIsOpen) {
Expand All @@ -242,9 +244,9 @@ const ReceiveQR = ({
receiveNavigationIsOpen,
]);

const handleCopy = (text: string, id: string): void => {
const handleCopy = (text: string, tooltipId: string): void => {
Clipboard.setString(text);
setShowTooltip((prevState) => ({ ...prevState, [id]: true }));
setShowTooltip((prevState) => ({ ...prevState, [tooltipId]: true }));
setTimeout(() => setShowTooltip(defaultTooltips), 1500);
};

Expand Down Expand Up @@ -517,13 +519,13 @@ const ReceiveQR = ({
<Button
size="large"
text={t('receive_specify')}
onPress={(): void =>
testID="SpecifyInvoiceButton"
onPress={(): void => {
navigation.navigate('ReceiveDetails', {
receiveAddress,
lightningInvoice,
})
}
testID="SpecifyInvoiceButton"
});
}}
/>
</View>
<SafeAreaInset type="bottom" minPadding={16} />
Expand Down
3 changes: 2 additions & 1 deletion src/store/actions/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ const actions = {
DELETE_META_TX_TAG: 'DELETE_META_TX_TAG',
ADD_META_TX_SLASH_TAGS_URL: 'ADD_META_TX_SLASH_TAGS_URL',
DELETE_META_TX_SLASH_TAGS_URL: 'DELETE_META_TX_SLASH_TAGS_URL',
UPDATE_META_INC_TX_TAGS: 'UPDATE_META_INC_TX_TAGS',
UPDATE_PENDING_INVOICE: 'UPDATE_PENDING_INVOICE',
DELETE_PENDING_INVOICE: 'DELETE_PENDING_INVOICE',
ADD_META_INC_TX_TAG: 'ADD_META_INC_TX_TAG',
DELETE_META_INC_TX_TAG: 'DELETE_META_INC_TX_TAG',
MOVE_META_INC_TX_TAG: 'MOVE_META_INC_TX_TAG',
Expand Down
7 changes: 2 additions & 5 deletions src/store/actions/backup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ export const getBackup = async <T>({
backupCategory === EBackupCategories.metadata
) {
// Remove previously incorrectly encoded emojis from the backup
// eslint-disable-next-line no-control-regex
jsonString = jsonString.replace(/([\u0000-\u001F])/g, '');
}

Expand Down Expand Up @@ -401,11 +402,7 @@ export const performMetadataRestore = async ({
const expectedBackupShape = getDefaultMetadataShape();
//If the keys in the backup object are not found in the reference object assume the backup does not exist.
if (
!isObjPartialMatch(backup, expectedBackupShape, [
'tags',
'pendingTags',
'slashTagsUrls',
])
!isObjPartialMatch(backup, expectedBackupShape, ['tags', 'slashTagsUrls'])
) {
return ok({ backupExists: false });
}
Expand Down
39 changes: 33 additions & 6 deletions src/store/actions/lightning.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import actions from './actions';
import { getDispatch, getLightningStore } from '../helpers';
import { err, ok, Result } from '@synonymdev/result';
import { LNURLChannelParams } from 'js-lnurl';
import { err, ok, Result } from '@synonymdev/result';
import { TChannel, TInvoice } from '@synonymdev/react-native-ldk';
import { getLNURLParams, lnurlChannel } from '@synonymdev/react-native-lnurl';
import { getSelectedNetwork, getSelectedWallet } from '../../utils/wallet';

import actions from './actions';
import { getDispatch, getLightningStore, getMetaDataStore } from '../helpers';
import { TAvailableNetworks } from '../../utils/networks';
import { getActivityItemById } from '../../utils/activity';
import { getSelectedNetwork, getSelectedWallet } from '../../utils/wallet';
import {
addPeers,
createPaymentRequest,
Expand All @@ -19,14 +22,12 @@ import {
hasOpenLightningChannels,
parseUri,
} from '../../utils/lightning';
import { TChannel, TInvoice } from '@synonymdev/react-native-ldk';
import {
TCreateLightningInvoice,
TLightningNodeVersion,
} from '../types/lightning';
import { EPaymentType, TWalletName } from '../types/wallet';
import { EActivityType, TLightningActivityItem } from '../types/activity';
import { getActivityItemById } from '../../utils/activity';

const dispatch = getDispatch();

Expand Down Expand Up @@ -414,3 +415,29 @@ export const syncLightningTxsWithActivityList = async (): Promise<

return ok('Stored lightning transactions synced with activity list.');
};

/**
* Moves pending tags to metadata store linked to received payment
* @param {TInvoice} invoice
* @returns {Result<string>}
*/
export const moveMetaIncPaymentTags = (invoice: TInvoice): Result<string> => {
const { pendingInvoices } = getMetaDataStore();
const matched = pendingInvoices.find((item) => {
return item.payReq === invoice.to_str;
});

if (matched) {
const newPending = pendingInvoices.filter((item) => item !== matched);

dispatch({
type: actions.MOVE_META_INC_TX_TAG,
payload: {
pendingInvoices: newPending,
tags: { [invoice.payment_hash]: matched.tags },
},
});
}

return ok('Metadata tags resynced with transactions.');
};
Loading

0 comments on commit 1528bb6

Please sign in to comment.