Skip to content

Commit 22fb5f3

Browse files
committed
feat: Choose Payment Method: highlight ability to do swaps
1 parent 1eb78f2 commit 22fb5f3

2 files changed

Lines changed: 126 additions & 6 deletions

File tree

views/ChoosePaymentMethod.tsx

Lines changed: 110 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from 'react';
22
import { Route } from '@react-navigation/native';
3-
import { StyleSheet, Text, View } from 'react-native';
3+
import { StyleSheet, Text, View, TouchableOpacity } from 'react-native';
44
import { StackNavigationProp } from '@react-navigation/stack';
55
import { LNURLWithdrawParams } from 'js-lnurl';
66
import { inject, observer } from 'mobx-react';
@@ -20,13 +20,27 @@ import BalanceStore from '../stores/BalanceStore';
2020
import CashuStore from '../stores/CashuStore';
2121
import UTXOsStore from '../stores/UTXOsStore';
2222
import InvoicesStore from '../stores/InvoicesStore';
23+
import SwapStore from '../stores/SwapStore';
2324

2425
import { feeStore, settingsStore } from '../stores/Stores';
2526

2627
import { localeString } from '../utils/LocaleUtils';
2728
import { themeColor } from '../utils/ThemeUtils';
2829
import BackendUtils from '../utils/BackendUtils';
30+
import { calculateLimit } from '../utils/SwapUtils';
2931
import Invoice from '../models/Invoice';
32+
import SwapIcon from '../assets/images/SVG/Swap.svg';
33+
34+
interface SubmarineSwapInfo {
35+
fees?: {
36+
percentage?: number;
37+
minerFees?: number;
38+
};
39+
limits?: {
40+
minimal?: number;
41+
maximal?: number;
42+
};
43+
}
3044

