Skip to content

Commit 26ee85f

Browse files
committed
Merge branch 'master' into release-v1.0.4
2 parents 6404b07 + 6115ca4 commit 26ee85f

File tree

25 files changed

+339
-218
lines changed

25 files changed

+339
-218
lines changed

__tests__/reselect.ts

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import '../src/utils/i18n';
55
import store, { RootState } from '../src/store';
66
import { dispatch } from '../src/store/helpers';
77
import { updateWallet } from '../src/store/slices/wallet';
8-
import { TBalance, balanceSelector } from '../src/store/reselect/aggregations';
8+
import {
9+
TBalance,
10+
balanceSelector,
11+
transferLimitsSelector,
12+
} from '../src/store/reselect/aggregations';
913
import {
1014
EChannelClosureReason,
1115
EChannelStatus,
@@ -117,4 +121,76 @@ describe('Reselect', () => {
117121
assert.deepEqual(balanceSelector(state), balance);
118122
});
119123
});
124+
125+
describe('transferLimitsSelector', () => {
126+
it('should calculate limits without LN channels', () => {
127+
// max value is limited by maxChannelSize / 2
128+
const s1 = cloneDeep(s);
129+
s1.wallet.wallets.wallet0.balance.bitcoinRegtest = 1000;
130+
s1.blocktank.info.options = {
131+
...s1.blocktank.info.options,
132+
minChannelSizeSat: 10,
133+
maxChannelSizeSat: 200,
134+
maxClientBalanceSat: 100,
135+
};
136+
137+
const received1 = transferLimitsSelector(s1);
138+
const expected1 = {
139+
minChannelSize: 11,
140+
maxChannelSize: 180,
141+
maxClientBalance: 90,
142+
};
143+
144+
expect(received1).toMatchObject(expected1);
145+
146+
// max value is limited by onchain balance
147+
const s2 = cloneDeep(s);
148+
s2.wallet.wallets.wallet0.balance.bitcoinRegtest = 50;
149+
s2.blocktank.info.options = {
150+
...s2.blocktank.info.options,
151+
minChannelSizeSat: 10,
152+
maxChannelSizeSat: 200,
153+
maxClientBalanceSat: 100,
154+
};
155+
156+
const received2 = transferLimitsSelector(s2);
157+
const expected2 = {
158+
minChannelSize: 11,
159+
maxChannelSize: 180,
160+
maxClientBalance: 40,
161+
};
162+
163+
expect(received2).toMatchObject(expected2);
164+
});
165+
166+
it('should calculate limits with existing LN channels', () => {
167+
// max value is limited by leftover node capacity
168+
const s1 = cloneDeep(s);
169+
s1.wallet.wallets.wallet0.balance.bitcoinRegtest = 1000;
170+
s1.blocktank.info.options = {
171+
...s1.blocktank.info.options,
172+
minChannelSizeSat: 10,
173+
maxChannelSizeSat: 200,
174+
};
175+
const channel1 = {
176+
channel_id: 'channel1',
177+
status: EChannelStatus.open,
178+
is_channel_ready: true,
179+
outbound_capacity_sat: 1,
180+
balance_sat: 2,
181+
channel_value_satoshis: 100,
182+
} as TChannel;
183+
const lnWallet = s1.lightning.nodes.wallet0;
184+
lnWallet.channels.bitcoinRegtest = { channel1 };
185+
186+
const received1 = transferLimitsSelector(s1);
187+
const expected1 = {
188+
minChannelSize: 11,
189+
maxChannelSize: 100,
190+
maxClientBalance: 50,
191+
};
192+
193+
expect(received1).toMatchObject(expected1);
194+
});
195+
});
120196
});

