11import * as React from 'react' ;
22import { Route } from '@react-navigation/native' ;
3- import { StyleSheet , Text , View } from 'react-native' ;
3+ import { StyleSheet , Text , View , TouchableOpacity } from 'react-native' ;
44import { StackNavigationProp } from '@react-navigation/stack' ;
55import { LNURLWithdrawParams } from 'js-lnurl' ;
66import { inject , observer } from 'mobx-react' ;
@@ -20,13 +20,27 @@ import BalanceStore from '../stores/BalanceStore';
2020import CashuStore from '../stores/CashuStore' ;
2121import UTXOsStore from '../stores/UTXOsStore' ;
2222import InvoicesStore from '../stores/InvoicesStore' ;
23+ import SwapStore from '../stores/SwapStore' ;
2324
2425import { feeStore , settingsStore } from '../stores/Stores' ;
2526
2627import { localeString } from '../utils/LocaleUtils' ;
2728import { themeColor } from '../utils/ThemeUtils' ;
2829import BackendUtils from '../utils/BackendUtils' ;
30+ import { calculateLimit } from '../utils/SwapUtils' ;
2931import 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
3145interface RouteParams {
3246 value : string ;
@@ -44,6 +58,7 @@ interface ChoosePaymentMethodProps {
4458 CashuStore ?: CashuStore ;
4559 UTXOsStore ?: UTXOsStore ;
4660 InvoicesStore ?: InvoicesStore ;
61+ SwapStore ?: SwapStore ;
4762}
4863
4964interface 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
6183export 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
0 commit comments