3145
interface RouteParams {
3246
value: string;
@@ -44,6 +58,7 @@ interface ChoosePaymentMethodProps {
4458
CashuStore?: CashuStore;
4559
UTXOsStore?: UTXOsStore;
4660
InvoicesStore?: InvoicesStore;
61+
SwapStore?: SwapStore;
4762
}
4863

4964
interface ChoosePaymentMethodState {
@@ -54,9 +69,16 @@ interface ChoosePaymentMethodState {
5469
offer: string;
5570
lnurlParams: LNURLWithdrawParams | undefined;
5671
feeRate: string;
72+
validAmountToSwap: boolean;
5773
}
5874

59-
@inject('BalanceStore', 'CashuStore', 'UTXOsStore', 'InvoicesStore')
75+
@inject(
76+
'BalanceStore',
77+
'CashuStore',
78+
'UTXOsStore',
79+
'InvoicesStore',
80+
'SwapStore'
81+
)
6082
@observer
6183
export default class ChoosePaymentMethod extends React.Component<
6284
ChoosePaymentMethodProps,
@@ -73,7 +95,8 @@ export default class ChoosePaymentMethod extends React.Component<
7395
lightningAddress: '',
7496
offer: '',
7597
lnurlParams: undefined,
76-
feeRate: ''
98+
feeRate: '',
99+
validAmountToSwap: false
77100
};
78101

79102
componentDidMount() {
@@ -135,12 +158,19 @@ export default class ChoosePaymentMethod extends React.Component<
135158
...(lnurlParams && { lnurlParams })
136159
};
137160
if (Object.keys(stateUpdate).length > 0) {
138-
this.setState((prev) => ({ ...prev, ...stateUpdate }));
161+
this.setState((prev) => {
162+
const newState = { ...prev, ...stateUpdate };
163+
const validAmountToSwap = this.isAmountValidToSwap(
164+
newState.satAmount
165+
);
166+
return { ...newState, validAmountToSwap };
167+
});
139168
}
140169

141170
this.fetchFeeEstimates({ lightning, lnurlParams });
142171
this.fetchOnchainFees(value);
143172
}
173+
144174
fetchOnchainFees = (value?: string) => {
145175
if (
146176
!value ||
@@ -169,6 +199,71 @@ export default class ChoosePaymentMethod extends React.Component<
169199
await Promise.all(tasks);
170200
};
171201

202+
isAmountValidToSwap(satAmount?: string): boolean {
203+
const { SwapStore } = this.props;
204+
const amount = satAmount ?? this.state.satAmount;
205+
206+
if (!SwapStore || !amount) {
207+
return false;
208+
}
209+
210+
const subInfo: SubmarineSwapInfo = SwapStore.subInfo;
211+
212+
if (!subInfo || Object.keys(subInfo).length === 0) {
213+
return false;
214+
}
215+
216+
const serviceFeePct = subInfo.fees?.percentage ?? 0;
217+
const networkFee = Number(subInfo.fees?.minerFees ?? 0);
218+
const reverse = false; // submarine swap: OnChain -> LN
219+
220+
const min = calculateLimit(
221+
subInfo.limits?.minimal ?? 0,
222+
serviceFeePct,
223+
networkFee,
224+
reverse
225+
);
226+
const max = calculateLimit(
227+
subInfo.limits?.maximal ?? 0,
228+
serviceFeePct,
229+
networkFee,
230+
reverse
231+
);
232+
const input = calculateLimit(
233+
Number(amount) || 0,
234+
serviceFeePct,
235+
networkFee,
236+
reverse
237+
);
238+
239+
return input >= min && input <= max;
240+
}
241+
242+
navigateToSwaps = () => {
243+
const { navigation } = this.props;
244+
const { lightning, satAmount } = this.state;
245+
const amountNum = Number(satAmount);
246+
if (!lightning || !satAmount || isNaN(amountNum) || amountNum <= 0) {
247+
return;
248+
}
249+
navigation.navigate('Swaps', {
250+
initialInvoice: lightning,
251+
initialAmountSats: satAmount.toString(),
252+
initialReverse: false // OnChain -> LN for paying a LN invoice
253+
});
254+
};
255+
256+
private renderSwapIconButton = () => (
257+
<TouchableOpacity onPress={this.navigateToSwaps} style={{ padding: 8 }}>
258+
<SwapIcon
259+
fill={themeColor('text')}
260+
width="36"
261+
height="26"
262+
style={{ alignSelf: 'center' }}
263+
/>
264+
</TouchableOpacity>
265+
);
266+
172267
hasInsufficientFunds = () => {
173268
const { BalanceStore, CashuStore } = this.props;
174269
const { totalBlockchainBalance, lightningBalance } = BalanceStore!;
@@ -221,7 +316,8 @@ export default class ChoosePaymentMethod extends React.Component<
221316
lightningAddress,
222317
offer,
223318
lnurlParams,
224-
feeRate
319+
feeRate,
320+
validAmountToSwap
225321
} = this.state;
226322

227323
const { accounts } = UTXOsStore!;
@@ -245,6 +341,12 @@ export default class ChoosePaymentMethod extends React.Component<
245341
showFees &&
246342
!!settingsStore?.settings?.privacy?.enableMempoolRates;
247343

344+
const canSwap =
345+
validAmountToSwap &&
346+
lightning &&
347+
satAmount &&
348+
Number(satAmount) > 0;
349+
248350
return (
249351
<Screen>
250352
<Header
@@ -253,6 +355,9 @@ export default class ChoosePaymentMethod extends React.Component<
253355
text: localeString('views.Accounts.select'),
254356
style: { color: themeColor('text') }
255357
}}
358+
rightComponent={
359+
canSwap ? this.renderSwapIconButton() : undefined
360+
}
256361
navigation={navigation}
257362
/>
258363

views/Swaps/index.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,19 @@ import KeyIcon from '../../assets/images/SVG/Key.svg';
6969

7070
import Storage from '../../storage';
7171

72+
export interface SwapRouteParams {
73+
initialInvoice?: string;
74+
initialAmountSats?: string | number;
75+
initialReverse?: boolean;
76+
}
77+
78+
export type SwapParamList = {
79+
Swaps: SwapRouteParams | undefined;
80+
};
81+
7282
interface SwapProps {
7383
navigation: StackNavigationProp<any, 'Swaps'>;
74-
route: RouteProp<any, 'Swaps'>;
84+
route: RouteProp<SwapParamList, 'Swaps'>;
7585
SwapStore: SwapStore;
7686
UnitsStore: UnitsStore;
7787
InvoicesStore: InvoicesStore;
@@ -346,6 +356,10 @@ export default class Swap extends React.PureComponent<SwapProps, SwapState> {
346356
const { initialInvoice, initialAmountSats, initialReverse } =
347357
route.params;
348358

359+
console.log('initialInvoice', initialInvoice);
360+
console.log('initialAmountSats', initialAmountSats);
361+
console.log('initialReverse', initialReverse);
362+
349363
if (
350364
initialInvoice !== undefined &&
351365
initialAmountSats !== undefined &&
@@ -395,6 +409,7 @@ export default class Swap extends React.PureComponent<SwapProps, SwapState> {
395409
networkFee,
396410
this.state.reverse
397411
);
412+
console.log('newInputSats', newInputSats);
398413
let newInputFiat = '';
399414
if (
400415
units === 'fiat' &&

0 commit comments

Comments
 (0)