e2e/boost.e2e.js

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,6 @@ d('Boost', () => {
5252
return;
5353
}
5454

55-
// switch off RBF mode
56-
await element(by.id('Settings')).tap();
57-
if (!__DEV__) {
58-
await element(by.id('DevOptions')).multiTap(5); // enable dev mode
59-
}
60-
await element(by.id('DevSettings')).tap();
61-
await element(by.id('RBF')).tap();
62-
await launchAndWait();
63-
6455
// fund the wallet
6556
await element(by.id('Receive')).tap();
6657
let { label: wAddress } = await element(by.id('QRCode')).getAttributes();
@@ -85,10 +76,11 @@ d('Boost', () => {
8576
await sleep(500); // wait for keyboard to hide
8677
await element(by.id('AddressContinue')).tap();
8778
await element(by.id('N1').withAncestor(by.id('SendAmountNumberPad'))).tap();
88-
await element(by.id('N0').withAncestor(by.id('SendAmountNumberPad'))).tap();
89-
await element(
90-
by.id('N000').withAncestor(by.id('SendAmountNumberPad')),
91-
).tap();
79+
for (let i = 0; i < 4; i++) {
80+
await element(
81+
by.id('N0').withAncestor(by.id('SendAmountNumberPad')),
82+
).tap();
83+
}
9284
await expect(element(by.text('10 000'))).toBeVisible();
9385
await element(by.id('ContinueAmount')).tap();
9486
await element(by.id('GRAB')).swipe('right', 'slow', 0.95, 0.5, 0.5); // Swipe to confirm
@@ -172,6 +164,15 @@ d('Boost', () => {
172164
return;
173165
}
174166

167+
// switch to RBF mode
168+
await element(by.id('Settings')).tap();
169+
if (!__DEV__) {
170+
await element(by.id('DevOptions')).multiTap(5); // enable dev mode
171+
}
172+
await element(by.id('DevSettings')).tap();
173+
await element(by.id('RBF')).tap();
174+
await launchAndWait();
175+
175176
// fund the wallet
176177
await element(by.id('Receive')).tap();
177178
let { label: wAddress } = await element(by.id('QRCode')).getAttributes();
@@ -196,10 +197,11 @@ d('Boost', () => {
196197
await sleep(500); // wait for keyboard to hide
197198
await element(by.id('AddressContinue')).tap();
198199
await element(by.id('N1').withAncestor(by.id('SendAmountNumberPad'))).tap();
199-
await element(by.id('N0').withAncestor(by.id('SendAmountNumberPad'))).tap();
200-
await element(
201-
by.id('N000').withAncestor(by.id('SendAmountNumberPad')),
202-
).tap();
200+
for (let i = 0; i < 4; i++) {
201+
await element(
202+
by.id('N0').withAncestor(by.id('SendAmountNumberPad')),
203+
).tap();
204+
}
203205
await expect(element(by.text('10 000'))).toBeVisible();
204206
await element(by.id('ContinueAmount')).tap();
205207
await element(by.id('GRAB')).swipe('right', 'slow', 0.95, 0.5, 0.5); // Swipe to confirm

e2e/channels.e2e.js

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ d('LN Channel Onboarding', () => {
5353
// Advanced
5454
// - can change amount
5555

56-
it('Can buy a channel via the SpendingAmount and CustomSetup', async () => {
56+
it('Can buy a channel with default and custom receive capacity', async () => {
5757
if (checkComplete('channels-1')) {
5858
return;
5959
}
@@ -99,7 +99,7 @@ d('LN Channel Onboarding', () => {
9999
await element(by.id('NavigationBack')).tap();
100100
await element(by.id('SpendingIntro-button')).tap();
101101

102-
// NumberPad
102+
// can change amount
103103
await element(by.id('N2').withAncestor(by.id('SpendingAmount'))).tap();
104104
await element(by.id('N0').withAncestor(by.id('SpendingAmount'))).multiTap(
105105
5,
@@ -116,7 +116,7 @@ d('LN Channel Onboarding', () => {
116116
.toBeVisible()
117117
.withTimeout(10000);
118118

119-
// Advanced
119+
// Get another channel with custom receiving capacity
120120
await element(by.id('NavigationClose')).tap();
121121
await element(by.id('ActivitySavings')).tap();
122122
await element(by.id('TransferToSpending')).tap();
@@ -130,23 +130,42 @@ d('LN Channel Onboarding', () => {
130130
await element(by.id('SpendingConfirmAdvanced')).tap();
131131

132132
// Receiving Capacity
133-
await element(by.id('SpendingAdvancedDefault')).tap();
134-
await expect(element(by.text('100 000'))).toBeVisible();
133+
// can continue with min amount
135134
await element(by.id('SpendingAdvancedMin')).tap();
136-
await expect(element(by.text('2 000'))).toBeVisible();
137-
await element(
138-
by.id('NRemove').withAncestor(by.id('SpendingAdvanced')),
139-
).multiTap(5);
135+
await expect(element(by.text('110 000'))).toBeVisible();
136+
await element(by.id('SpendingAdvancedContinue')).tap();
137+
await element(by.id('SpendingConfirmDefault')).tap();
138+
await element(by.id('SpendingConfirmAdvanced')).tap();
139+
140+
// can continue with default amount
141+
await element(by.id('SpendingAdvancedDefault')).tap();
142+
await element(by.id('SpendingAdvancedContinue')).tap();
143+
await element(by.id('SpendingConfirmDefault')).tap();
144+
await element(by.id('SpendingConfirmAdvanced')).tap();
145+
146+
// can continue with max amount
147+
await element(by.id('SpendingAdvancedMax')).tap();
148+
await element(by.id('SpendingAdvancedContinue')).tap();
149+
await element(by.id('SpendingConfirmDefault')).tap();
150+
await element(by.id('SpendingConfirmAdvanced')).tap();
151+
152+
// can set custom amount
140153
await element(by.id('N1').withAncestor(by.id('SpendingAdvanced'))).tap();
141154
await element(by.id('N5').withAncestor(by.id('SpendingAdvanced'))).tap();
142155
await element(
143156
by.id('N0').withAncestor(by.id('SpendingAdvanced')),
144157
).multiTap(4);
145158
await element(by.id('SpendingAdvancedContinue')).tap();
146-
await element(by.id('SpendingAdvancedAmount')).tap();
147-
await expect(element(by.text('100 000'))).toBeVisible();
148-
// Receiving capacity minus reserve
149-
await expect(element(by.text('147 500'))).toBeVisible();
159+
await expect(
160+
element(
161+
by.text('100 000').withAncestor(by.id('SpendingConfirmChannel')),
162+
),
163+
).toBeVisible();
164+
await expect(
165+
element(
166+
by.text('150 000').withAncestor(by.id('SpendingConfirmChannel')),
167+
),
168+
).toBeVisible();
150169

151170
// Swipe to confirm (set x offset to avoid navigating back)
152171
await element(by.id('GRAB')).swipe('right', 'slow', 0.95, 0.5, 0.5);

src/assets/tos.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ const TOS = (): ReactElement => (
213213
<P>5.3.3 Synonym may implement protective measures to ensure that any User does not breach any transactional limitations imposed thereon. The absence or ineffectiveness of any protective measures shall have no effect on the restrictions of any User pursuant to these Terms.</P>
214214
<P>5.4 <U>Closure of Blocktank Connections:</U></P>
215215
<P><I>In this section, we discuss how and when we may close Blocktank Connections. It is important to note that, whilst you may choose a minimum duration for your Blocktank Connection, we may keep Blocktank Connections open for longer than the minimum duration, at our discretion. You may request to close a Blocktank Connection as further described in this section. In certain circumstances, we may elect to close a Blocktank Connection, through cooperative or force close, before the Connection Duration expires. This section also sets out your and our rights and obligations, after a Blocktank Connection is closed.</I></P>
216-
<P>5.4.1 Upon purchasing a Blocktank Connection, you shall be required to confirm how long you wish the Blocktank Connection to be open (the Connection Duration). The minimum configurable Connection Duration shall be four weeks and maximum configurable Connection Duration shall be 12 weeks, subject to the rights of Synonym to extend or reduce such maximum or minimum Connection Duration at any time, without notice to you and to otherwise close a Blocktank Connection pursuant to these Terms. Irrespective of any Connection Duration, Synonym may opt, in its sole discretion, to extend or reduce the duration of any Blocktank Connection in accordance with these Terms.</P>
216+
<P>5.4.1 Upon purchasing a Blocktank Connection, you shall be required to confirm how long you wish the Blocktank Connection to be open (the Connection Duration). The configurable Connection Duration shall be 6 weeks, subject to the rights of Synonym to extend or reduce such maximum or minimum Connection Duration at any time, without notice to you and to otherwise close a Blocktank Connection pursuant to these Terms. Irrespective of any Connection Duration, Synonym may opt, in its sole discretion, to extend or reduce the duration of any Blocktank Connection in accordance with these Terms.</P>
217217
<P>5.4.2 Notwithstanding the Connection Duration, you may request to close your Blocktank Connection at any time by utilising your preferred software to request a cooperative closure (a <B>“Closure Request”</B>). Synonym has no obligation to respond to, or cooperate with, any Closure Request. In the event that Synonym does not cooperate with any Closure Request, you may initiate a forced closure of a Blocktank Connection by submitting the latest state onto the Bitcoin on-chain network (a <B>“Force Close”</B>). Upon any request for a Force Close, you may be required to wait for a period of time (being not less than two weeks) before receiving any BTC standing to your credit in the Blocktank Connection. Synonym and its Associates shall have no liability or responsibility for any Losses directly or indirectly arising out of or related to a Force Close and any delays experienced by a User in receiving BTC as a result of any Force Close (including any fluctuations in the price of BTC during such time).</P>
218218
<P>5.4.3 Notwithstanding the Connection Duration, Synonym retains the right to close a Blocktank Connection prior to the Connection Duration, through cooperative or Force Close, at its discretion. Synonym may utilise this right: (i) with your written consent (which may take the form of you providing a digital signature to authorise a cooperative settlement transaction); (ii) if Synonym determines or suspects that you have violated, breached, or acted inconsistent with any of these Terms or exposed Synonym or its Associates to civil, criminal, or administrative penalties or to Economic Sanctions or other restrictive trade measures or Losses pursuant to applicable Laws, or in connection with an investigation regarding any of the foregoing; (iii) if Synonym determines or suspects that any representations or warranties provided by you pursuant to these Terms are incorrect or later cease to be correct; (iv) as required under applicable Laws or pursuant to a request or demand by any Government; (v) if your Blocktank Connection has been inactive for fourteen or more days; (vi) to perform software upgrades that are incompatible with your Blocktank Connection as a result of any failure, malfunction, error, data loss or technical issue encountered by your Blocktank Connection, the Bitkit Wallet, or the Lightning Network or otherwise, or (vii) if otherwise permitted pursuant to these Terms.</P>
219219
<P>5.4.4 Following expiry of the Connection Duration, Synonym has the right, but not the obligation, to close a Blocktank Connection, through cooperative or Force Close, at its discretion. Synonym retains the right to keep a Blocktank Connection open for an indefinite period, at its sole discretion. In order to request closure of a Blocktank Connection, you may follow the procedure set out in Section 5.4.2.</P>

src/components/LightningChannel.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ const LightningChannel = ({
1818
status = 'pending',
1919
showLabels = false,
2020
style,
21+
testID,
2122
}: {
2223
capacity: number;
2324
localBalance: number;
2425
remoteBalance: number;
2526
status?: TStatus;
2627
showLabels?: boolean;
2728
style?: StyleProp<ViewStyle>;
29+
testID?: string;
2830
}): ReactElement => {
2931
const { t } = useTranslation('lightning');
3032

@@ -49,7 +51,9 @@ const LightningChannel = ({
4951
};
5052

5153
return (
52-
<View style={[status === 'pending' && styles.pending, style]}>
54+
<View
55+
style={[status === 'pending' && styles.pending, style]}
56+
testID={testID}>
5357
{showLabels && (
5458
<View style={styles.labels}>
5559
<Caption13Up color="secondary">{t('spending_label')}</Caption13Up>

src/components/OnboardingScreen.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const OnboardingScreen = ({
1616
buttonText,
1717
displayBackButton = true,
1818
disableNav = false,
19+
mirrorImage = false,
1920
testID,
2021
onClosePress,
2122
onButtonPress,
@@ -28,6 +29,7 @@ const OnboardingScreen = ({
2829
buttonText: string;
2930
displayBackButton?: boolean;
3031
disableNav?: boolean;
32+
mirrorImage?: boolean;
3133
testID?: string;
3234
onClosePress?: () => void;
3335
onButtonPress: () => void;
@@ -51,6 +53,7 @@ const OnboardingScreen = ({
5153
styles.imageContainer,
5254
// eslint-disable-next-line react-native/no-inline-styles
5355
{ marginBottom: imagePosition === 'center' ? 'auto' : 48 },
56+
mirrorImage ? { transform: [{ rotateY: '180deg' }] } : {},
5457
]}>
5558
<Image style={styles.image} source={image} />
5659
</View>

src/components/SwipeToConfirm.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ const SwipeToConfirm = ({
5555
const loadingOpacity = useSharedValue(loading ? 1 : 0);
5656

5757
const panGesture = Gesture.Pan()
58+
.enabled(!loading && !confirmed) // disable so you can't swipe back
5859
.onStart(() => {
5960
prevPanX.value = panX.value;
6061
})
@@ -86,14 +87,28 @@ const SwipeToConfirm = ({
8687
});
8788

8889
const startIconOpacityStyle = useAnimatedStyle(() => {
89-
const opacity = 1 - panX.value / (maxPanX / 2);
90+
let opacity = 1 - panX.value / (maxPanX / 2) - loadingOpacity.value;
91+
92+
// FIXME: for some reason, the opacity is sometimes ~500
93+
// it happens when maxPanX is 1, so we check for that
94+
if (opacity > 2) {
95+
opacity = 0;
96+
}
97+
9098
return { opacity };
9199
});
92100

93101
// hide if loading is visible
94102
const endIconOpacityStyle = useAnimatedStyle(() => {
95-
const opacity =
103+
let opacity =
96104
(panX.value - maxPanX / 2) / (maxPanX / 2) - loadingOpacity.value;
105+
106+
// FIXME: for some reason, the opacity is sometimes ~500
107+
// it happens when maxPanX is 1, so we check for that
108+
if (opacity > 2) {
109+
opacity = 0;
110+
}
111+
97112
return { opacity };
98113
});
99114

@@ -103,7 +118,7 @@ const SwipeToConfirm = ({
103118

104119
useEffect(() => {
105120
loadingOpacity.value = withTiming(loading ? 1 : 0, {
106-
duration: 300,
121+
duration: 500,
107122
});
108123
}, [loading, loadingOpacity]);
109124

@@ -183,6 +198,7 @@ const styles = StyleSheet.create({
183198
borderRadius: CIRCLE_SIZE,
184199
},
185200
icon: {
201+
opacity: 0,
186202
position: 'absolute',
187203
left: 0,
188204
top: 0,

src/components/TabBar.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import { receiveIcon, sendIcon } from '../assets/icons/tabs';
1414
import { toggleBottomSheet } from '../store/utils/ui';
1515
import { resetSendTransaction } from '../store/actions/wallet';
1616
import { viewControllersSelector } from '../store/reselect/ui';
17+
import { spendingOnboardingSelector } from '../store/reselect/aggregations';
1718
import useColors from '../hooks/colors';
18-
import { useBalance } from '../hooks/wallet';
1919
import { useAppSelector } from '../hooks/redux';
2020
import { ScanIcon } from '../styles/icons';
2121
import { AnimatedView } from '../styles/components';
@@ -30,9 +30,9 @@ const TabBar = ({
3030
}): ReactElement => {
3131
const { white10 } = useColors();
3232
const insets = useSafeAreaInsets();
33-
const { lightningBalance } = useBalance();
3433
const { t } = useTranslation('wallet');
3534
const viewControllers = useAppSelector(viewControllersSelector);
35+
const isSpendingOnboarding = useAppSelector(spendingOnboardingSelector);
3636

3737
const shouldHide = useMemo(() => {
3838
const activityFilterSheets = ['timeRangePrompt', 'tagsPrompt'];
@@ -42,7 +42,8 @@ const TabBar = ({
4242
const onReceivePress = (): void => {
4343
const currentRoute = rootNavigation.getCurrenRoute();
4444

45-
if (currentRoute === 'ActivitySpending' && lightningBalance === 0) {
45+
// if we are on the spending screen and the user has not yet received funds
46+
if (currentRoute === 'ActivitySpending' && isSpendingOnboarding) {
4647
toggleBottomSheet('receiveNavigation', {
4748
receiveScreen: 'ReceiveAmount',
4849
});

0 commit comments

Comments
 (0)