2024-03-31 20:59:14 +01:00
import AsyncStorage from '@react-native-async-storage/async-storage' ;
2024-05-28 09:33:15 -04:00
import { RouteProp , StackActions , useFocusEffect , useRoute } from '@react-navigation/native' ;
2024-03-31 20:59:14 +01:00
import BigNumber from 'bignumber.js' ;
import * as bitcoin from 'bitcoinjs-lib' ;
2024-05-31 13:18:01 -04:00
import React , { useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
2018-03-17 22:39:21 +02:00
import {
2020-06-09 15:08:18 +01:00
ActivityIndicator ,
2019-08-07 01:45:27 -04:00
Alert ,
2019-09-02 23:28:52 -04:00
Dimensions ,
2024-09-05 19:58:25 -04:00
findNodeHandle ,
2021-02-25 19:13:34 +03:00
FlatList ,
2021-03-18 22:30:01 -04:00
I18nManager ,
2021-02-25 19:13:34 +03:00
Keyboard ,
LayoutAnimation ,
2024-05-22 20:35:36 +01:00
NativeScrollEvent ,
NativeSyntheticEvent ,
2018-12-11 22:52:46 +00:00
Platform ,
2021-02-25 19:13:34 +03:00
StyleSheet ,
2018-12-23 19:44:31 -05:00
Text ,
2021-02-25 19:13:34 +03:00
TextInput ,
TouchableOpacity ,
View ,
2018-10-20 22:10:21 +01:00
} from 'react-native' ;
2024-03-31 20:59:14 +01:00
import DocumentPicker from 'react-native-document-picker' ;
2024-06-12 12:46:44 -04:00
import { Icon } from '@rneui/themed' ;
2020-12-25 19:09:53 +03:00
import RNFS from 'react-native-fs' ;
2024-03-31 20:59:14 +01:00
import { btcToSatoshi , fiatToBTC } from '../../blue_modules/currency' ;
import * as fs from '../../blue_modules/fs' ;
import triggerHapticFeedback , { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback' ;
2024-08-24 14:06:17 -04:00
import { BlueText } from '../../BlueComponents' ;
2021-02-25 19:13:34 +03:00
import { HDSegwitBech32Wallet , MultisigHDWallet , WatchOnlyWallet } from '../../class' ;
2020-04-28 15:48:36 +01:00
import DeeplinkSchemaMatch from '../../class/deeplink-schema-match' ;
2024-03-31 20:59:14 +01:00
import { AbstractHDElectrumWallet } from '../../class/wallets/abstract-hd-electrum-wallet' ;
2021-02-25 19:13:34 +03:00
import AddressInput from '../../components/AddressInput' ;
2024-03-31 20:59:14 +01:00
import presentAlert from '../../components/Alert' ;
2021-02-25 19:13:34 +03:00
import AmountInput from '../../components/AmountInput' ;
2024-08-04 17:00:50 -04:00
import { BottomModalHandle } from '../../components/BottomModal' ;
2024-03-31 20:59:14 +01:00
import Button from '../../components/Button' ;
import CoinsSelected from '../../components/CoinsSelected' ;
2024-08-24 14:06:17 -04:00
import InputAccessoryAllFunds , { InputAccessoryAllFundsAccessoryViewID } from '../../components/InputAccessoryAllFunds' ;
2023-10-23 21:28:44 -04:00
import { useTheme } from '../../components/themes' ;
2024-10-01 13:26:57 -04:00
import { scanQrHelper } from '../../helpers/scan-qr' ;
2024-03-31 20:59:14 +01:00
import loc , { formatBalance , formatBalanceWithoutSuffix } from '../../loc' ;
import { BitcoinUnit , Chain } from '../../models/bitcoinUnits' ;
import NetworkTransactionFees , { NetworkTransactionFee } from '../../models/networkTransactionFees' ;
2024-05-27 23:00:28 +01:00
import { CreateTransactionTarget , CreateTransactionUtxo , TWallet } from '../../class/wallets/types' ;
2024-05-22 20:35:36 +01:00
import { TOptions } from 'bip21' ;
import assert from 'assert' ;
import { NativeStackNavigationProp } from '@react-navigation/native-stack' ;
import { SendDetailsStackParamList } from '../../navigation/SendDetailsStackParamList' ;
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation' ;
2024-05-27 23:00:28 +01:00
import { ContactList } from '../../class/contact-list' ;
2024-05-31 13:18:01 -04:00
import { useStorage } from '../../hooks/context/useStorage' ;
2024-07-30 19:05:58 -04:00
import SelectFeeModal from '../../components/SelectFeeModal' ;
2024-08-24 12:16:38 -04:00
import { useKeyboard } from '../../hooks/useKeyboard' ;
2024-08-24 14:06:17 -04:00
import { DismissKeyboardInputAccessory , DismissKeyboardInputAccessoryViewID } from '../../components/DismissKeyboardInputAccessory' ;
2024-09-05 19:58:25 -04:00
import ActionSheet from '../ActionSheet' ;
2024-09-27 08:39:31 -04:00
import HeaderMenuButton from '../../components/HeaderMenuButton' ;
2024-10-13 03:08:52 -04:00
import { CommonToolTipActions } from '../../typings/CommonToolTipActions' ;
2024-10-23 17:22:39 -04:00
import { Action } from '../../components/types' ;
2019-02-03 04:09:46 -05:00
2024-05-22 20:35:36 +01:00
interface IPaymentDestinations {
2024-05-27 23:00:28 +01:00
address : string ; // btc address or payment code
2024-05-22 20:35:36 +01:00
amountSats? : number | string ;
amount? : string | number | 'MAX' ;
key : string ; // random id to look up this record
}
interface IFee {
current : number | null ;
slowFee : number | null ;
mediumFee : number | null ;
fastestFee : number | null ;
}
type NavigationProps = NativeStackNavigationProp < SendDetailsStackParamList , 'SendDetails' > ;
2024-05-28 09:33:15 -04:00
type RouteProps = RouteProp < SendDetailsStackParamList , 'SendDetails' > ;
2024-05-22 20:35:36 +01:00
2021-02-25 19:13:34 +03:00
const SendDetails = ( ) = > {
2024-05-31 11:52:29 -04:00
const { wallets , setSelectedWalletID , sleep , txMetadata , saveToDisk } = useStorage ( ) ;
2024-05-22 20:35:36 +01:00
const navigation = useExtendedNavigation < NavigationProps > ( ) ;
2024-06-07 14:50:50 -04:00
const setParams = navigation . setParams ;
2024-05-28 09:33:15 -04:00
const route = useRoute < RouteProps > ( ) ;
2024-05-22 20:35:36 +01:00
const name = route . name ;
2024-10-23 18:36:28 -04:00
const feeUnit = route . params ? . feeUnit ? ? BitcoinUnit . BTC ;
const amountUnit = route . params ? . amountUnit ? ? BitcoinUnit . BTC ;
const frozenBalance = route . params ? . frozenBalance ? ? 0 ;
const transactionMemo = route . params ? . transactionMemo ;
const utxos = route . params ? . utxos ;
const payjoinUrl = route . params ? . payjoinUrl ;
const isTransactionReplaceable = route . params ? . isTransactionReplaceable ;
2024-05-28 09:33:15 -04:00
const routeParams = route . params ;
2024-05-22 20:35:36 +01:00
const scrollView = useRef < FlatList < any > > ( null ) ;
2021-03-12 17:02:49 +03:00
const scrollIndex = useRef ( 0 ) ;
2021-02-25 19:13:34 +03:00
const { colors } = useTheme ( ) ;
2024-05-22 20:35:36 +01:00
const popAction = StackActions . pop ( 1 ) ;
2021-02-25 19:13:34 +03:00
// state
const [ width , setWidth ] = useState ( Dimensions . get ( 'window' ) . width ) ;
2021-06-03 08:45:49 -04:00
const [ isLoading , setIsLoading ] = useState ( false ) ;
2024-05-22 20:35:36 +01:00
const [ wallet , setWallet ] = useState < TWallet | null > ( null ) ;
2024-06-30 13:17:55 -04:00
const feeModalRef = useRef < BottomModalHandle > ( null ) ;
2024-10-23 18:36:28 -04:00
const { isVisible } = useKeyboard ( ) ;
2024-05-22 20:35:36 +01:00
const [ addresses , setAddresses ] = useState < IPaymentDestinations [ ] > ( [ ] ) ;
const [ units , setUnits ] = useState < BitcoinUnit [ ] > ( [ ] ) ;
2021-02-25 19:13:34 +03:00
const [ networkTransactionFees , setNetworkTransactionFees ] = useState ( new NetworkTransactionFee ( 3 , 2 , 1 ) ) ;
2021-03-24 20:45:07 -04:00
const [ networkTransactionFeesIsLoading , setNetworkTransactionFeesIsLoading ] = useState ( false ) ;
2024-05-22 20:35:36 +01:00
const [ customFee , setCustomFee ] = useState < string | null > ( null ) ;
const [ feePrecalc , setFeePrecalc ] = useState < IFee > ( { current : null , slowFee : null , mediumFee : null , fastestFee : null } ) ;
const [ changeAddress , setChangeAddress ] = useState < string | null > ( null ) ;
2021-02-25 19:13:34 +03:00
const [ dumb , setDumb ] = useState ( false ) ;
2023-11-11 07:33:50 -04:00
const { isEditable } = routeParams ;
2021-08-23 15:54:18 -04:00
// if utxo is limited we use it to calculate available balance
2024-10-23 18:36:28 -04:00
const balance : number = utxos ? utxos . reduce ( ( prev , curr ) = > prev + curr . value , 0 ) : ( wallet ? . getBalance ( ) ? ? 0 ) ;
2021-08-23 15:54:18 -04:00
const allBalance = formatBalanceWithoutSuffix ( balance , BitcoinUnit . BTC , true ) ;
2021-02-25 19:13:34 +03:00
// if cutomFee is not set, we need to choose highest possible fee for wallet balance
2021-09-14 14:08:15 +01:00
// if there are no funds for even Slow option, use 1 sat/vbyte fee
2021-03-19 15:29:27 +00:00
const feeRate = useMemo ( ( ) = > {
2021-02-25 19:13:34 +03:00
if ( customFee ) return customFee ;
if ( feePrecalc . slowFee === null ) return '1' ; // wait for precalculated fees
let initialFee ;
if ( feePrecalc . fastestFee !== null ) {
initialFee = String ( networkTransactionFees . fastestFee ) ;
} else if ( feePrecalc . mediumFee !== null ) {
initialFee = String ( networkTransactionFees . mediumFee ) ;
2019-02-18 02:25:27 -05:00
} else {
2021-02-25 19:13:34 +03:00
initialFee = String ( networkTransactionFees . slowFee ) ;
2019-02-18 02:25:27 -05:00
}
2021-02-25 19:13:34 +03:00
return initialFee ;
} , [ customFee , feePrecalc , networkTransactionFees ] ) ;
2024-08-04 22:13:25 -04:00
useEffect ( ( ) = > {
console . log ( 'send/details - useEffect' ) ;
if ( wallet ) {
setHeaderRightOptions ( ) ;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
} , [ colors , wallet , isTransactionReplaceable , balance , addresses , isEditable , isLoading ] ) ;
2021-08-23 15:39:52 -04:00
2021-02-25 19:13:34 +03:00
useEffect ( ( ) = > {
// decode route params
2021-08-28 00:49:46 -04:00
const currentAddress = addresses [ scrollIndex . current ] ;
2021-02-25 19:13:34 +03:00
if ( routeParams . uri ) {
2020-06-14 21:15:58 -04:00
try {
2023-07-25 14:50:04 +01:00
const { address , amount , memo , payjoinUrl : pjUrl } = DeeplinkSchemaMatch . decodeBitcoinUri ( routeParams . uri ) ;
2021-09-04 15:39:31 -04:00
2023-07-25 14:50:04 +01:00
setUnits ( u = > {
u [ scrollIndex . current ] = BitcoinUnit . BTC ; // also resetting current unit to BTC
return [ . . . u ] ;
2021-09-04 15:39:31 -04:00
} ) ;
2023-07-25 14:50:04 +01:00
setAddresses ( addrs = > {
2021-08-28 00:49:46 -04:00
if ( currentAddress ) {
currentAddress . address = address ;
if ( Number ( amount ) > 0 ) {
2024-05-22 20:35:36 +01:00
currentAddress . amount = amount ! ;
currentAddress . amountSats = btcToSatoshi ( amount ! ) ;
2021-08-28 00:49:46 -04:00
}
2023-07-25 14:50:04 +01:00
addrs [ scrollIndex . current ] = currentAddress ;
return [ . . . addrs ] ;
2021-08-28 00:49:46 -04:00
} else {
2024-05-22 20:35:36 +01:00
return [ . . . addrs , { address , amount , amountSats : btcToSatoshi ( amount ! ) , key : String ( Math . random ( ) ) } as IPaymentDestinations ] ;
2021-08-28 00:49:46 -04:00
}
} ) ;
if ( memo ? . trim ( ) . length > 0 ) {
2024-10-23 18:36:28 -04:00
setParams ( { transactionMemo : memo } ) ;
2021-08-28 00:49:46 -04:00
}
2024-10-23 18:36:28 -04:00
setParams ( { payjoinUrl : pjUrl , amountUnit : BitcoinUnit.BTC } ) ;
2020-06-14 21:15:58 -04:00
} catch ( error ) {
console . log ( error ) ;
2024-02-07 15:24:24 -04:00
presentAlert ( { title : loc.errors.error , message : loc.send.details_error_decode } ) ;
2019-09-02 23:28:52 -04:00
}
2021-02-25 19:13:34 +03:00
} else if ( routeParams . address ) {
2021-09-09 12:00:11 +01:00
const { amount , amountSats , unit = BitcoinUnit . BTC } = routeParams ;
2024-06-10 15:11:02 -04:00
// @ts-ignore: needs fix
setAddresses ( value = > {
2024-06-07 09:37:45 -04:00
if ( currentAddress && currentAddress . address && routeParams . address ) {
2021-08-28 00:49:46 -04:00
currentAddress . address = routeParams . address ;
2024-06-10 15:11:02 -04:00
value [ scrollIndex . current ] = currentAddress ;
return [ . . . value ] ;
2021-08-28 00:49:46 -04:00
} else {
2024-06-10 15:11:02 -04:00
return [ . . . value , { address : routeParams.address , key : String ( Math . random ( ) ) , amount , amountSats } ] ;
2021-08-28 00:49:46 -04:00
}
} ) ;
2023-07-25 14:50:04 +01:00
setUnits ( u = > {
u [ scrollIndex . current ] = unit ;
return [ . . . u ] ;
2021-09-09 12:00:11 +01:00
} ) ;
2024-06-10 22:41:14 -04:00
} else if ( routeParams . addRecipientParams ) {
const index = addresses . length === 0 ? 0 : scrollIndex.current ;
2024-06-28 19:11:08 -04:00
const { address , amount } = routeParams . addRecipientParams ;
setAddresses ( prevAddresses = > {
const updatedAddresses = [ . . . prevAddresses ] ;
if ( address ) {
updatedAddresses [ index ] = {
. . . updatedAddresses [ index ] ,
address ,
amount : amount ? ? updatedAddresses [ index ] . amount ,
amountSats : amount ? btcToSatoshi ( amount ) : updatedAddresses [ index ] . amountSats ,
} as IPaymentDestinations ;
}
return updatedAddresses ;
} ) ;
2024-06-28 16:00:20 -04:00
2024-06-28 19:11:08 -04:00
// @ts-ignore: Fix later
setParams ( prevParams = > ( { . . . prevParams , addRecipientParams : undefined } ) ) ;
2021-02-01 19:26:31 +03:00
} else {
2024-05-22 20:35:36 +01:00
setAddresses ( [ { address : '' , key : String ( Math . random ( ) ) } as IPaymentDestinations ] ) ; // key is for the FlatList
2021-02-01 19:26:31 +03:00
}
2024-08-04 22:13:25 -04:00
// eslint-disable-next-line react-hooks/exhaustive-deps
} , [ routeParams . uri , routeParams . address , routeParams . addRecipientParams ] ) ;
2021-08-28 00:49:46 -04:00
useEffect ( ( ) = > {
// check if we have a suitable wallet
2023-07-25 14:50:04 +01:00
const suitable = wallets . filter ( w = > w . chain === Chain . ONCHAIN && w . allowSend ( ) ) ;
2021-08-28 00:49:46 -04:00
if ( suitable . length === 0 ) {
2024-02-07 15:24:24 -04:00
presentAlert ( { title : loc.errors.error , message : loc.send.details_wallet_before_tx } ) ;
2021-08-28 00:49:46 -04:00
navigation . goBack ( ) ;
return ;
}
2023-07-25 14:50:04 +01:00
const newWallet = ( routeParams . walletID && wallets . find ( w = > w . getID ( ) === routeParams . walletID ) ) || suitable [ 0 ] ;
setWallet ( newWallet ) ;
2024-10-23 18:36:28 -04:00
setParams ( { feeUnit : newWallet.getPreferredBalanceUnit ( ) , amountUnit : newWallet.getPreferredBalanceUnit ( ) } ) ;
2019-02-01 01:19:09 -05:00
2021-02-25 19:13:34 +03:00
// we are ready!
setIsLoading ( false ) ;
// load cached fees
AsyncStorage . getItem ( NetworkTransactionFee . StorageKey )
2021-03-24 20:17:17 +03:00
. then ( res = > {
2024-05-22 20:35:36 +01:00
if ( ! res ) return ;
2021-02-25 19:13:34 +03:00
const fees = JSON . parse ( res ) ;
if ( ! fees ? . fastestFee ) return ;
setNetworkTransactionFees ( fees ) ;
} )
. catch ( e = > console . log ( 'loading cached recommendedFees error' , e ) ) ;
// load fresh fees from servers
2021-03-24 20:45:07 -04:00
setNetworkTransactionFeesIsLoading ( true ) ;
2021-02-25 19:13:34 +03:00
NetworkTransactionFees . recommendedFees ( )
. then ( async fees = > {
if ( ! fees ? . fastestFee ) return ;
setNetworkTransactionFees ( fees ) ;
await AsyncStorage . setItem ( NetworkTransactionFee . StorageKey , JSON . stringify ( fees ) ) ;
} )
2021-03-24 20:45:07 -04:00
. catch ( e = > console . log ( 'loading recommendedFees error' , e ) )
. finally ( ( ) = > {
LayoutAnimation . configureNext ( LayoutAnimation . Presets . easeInEaseOut ) ;
setNetworkTransactionFeesIsLoading ( false ) ;
} ) ;
2024-08-04 22:13:25 -04:00
} , [ ] ) ; // eslint-disable-line react-hooks/exhaustive-deps
2021-02-25 19:13:34 +03:00
// change header and reset state on wallet change
useEffect ( ( ) = > {
if ( ! wallet ) return ;
2023-11-20 16:02:47 -04:00
setSelectedWalletID ( wallet . getID ( ) ) ;
2020-09-14 13:49:08 +03:00
2021-02-25 19:13:34 +03:00
// reset other values
setChangeAddress ( null ) ;
2024-10-23 18:36:28 -04:00
setParams ( {
utxos : null ,
2024-10-23 19:01:21 -04:00
isTransactionReplaceable : wallet.type === HDSegwitBech32Wallet . type && ! routeParams . isTransactionReplaceable ? true : undefined ,
2024-10-23 18:36:28 -04:00
} ) ;
2021-02-25 19:13:34 +03:00
// update wallet UTXO
wallet
. fetchUtxo ( )
. then ( ( ) = > {
// we need to re-calculate fees
setDumb ( v = > ! v ) ;
} )
. catch ( e = > console . log ( 'fetchUtxo error' , e ) ) ;
2024-08-04 22:13:25 -04:00
} , [ wallet ] ) ; // eslint-disable-line react-hooks/exhaustive-deps
2021-02-25 19:13:34 +03:00
// recalc fees in effect so we don't block render
useEffect ( ( ) = > {
if ( ! wallet ) return ; // wait for it
const fees = networkTransactionFees ;
2021-03-19 15:29:27 +00:00
const requestedSatPerByte = Number ( feeRate ) ;
2024-10-23 18:36:28 -04:00
const lutxo = utxos || wallet . getUtxo ( ) ;
2021-12-23 12:46:29 +03:00
let frozen = 0 ;
2024-10-23 18:36:28 -04:00
if ( ! utxos ) {
2021-12-23 12:46:29 +03:00
// if utxo is not limited search for frozen outputs and calc it's balance
frozen = wallet
. getUtxo ( true )
. filter ( o = > ! lutxo . some ( i = > i . txid === o . txid && i . vout === o . vout ) )
. reduce ( ( prev , curr ) = > prev + curr . value , 0 ) ;
}
2020-10-05 18:53:47 +01:00
2021-02-25 19:13:34 +03:00
const options = [
{ key : 'current' , fee : requestedSatPerByte } ,
{ key : 'slowFee' , fee : fees.slowFee } ,
{ key : 'mediumFee' , fee : fees.mediumFee } ,
{ key : 'fastestFee' , fee : fees.fastestFee } ,
] ;
2018-01-30 22:42:38 +00:00
2024-05-22 20:35:36 +01:00
const newFeePrecalc : /* Record<string, any> */ IFee = { . . . feePrecalc } ;
2019-01-29 23:00:12 -05:00
2021-02-25 19:13:34 +03:00
for ( const opt of options ) {
let targets = [ ] ;
for ( const transaction of addresses ) {
if ( transaction . amount === BitcoinUnit . MAX ) {
// single output with MAX
targets = [ { address : transaction.address } ] ;
break ;
}
2024-05-22 20:35:36 +01:00
const value = transaction . amountSats ;
if ( Number ( value ) > 0 ) {
2021-02-25 19:13:34 +03:00
targets . push ( { address : transaction.address , value } ) ;
} else if ( transaction . amount ) {
2024-01-28 11:11:08 -04:00
if ( btcToSatoshi ( transaction . amount ) > 0 ) {
targets . push ( { address : transaction.address , value : btcToSatoshi ( transaction . amount ) } ) ;
2021-02-25 19:13:34 +03:00
}
}
}
2019-01-29 23:00:12 -05:00
2021-02-25 19:13:34 +03:00
// if targets is empty, insert dust
if ( targets . length === 0 ) {
targets . push ( { address : '36JxaUrpDzkEerkTf1FzwHNE1Hb7cCjgJV' , value : 546 } ) ;
}
2019-01-29 23:00:12 -05:00
2021-02-25 19:13:34 +03:00
// replace wrong addresses with dump
targets = targets . map ( t = > {
2021-12-05 18:46:42 +00:00
if ( ! wallet . isAddressValid ( t . address ) ) {
2021-02-25 19:13:34 +03:00
return { . . . t , address : '36JxaUrpDzkEerkTf1FzwHNE1Hb7cCjgJV' } ;
2021-12-05 18:46:42 +00:00
} else {
return t ;
2019-09-02 23:28:52 -04:00
}
2021-02-25 19:13:34 +03:00
} ) ;
2019-09-02 23:28:52 -04:00
2021-02-25 19:13:34 +03:00
let flag = false ;
while ( true ) {
2019-09-02 23:28:52 -04:00
try {
2024-05-22 20:35:36 +01:00
const { fee } = wallet . coinselect ( lutxo , targets , opt . fee ) ;
2021-02-25 19:13:34 +03:00
2024-05-22 20:35:36 +01:00
// @ts-ignore options& opt are used only to iterate keys we predefined and we know exist
2021-02-25 19:13:34 +03:00
newFeePrecalc [ opt . key ] = fee ;
break ;
2024-05-22 20:35:36 +01:00
} catch ( e : any ) {
2021-02-25 19:13:34 +03:00
if ( e . message . includes ( 'Not enough' ) && ! flag ) {
flag = true ;
// if we don't have enough funds, construct maximum possible transaction
targets = targets . map ( ( t , index ) = > ( index > 0 ? { . . . t , value : 546 } : { address : t.address } ) ) ;
continue ;
}
2024-05-22 20:35:36 +01:00
// @ts-ignore options& opt are used only to iterate keys we predefined and we know exist
2021-02-25 19:13:34 +03:00
newFeePrecalc [ opt . key ] = null ;
break ;
2019-09-02 23:28:52 -04:00
}
}
2018-10-27 15:38:22 -04:00
}
2021-02-25 19:13:34 +03:00
setFeePrecalc ( newFeePrecalc ) ;
2024-10-23 18:36:28 -04:00
setParams ( { frozenBalance : frozen } ) ;
} , [ wallet , networkTransactionFees , utxos , addresses , feeRate , dumb ] ) ; // eslint-disable-line react-hooks/exhaustive-deps
2018-01-30 22:42:38 +00:00
2021-12-23 12:46:29 +03:00
// we need to re-calculate fees if user opens-closes coin control
2021-12-23 18:39:33 +03:00
useFocusEffect (
useCallback ( ( ) = > {
2024-05-18 00:36:03 -04:00
setIsLoading ( false ) ;
2021-12-23 18:39:33 +03:00
setDumb ( v = > ! v ) ;
2024-07-23 13:44:04 -04:00
return ( ) = > {
feeModalRef . current ? . dismiss ( ) ;
} ;
2021-12-23 18:39:33 +03:00
} , [ ] ) ,
) ;
2021-12-23 12:46:29 +03:00
2021-02-25 19:13:34 +03:00
const getChangeAddressAsync = async ( ) = > {
if ( changeAddress ) return changeAddress ; // cache
2020-10-05 18:53:47 +01:00
2021-02-25 19:13:34 +03:00
let change ;
2024-05-22 20:35:36 +01:00
if ( WatchOnlyWallet . type === wallet ? . type && ! wallet . isHd ( ) ) {
2020-10-05 18:53:47 +01:00
// plain watchonly - just get the address
2021-02-25 19:13:34 +03:00
change = wallet . getAddress ( ) ;
2020-10-05 18:53:47 +01:00
} else {
// otherwise, lets call widely-used getChangeAddressAsync()
try {
2024-05-22 20:35:36 +01:00
change = await Promise . race ( [ sleep ( 2000 ) , wallet ? . getChangeAddressAsync ( ) ] ) ;
2020-10-05 18:53:47 +01:00
} catch ( _ ) { }
2021-02-25 19:13:34 +03:00
if ( ! change ) {
2020-10-05 18:53:47 +01:00
// either sleep expired or getChangeAddressAsync threw an exception
if ( wallet instanceof AbstractHDElectrumWallet ) {
2021-02-25 19:13:34 +03:00
change = wallet . _getInternalAddressByIndex ( wallet . getNextFreeChangeAddressIndex ( ) ) ;
2020-10-05 18:53:47 +01:00
} else {
// legacy wallets
2024-05-22 20:35:36 +01:00
change = wallet ? . getAddress ( ) ;
2020-10-05 18:53:47 +01:00
}
}
}
2021-02-25 19:13:34 +03:00
if ( change ) setChangeAddress ( change ) ; // cache
2020-10-05 18:53:47 +01:00
2021-02-25 19:13:34 +03:00
return change ;
} ;
2020-09-14 13:49:08 +03:00
/ * *
2021-02-25 19:13:34 +03:00
* TODO : refactor this mess , get rid of regexp , use https : //github.com/bitcoinjs/bitcoinjs-lib/issues/890 etc etc
*
* @param data { String } Can be address or ` bitcoin:xxxxxxx ` uri scheme , or invalid garbage
2020-09-14 13:49:08 +03:00
* /
2024-05-27 23:00:28 +01:00
2024-05-22 20:35:36 +01:00
const processAddressData = ( data : string | { data? : any } ) = > {
2024-05-27 23:00:28 +01:00
assert ( wallet , 'Internal error: wallet not set' ) ;
2024-05-22 20:35:36 +01:00
if ( typeof data !== 'string' ) {
data = String ( data . data ) ;
}
2021-07-04 22:32:35 -04:00
const currentIndex = scrollIndex . current ;
2021-02-25 19:13:34 +03:00
setIsLoading ( true ) ;
if ( ! data . replace ) {
// user probably scanned PSBT and got an object instead of string..?
setIsLoading ( false ) ;
2024-02-07 15:24:24 -04:00
return presentAlert ( { title : loc.errors.error , message : loc.send.details_address_field_is_not_valid } ) ;
2021-02-25 19:13:34 +03:00
}
2020-09-14 13:49:08 +03:00
2024-05-27 23:00:28 +01:00
const cl = new ContactList ( ) ;
2021-02-25 19:13:34 +03:00
const dataWithoutSchema = data . replace ( 'bitcoin:' , '' ) . replace ( 'BITCOIN:' , '' ) ;
2024-05-27 23:00:28 +01:00
if ( wallet . isAddressValid ( dataWithoutSchema ) || cl . isPaymentCodeValid ( dataWithoutSchema ) ) {
2023-07-25 14:50:04 +01:00
setAddresses ( addrs = > {
addrs [ scrollIndex . current ] . address = dataWithoutSchema ;
return [ . . . addrs ] ;
2021-03-12 19:19:04 +03:00
} ) ;
2021-02-25 19:13:34 +03:00
setIsLoading ( false ) ;
2024-05-24 11:06:37 -04:00
setTimeout ( ( ) = > scrollView . current ? . scrollToIndex ( { index : currentIndex , animated : false } ) , 50 ) ;
2021-02-25 19:13:34 +03:00
return ;
}
let address = '' ;
2024-05-22 20:35:36 +01:00
let options : TOptions ;
2021-02-25 19:13:34 +03:00
try {
if ( ! data . toLowerCase ( ) . startsWith ( 'bitcoin:' ) ) data = ` bitcoin: ${ data } ` ;
const decoded = DeeplinkSchemaMatch . bip21decode ( data ) ;
address = decoded . address ;
options = decoded . options ;
} catch ( error ) {
data = data . replace ( /(amount)=([^&]+)/g , '' ) . replace ( /(amount)=([^&]+)&/g , '' ) ;
const decoded = DeeplinkSchemaMatch . bip21decode ( data ) ;
decoded . options . amount = 0 ;
address = decoded . address ;
options = decoded . options ;
}
console . log ( 'options' , options ) ;
2024-05-27 23:00:28 +01:00
if ( wallet . isAddressValid ( address ) ) {
2023-07-25 14:50:04 +01:00
setAddresses ( addrs = > {
addrs [ scrollIndex . current ] . address = address ;
2024-05-22 20:35:36 +01:00
addrs [ scrollIndex . current ] . amount = options ? . amount ? ? 0 ;
addrs [ scrollIndex . current ] . amountSats = new BigNumber ( options ? . amount ? ? 0 ) . multipliedBy ( 100000000 ) . toNumber ( ) ;
2023-07-25 14:50:04 +01:00
return [ . . . addrs ] ;
2021-03-12 19:19:04 +03:00
} ) ;
2023-07-25 14:50:04 +01:00
setUnits ( u = > {
u [ scrollIndex . current ] = BitcoinUnit . BTC ; // also resetting current unit to BTC
return [ . . . u ] ;
2021-03-12 19:19:04 +03:00
} ) ;
2024-10-23 19:03:35 -04:00
setParams ( { transactionMemo : options.label || '' , amountUnit : BitcoinUnit.BTC , payjoinUrl : options.pj || '' } ) ; // there used to be `options.message` here as well. bug?
2021-07-04 22:32:35 -04:00
// RN Bug: contentOffset gets reset to 0 when state changes. Remove code once this bug is resolved.
2024-05-22 20:35:36 +01:00
setTimeout ( ( ) = > scrollView . current ? . scrollToIndex ( { index : currentIndex , animated : false } ) , 50 ) ;
2021-02-25 19:13:34 +03:00
}
setIsLoading ( false ) ;
} ;
const createTransaction = async ( ) = > {
2024-05-27 23:00:28 +01:00
assert ( wallet , 'Internal error: wallet is not set' ) ;
2021-02-25 19:13:34 +03:00
Keyboard . dismiss ( ) ;
setIsLoading ( true ) ;
2021-03-19 15:29:27 +00:00
const requestedSatPerByte = feeRate ;
2021-02-25 19:13:34 +03:00
for ( const [ index , transaction ] of addresses . entries ( ) ) {
let error ;
2024-05-22 20:35:36 +01:00
if ( ! transaction . amount || Number ( transaction . amount ) < 0 || parseFloat ( String ( transaction . amount ) ) === 0 ) {
2021-02-25 19:13:34 +03:00
error = loc . send . details_amount_field_is_not_valid ;
console . log ( 'validation error' ) ;
2024-05-22 20:35:36 +01:00
} else if ( parseFloat ( String ( transaction . amountSats ) ) <= 500 ) {
2021-02-25 19:13:34 +03:00
error = loc . send . details_amount_field_is_less_than_minimum_amount_sat ;
console . log ( 'validation error' ) ;
} else if ( ! requestedSatPerByte || parseFloat ( requestedSatPerByte ) < 1 ) {
error = loc . send . details_fee_field_is_not_valid ;
console . log ( 'validation error' ) ;
} else if ( ! transaction . address ) {
error = loc . send . details_address_field_is_not_valid ;
console . log ( 'validation error' ) ;
2024-05-22 20:35:36 +01:00
} else if ( balance - Number ( transaction . amountSats ) < 0 ) {
2021-02-25 19:13:34 +03:00
// first sanity check is that sending amount is not bigger than available balance
2021-12-23 12:46:29 +03:00
error = frozenBalance > 0 ? loc.send.details_total_exceeds_balance_frozen : loc.send.details_total_exceeds_balance ;
2021-02-25 19:13:34 +03:00
console . log ( 'validation error' ) ;
} else if ( transaction . address ) {
const address = transaction . address . trim ( ) . toLowerCase ( ) ;
if ( address . startsWith ( 'lnb' ) || address . startsWith ( 'lightning:lnb' ) ) {
2021-10-17 10:57:16 -04:00
error = loc . send . provided_address_is_invoice ;
2021-02-25 19:13:34 +03:00
console . log ( 'validation error' ) ;
2020-09-14 13:49:08 +03:00
}
}
2021-02-25 19:13:34 +03:00
if ( ! error ) {
2024-05-27 23:00:28 +01:00
const cl = new ContactList ( ) ;
if ( ! wallet . isAddressValid ( transaction . address ) && ! cl . isPaymentCodeValid ( transaction . address ) ) {
2021-02-25 19:13:34 +03:00
console . log ( 'validation error' ) ;
error = loc . send . details_address_field_is_not_valid ;
2020-09-14 13:49:08 +03:00
}
}
2024-06-02 22:37:58 +01:00
// validating payment codes, if any
if ( ! error ) {
if ( transaction . address . startsWith ( 'sp1' ) ) {
if ( ! wallet . allowSilentPaymentSend ( ) ) {
console . log ( 'validation error' ) ;
error = loc . send . cant_send_to_silentpayment_adress ;
}
}
if ( transaction . address . startsWith ( 'PM' ) ) {
if ( ! wallet . allowBIP47 ( ) ) {
console . log ( 'validation error' ) ;
error = loc . send . cant_send_to_bip47 ;
} else if ( ! ( wallet as unknown as AbstractHDElectrumWallet ) . getBIP47NotificationTransaction ( transaction . address ) ) {
console . log ( 'validation error' ) ;
error = loc . send . cant_find_bip47_notification ;
} else {
// BIP47 is allowed, notif tx is in place, lets sync joint addresses with the receiver
await ( wallet as unknown as AbstractHDElectrumWallet ) . syncBip47ReceiversAddresses ( transaction . address ) ;
}
}
}
2021-02-25 19:13:34 +03:00
if ( error ) {
2024-05-22 20:35:36 +01:00
scrollView . current ? . scrollToIndex ( { index } ) ;
2021-02-25 19:13:34 +03:00
setIsLoading ( false ) ;
2024-03-21 00:52:21 -04:00
presentAlert ( { title : loc.errors.error , message : error } ) ;
2023-12-29 07:52:12 -04:00
triggerHapticFeedback ( HapticFeedbackTypes . NotificationError ) ;
2021-02-25 19:13:34 +03:00
return ;
2021-02-01 19:26:31 +03:00
}
}
2021-02-25 19:13:34 +03:00
try {
await createPsbtTransaction ( ) ;
2024-05-22 20:35:36 +01:00
} catch ( Err : any ) {
2021-02-25 19:13:34 +03:00
setIsLoading ( false ) ;
2024-02-07 15:24:24 -04:00
presentAlert ( { title : loc.errors.error , message : Err.message } ) ;
2023-12-29 07:52:12 -04:00
triggerHapticFeedback ( HapticFeedbackTypes . NotificationError ) ;
2021-02-25 19:13:34 +03:00
}
2020-09-14 13:49:08 +03:00
} ;
2021-02-25 19:13:34 +03:00
const createPsbtTransaction = async ( ) = > {
2024-05-22 20:35:36 +01:00
if ( ! wallet ) return ;
2023-07-25 14:50:04 +01:00
const change = await getChangeAddressAsync ( ) ;
2024-05-22 20:35:36 +01:00
assert ( change , 'Could not get change address' ) ;
2021-03-19 15:29:27 +00:00
const requestedSatPerByte = Number ( feeRate ) ;
2024-10-23 18:36:28 -04:00
const lutxo : CreateTransactionUtxo [ ] = utxos || ( wallet ? . getUtxo ( ) ? ? [ ] ) ;
2021-04-28 07:55:27 +01:00
console . log ( { requestedSatPerByte , lutxo : lutxo.length } ) ;
2019-06-01 21:44:39 +01:00
2024-05-27 23:00:28 +01:00
const targets : CreateTransactionTarget [ ] = [ ] ;
2021-02-25 19:13:34 +03:00
for ( const transaction of addresses ) {
2020-04-24 15:03:15 +01:00
if ( transaction . amount === BitcoinUnit . MAX ) {
2021-03-12 19:19:04 +03:00
// output with MAX
targets . push ( { address : transaction.address } ) ;
continue ;
2020-04-24 15:03:15 +01:00
}
2024-05-22 20:35:36 +01:00
const value = parseInt ( String ( transaction . amountSats ) , 10 ) ;
2020-04-24 15:03:15 +01:00
if ( value > 0 ) {
targets . push ( { address : transaction.address , value } ) ;
2020-06-24 20:23:56 +01:00
} else if ( transaction . amount ) {
2024-01-28 11:11:08 -04:00
if ( btcToSatoshi ( transaction . amount ) > 0 ) {
targets . push ( { address : transaction.address , value : btcToSatoshi ( transaction . amount ) } ) ;
2020-06-24 20:23:56 +01:00
}
2019-09-02 23:28:52 -04:00
}
2019-08-04 20:33:15 +01:00
}
2019-06-01 21:44:39 +01:00
2024-05-27 23:00:28 +01:00
const targetsOrig = JSON . parse ( JSON . stringify ( targets ) ) ;
// preserving original since it will be mutated
2024-05-22 20:35:36 +01:00
// without forcing `HDSegwitBech32Wallet` i had a weird ts error, complaining about last argument (fp)
const { tx , outputs , psbt , fee } = ( wallet as HDSegwitBech32Wallet ) ? . createTransaction (
2021-02-25 19:13:34 +03:00
lutxo ,
2019-12-10 23:13:40 -05:00
targets ,
requestedSatPerByte ,
2023-07-25 14:50:04 +01:00
change ,
2021-02-25 19:13:34 +03:00
isTransactionReplaceable ? HDSegwitBech32Wallet.defaultRBFSequence : HDSegwitBech32Wallet.finalRBFSequence ,
2024-05-22 20:35:36 +01:00
false ,
0 ,
2019-12-10 23:13:40 -05:00
) ;
2019-09-27 15:49:56 +01:00
2021-09-09 12:00:11 +01:00
if ( tx && routeParams . launchedBy && psbt ) {
console . warn ( 'navigating back to ' , routeParams . launchedBy ) ;
2024-08-04 22:13:25 -04:00
2024-05-22 20:35:36 +01:00
// @ts-ignore idk how to fix FIXME?
2024-06-30 13:17:55 -04:00
2021-09-09 12:00:11 +01:00
navigation . navigate ( routeParams . launchedBy , { psbt } ) ;
}
2024-05-22 20:35:36 +01:00
if ( wallet ? . type === WatchOnlyWallet . type ) {
2019-09-27 15:49:56 +01:00
// watch-only wallets with enabled HW wallet support have different flow. we have to show PSBT to user as QR code
// so he can scan it and sign it. then we have to scan it back from user (via camera and QR code), and ask
// user whether he wants to broadcast it
2021-02-25 19:13:34 +03:00
navigation . navigate ( 'PsbtWithHardwareWallet' , {
2021-08-28 00:49:46 -04:00
memo : transactionMemo ,
2024-10-02 20:25:54 -04:00
walletID : wallet.getID ( ) ,
2019-12-11 22:17:09 -05:00
psbt ,
2021-09-09 12:00:11 +01:00
launchedBy : routeParams.launchedBy ,
2019-12-11 22:17:09 -05:00
} ) ;
2021-02-25 19:13:34 +03:00
setIsLoading ( false ) ;
2019-09-27 15:49:56 +01:00
return ;
}
2019-06-01 21:44:39 +01:00
2024-05-22 20:35:36 +01:00
if ( wallet ? . type === MultisigHDWallet . type ) {
2021-02-25 19:13:34 +03:00
navigation . navigate ( 'PsbtMultisig' , {
2021-08-28 00:49:46 -04:00
memo : transactionMemo ,
2020-10-05 22:25:14 +01:00
psbtBase64 : psbt.toBase64 ( ) ,
2020-12-01 00:40:54 -05:00
walletID : wallet.getID ( ) ,
2021-09-09 12:00:11 +01:00
launchedBy : routeParams.launchedBy ,
2020-10-05 22:25:14 +01:00
} ) ;
2021-02-25 19:13:34 +03:00
setIsLoading ( false ) ;
2020-10-05 22:25:14 +01:00
return ;
}
2024-05-22 20:35:36 +01:00
assert ( tx , 'createTRansaction failed' ) ;
2021-02-25 19:13:34 +03:00
txMetadata [ tx . getId ( ) ] = {
2021-08-28 00:49:46 -04:00
memo : transactionMemo ,
2019-06-01 21:44:39 +01:00
} ;
2021-02-25 19:13:34 +03:00
await saveToDisk ( ) ;
2020-11-06 18:52:12 +03:00
2023-07-25 14:50:04 +01:00
let recipients = outputs . filter ( ( { address } ) = > address !== change ) ;
2021-06-09 14:51:53 +01:00
if ( recipients . length === 0 ) {
// special case. maybe the only destination in this transaction is our own change address..?
// (ez can be the case for single-address wallet when doing self-payment for consolidation)
recipients = outputs ;
}
2020-11-06 18:52:12 +03:00
2021-02-25 19:13:34 +03:00
navigation . navigate ( 'Confirm' , {
2019-12-11 22:17:09 -05:00
fee : new BigNumber ( fee ) . dividedBy ( 100000000 ) . toNumber ( ) ,
2021-08-28 00:49:46 -04:00
memo : transactionMemo ,
2021-08-10 07:35:25 -07:00
walletID : wallet.getID ( ) ,
2019-12-11 22:17:09 -05:00
tx : tx.toHex ( ) ,
2024-05-27 23:00:28 +01:00
targets : targetsOrig ,
2020-11-06 18:52:12 +03:00
recipients ,
2019-12-11 22:17:09 -05:00
satoshiPerByte : requestedSatPerByte ,
2021-02-25 19:13:34 +03:00
payjoinUrl ,
2020-09-21 20:32:20 +01:00
psbt ,
2019-12-11 22:17:09 -05:00
} ) ;
2021-02-25 19:13:34 +03:00
setIsLoading ( false ) ;
2020-10-26 20:50:03 +03:00
} ;
2024-06-08 12:01:56 -04:00
useEffect ( ( ) = > {
const newWallet = wallets . find ( w = > w . getID ( ) === routeParams . walletID ) ;
if ( newWallet ) {
setWallet ( newWallet ) ;
}
2024-08-04 22:13:25 -04:00
// eslint-disable-next-line react-hooks/exhaustive-deps
} , [ routeParams . walletID ] ) ;
2018-12-23 19:44:31 -05:00
2024-10-23 18:36:28 -04:00
const setTransactionMemo = ( memo : string ) = > {
setParams ( { transactionMemo : memo } ) ;
} ;
2020-11-19 14:33:18 +00:00
/ * *
* same as ` importTransaction ` , but opens camera instead .
*
* @returns { Promise < void > }
* /
2024-10-01 13:26:57 -04:00
const importQrTransaction = async ( ) = > {
2024-05-22 20:35:36 +01:00
if ( wallet ? . type !== WatchOnlyWallet . type ) {
2024-02-07 15:24:24 -04:00
return presentAlert ( { title : loc.errors.error , message : 'Importing transaction in non-watchonly wallet (this should never happen)' } ) ;
2020-11-19 14:33:18 +00:00
}
2024-10-01 13:26:57 -04:00
const data = await scanQrHelper ( route . name , true ) ;
importQrTransactionOnBarScanned ( data ) ;
2024-08-04 22:13:25 -04:00
} ;
const importQrTransactionOnBarScanned = ( ret : any ) = > {
navigation . getParent ( ) ? . getParent ( ) ? . dispatch ( popAction ) ;
if ( ! wallet ) return ;
if ( ! ret . data ) ret = { data : ret } ;
if ( ret . data . toUpperCase ( ) . startsWith ( 'UR' ) ) {
presentAlert ( { title : loc.errors.error , message : 'BC-UR not decoded. This should never happen' } ) ;
} else if ( ret . data . indexOf ( '+' ) === - 1 && ret . data . indexOf ( '=' ) === - 1 && ret . data . indexOf ( '=' ) === - 1 ) {
// this looks like NOT base64, so maybe its transaction's hex
// we dont support it in this flow
} else {
// psbt base64?
// we construct PSBT object and pass to next screen
// so user can do smth with it:
const psbt = bitcoin . Psbt . fromBase64 ( ret . data ) ;
navigation . navigate ( 'PsbtWithHardwareWallet' , {
memo : transactionMemo ,
2024-10-02 20:25:54 -04:00
walletID : wallet.getID ( ) ,
2024-08-04 22:13:25 -04:00
psbt ,
} ) ;
setIsLoading ( false ) ;
}
} ;
2020-11-19 14:33:18 +00:00
2020-09-16 17:52:10 +01:00
/ * *
* watch - only wallets with enabled HW wallet support have different flow . we have to show PSBT to user as QR code
* so he can scan it and sign it . then we have to scan it back from user ( via camera and QR code ) , and ask
* user whether he wants to broadcast it .
* alternatively , user can export psbt file , sign it externally and then import it
*
* @returns { Promise < void > }
* /
2024-08-04 22:13:25 -04:00
const importTransaction = async ( ) = > {
2024-05-22 20:35:36 +01:00
if ( wallet ? . type !== WatchOnlyWallet . type ) {
2024-02-07 15:24:24 -04:00
return presentAlert ( { title : loc.errors.error , message : 'Importing transaction in non-watchonly wallet (this should never happen)' } ) ;
2020-09-16 17:52:10 +01:00
}
2019-12-31 21:31:04 -06:00
try {
2021-12-27 15:19:37 -06:00
const res = await DocumentPicker . pickSingle ( {
2020-10-05 22:25:14 +01:00
type :
Platform . OS === 'ios'
2024-09-09 21:39:35 -04:00
? [ 'io.bluewallet.psbt' , 'io.bluewallet.psbt.txn' , DocumentPicker . types . plainText , DocumentPicker . types . json ]
2020-10-05 22:25:14 +01:00
: [ DocumentPicker . types . allFiles ] ,
2020-02-24 21:45:14 +00:00
} ) ;
2020-09-16 17:52:10 +01:00
if ( DeeplinkSchemaMatch . isPossiblySignedPSBTFile ( res . uri ) ) {
// we assume that transaction is already signed, so all we have to do is get txhex and pass it to next screen
// so user can broadcast:
2020-01-03 21:12:29 -06:00
const file = await RNFS . readFile ( res . uri , 'ascii' ) ;
2020-09-16 17:52:10 +01:00
const psbt = bitcoin . Psbt . fromBase64 ( file ) ;
const txhex = psbt . extractTransaction ( ) . toHex ( ) ;
2024-10-02 20:25:54 -04:00
navigation . navigate ( 'PsbtWithHardwareWallet' , { memo : transactionMemo , walletID : wallet.getID ( ) , txhex } ) ;
2021-02-25 19:13:34 +03:00
setIsLoading ( false ) ;
2024-06-30 13:17:55 -04:00
2021-02-25 19:13:34 +03:00
return ;
}
2020-09-16 17:52:10 +01:00
2021-02-25 19:13:34 +03:00
if ( DeeplinkSchemaMatch . isPossiblyPSBTFile ( res . uri ) ) {
2020-09-16 17:52:10 +01:00
// looks like transaction is UNsigned, so we construct PSBT object and pass to next screen
// so user can do smth with it:
const file = await RNFS . readFile ( res . uri , 'ascii' ) ;
const psbt = bitcoin . Psbt . fromBase64 ( file ) ;
2024-10-02 20:25:54 -04:00
navigation . navigate ( 'PsbtWithHardwareWallet' , { memo : transactionMemo , walletID : wallet.getID ( ) , psbt } ) ;
2021-02-25 19:13:34 +03:00
setIsLoading ( false ) ;
2024-06-30 13:17:55 -04:00
2021-02-25 19:13:34 +03:00
return ;
}
if ( DeeplinkSchemaMatch . isTXNFile ( res . uri ) ) {
2020-09-16 17:52:10 +01:00
// plain text file with txhex ready to broadcast
2020-11-27 16:35:14 +00:00
const file = ( await RNFS . readFile ( res . uri , 'ascii' ) ) . replace ( '\n' , '' ) . replace ( '\r' , '' ) ;
2024-10-02 20:25:54 -04:00
navigation . navigate ( 'PsbtWithHardwareWallet' , { memo : transactionMemo , walletID : wallet.getID ( ) , txhex : file } ) ;
2021-02-25 19:13:34 +03:00
setIsLoading ( false ) ;
2024-06-30 13:17:55 -04:00
2021-02-25 19:13:34 +03:00
return ;
2019-12-31 21:31:04 -06:00
}
2021-02-25 19:13:34 +03:00
2024-02-07 15:24:24 -04:00
presentAlert ( { title : loc.errors.error , message : loc.send.details_unrecognized_file_format } ) ;
2019-12-31 21:31:04 -06:00
} catch ( err ) {
if ( ! DocumentPicker . isCancel ( err ) ) {
2024-02-07 15:24:24 -04:00
presentAlert ( { title : loc.errors.error , message : loc.send.details_no_signed_tx } ) ;
2019-12-31 21:31:04 -06:00
}
}
2024-08-04 22:13:25 -04:00
} ;
2019-12-31 21:31:04 -06:00
2021-02-25 19:13:34 +03:00
const askCosignThisTransaction = async ( ) = > {
2020-10-08 16:39:31 +01:00
return new Promise ( resolve = > {
Alert . alert (
'' ,
2021-02-28 10:30:11 +03:00
loc . multisig . cosign_this_transaction ,
2020-10-08 16:39:31 +01:00
[
{
text : loc._.no ,
style : 'cancel' ,
onPress : ( ) = > resolve ( false ) ,
} ,
{
text : loc._.yes ,
onPress : ( ) = > resolve ( true ) ,
} ,
] ,
{ cancelable : false } ,
) ;
} ) ;
} ;
2024-08-04 22:13:25 -04:00
const _importTransactionMultisig = async ( base64arg : string | false ) = > {
try {
const base64 = base64arg || ( await fs . openSignedTransaction ( ) ) ;
if ( ! base64 ) return ;
const psbt = bitcoin . Psbt . fromBase64 ( base64 ) ; // if it doesnt throw - all good, its valid
if ( ( wallet as MultisigHDWallet ) ? . howManySignaturesCanWeMake ( ) > 0 && ( await askCosignThisTransaction ( ) ) ) {
setIsLoading ( true ) ;
await sleep ( 100 ) ;
( wallet as MultisigHDWallet ) . cosignPsbt ( psbt ) ;
setIsLoading ( false ) ;
await sleep ( 100 ) ;
}
2024-06-16 22:26:43 -04:00
2024-08-04 22:13:25 -04:00
if ( wallet ) {
navigation . navigate ( 'PsbtMultisig' , {
memo : transactionMemo ,
psbtBase64 : psbt.toBase64 ( ) ,
walletID : wallet.getID ( ) ,
} ) ;
2024-05-22 20:35:36 +01:00
}
2024-08-04 22:13:25 -04:00
} catch ( error : any ) {
presentAlert ( { title : loc.send.problem_with_psbt , message : error.message } ) ;
}
setIsLoading ( false ) ;
} ;
2020-10-05 22:25:14 +01:00
2024-08-04 22:13:25 -04:00
const importTransactionMultisig = ( ) = > {
2024-05-22 20:35:36 +01:00
return _importTransactionMultisig ( false ) ;
2024-08-04 22:13:25 -04:00
} ;
2024-06-17 14:27:45 -04:00
2024-08-04 22:13:25 -04:00
const onBarScanned = ( ret : any ) = > {
navigation . getParent ( ) ? . dispatch ( popAction ) ;
if ( ! ret . data ) ret = { data : ret } ;
if ( ret . data . toUpperCase ( ) . startsWith ( 'UR' ) ) {
presentAlert ( { title : loc.errors.error , message : 'BC-UR not decoded. This should never happen' } ) ;
} else if ( ret . data . indexOf ( '+' ) === - 1 && ret . data . indexOf ( '=' ) === - 1 && ret . data . indexOf ( '=' ) === - 1 ) {
// this looks like NOT base64, so maybe its transaction's hex
// we dont support it in this flow
} else {
// psbt base64?
return _importTransactionMultisig ( ret . data ) ;
}
} ;
const importTransactionMultisigScanQr = async ( ) = > {
2024-10-01 13:26:57 -04:00
const data = await scanQrHelper ( route . name , true ) ;
onBarScanned ( data ) ;
2024-08-04 22:13:25 -04:00
} ;
2020-10-08 16:39:31 +01:00
2024-08-07 20:32:16 -04:00
const handleAddRecipient = ( ) = > {
setAddresses ( prevAddresses = > [ . . . prevAddresses , { address : '' , key : String ( Math . random ( ) ) } as IPaymentDestinations ] ) ;
// Wait for the state to update before scrolling
setTimeout ( ( ) = > {
scrollIndex . current = addresses . length ; // New index is at the end of the list
scrollView . current ? . scrollToIndex ( {
index : scrollIndex.current ,
animated : true ,
} ) ;
} , 0 ) ;
2024-08-04 22:13:25 -04:00
} ;
2020-09-23 09:30:14 +03:00
2024-10-13 03:08:52 -04:00
const onRemoveAllRecipientsConfirmed = useCallback ( ( ) = > {
setAddresses ( [ { address : '' , key : String ( Math . random ( ) ) } as IPaymentDestinations ] ) ;
} , [ ] ) ;
const handleRemoveAllRecipients = useCallback ( ( ) = > {
Alert . alert ( loc . send . details_recipients_title , loc . send . details_add_recc_rem_all_alert_description , [
{
text : loc._.cancel ,
onPress : ( ) = > { } ,
style : 'cancel' ,
} ,
{
text : loc._.ok ,
onPress : onRemoveAllRecipientsConfirmed ,
} ,
] ) ;
} , [ onRemoveAllRecipientsConfirmed ] ) ;
2024-08-07 20:32:16 -04:00
const handleRemoveRecipient = ( ) = > {
if ( addresses . length > 1 ) {
const newAddresses = [ . . . addresses ] ;
newAddresses . splice ( scrollIndex . current , 1 ) ;
// Adjust the current index if the last item was removed
const newIndex = scrollIndex . current >= newAddresses . length ? newAddresses . length - 1 : scrollIndex.current ;
setAddresses ( newAddresses ) ;
2024-06-30 13:17:55 -04:00
2024-08-07 20:32:16 -04:00
// Wait for the state to update before scrolling
setTimeout ( ( ) = > {
scrollView . current ? . scrollToIndex ( {
index : newIndex ,
animated : true ,
} ) ;
} , 0 ) ;
// Update the scroll index reference
scrollIndex . current = newIndex ;
2024-08-07 00:07:44 -04:00
}
2024-08-04 22:13:25 -04:00
} ;
2020-09-23 09:30:14 +03:00
2024-08-04 22:13:25 -04:00
const handleCoinControl = async ( ) = > {
2024-05-22 20:35:36 +01:00
if ( ! wallet ) return ;
2021-02-25 19:13:34 +03:00
navigation . navigate ( 'CoinControl' , {
2024-05-22 20:35:36 +01:00
walletID : wallet?.getID ( ) ,
2021-02-25 19:13:34 +03:00
} ) ;
2024-08-04 22:13:25 -04:00
} ;
2020-11-11 20:58:09 -05:00
2024-08-04 22:13:25 -04:00
const handleInsertContact = async ( ) = > {
2024-07-22 01:21:40 -04:00
if ( ! wallet ) return ;
2024-06-07 14:50:50 -04:00
navigation . navigate ( 'PaymentCodeList' , { walletID : wallet.getID ( ) } ) ;
2024-08-04 22:13:25 -04:00
} ;
2024-06-07 09:37:45 -04:00
2024-08-04 22:13:25 -04:00
const handlePsbtSign = async ( ) = > {
2021-02-25 19:13:34 +03:00
setIsLoading ( true ) ;
2021-02-18 16:37:43 +03:00
await new Promise ( resolve = > setTimeout ( resolve , 100 ) ) ; // sleep for animations
2024-07-23 13:44:04 -04:00
const scannedData = await scanQrHelper ( name , true , undefined ) ;
2024-07-14 22:54:30 -04:00
if ( ! scannedData ) return setIsLoading ( false ) ;
2021-02-25 19:13:34 +03:00
2024-07-14 22:54:30 -04:00
let tx ;
let psbt ;
try {
psbt = bitcoin . Psbt . fromBase64 ( scannedData ) ;
tx = ( wallet as MultisigHDWallet ) . cosignPsbt ( psbt ) . tx ;
} catch ( e : any ) {
presentAlert ( { title : loc.errors.error , message : e.message } ) ;
return ;
} finally {
setIsLoading ( false ) ;
}
2021-02-25 19:13:34 +03:00
2024-07-14 22:54:30 -04:00
if ( ! tx || ! wallet ) return setIsLoading ( false ) ;
2024-07-14 20:11:30 -04:00
2024-07-14 22:54:30 -04:00
// we need to remove change address from recipients, so that Confirm screen show more accurate info
const changeAddresses : string [ ] = [ ] ;
// @ts-ignore hacky
for ( let c = 0 ; c < wallet . next_free_change_address_index + wallet . gap_limit ; c ++ ) {
2024-05-22 20:35:36 +01:00
// @ts-ignore hacky
2024-07-14 22:54:30 -04:00
changeAddresses . push ( wallet . _getInternalAddressByIndex ( c ) ) ;
}
const recipients = psbt . txOutputs . filter ( ( { address } ) = > ! changeAddresses . includes ( String ( address ) ) ) ;
2024-07-14 20:11:30 -04:00
2024-07-14 22:54:30 -04:00
navigation . navigate ( 'CreateTransaction' , {
fee : new BigNumber ( psbt . getFee ( ) ) . dividedBy ( 100000000 ) . toNumber ( ) ,
feeSatoshi : psbt.getFee ( ) ,
wallet ,
tx : tx.toHex ( ) ,
recipients ,
satoshiPerByte : psbt.getFeeRate ( ) ,
showAnimatedQr : true ,
psbt ,
2021-02-25 19:13:34 +03:00
} ) ;
2024-08-04 22:13:25 -04:00
} ;
2021-08-23 15:39:52 -04:00
2024-08-04 22:13:25 -04:00
// Header Right Button
const headerRightOnPress = ( id : string ) = > {
2024-10-13 03:08:52 -04:00
if ( id === CommonToolTipActions . AddRecipient . id ) {
2024-08-04 22:13:25 -04:00
handleAddRecipient ( ) ;
2024-10-13 03:08:52 -04:00
} else if ( id === CommonToolTipActions . RemoveRecipient . id ) {
2024-08-04 22:13:25 -04:00
handleRemoveRecipient ( ) ;
2024-10-22 21:38:43 -04:00
} else if ( id === CommonToolTipActions . SignPSBT . id ) {
2024-08-04 22:13:25 -04:00
handlePsbtSign ( ) ;
2024-10-22 21:38:43 -04:00
} else if ( id === CommonToolTipActions . SendMax . id ) {
2024-08-04 22:13:25 -04:00
onUseAllPressed ( ) ;
2024-10-22 21:38:43 -04:00
} else if ( id === CommonToolTipActions . AllowRBF . id ) {
2024-08-04 22:13:25 -04:00
onReplaceableFeeSwitchValueChanged ( ! isTransactionReplaceable ) ;
2024-10-22 21:38:43 -04:00
} else if ( id === CommonToolTipActions . ImportTransaction . id ) {
2024-08-04 22:13:25 -04:00
importTransaction ( ) ;
2024-10-22 21:38:43 -04:00
} else if ( id === CommonToolTipActions . ImportTransactionQR . id ) {
2024-08-04 22:13:25 -04:00
importQrTransaction ( ) ;
2024-10-22 21:38:43 -04:00
} else if ( id === CommonToolTipActions . ImportTransactionMultsig . id ) {
2024-08-04 22:13:25 -04:00
importTransactionMultisig ( ) ;
2024-10-22 21:38:43 -04:00
} else if ( id === CommonToolTipActions . CoSignTransaction . id ) {
2024-08-04 22:13:25 -04:00
importTransactionMultisigScanQr ( ) ;
2024-10-22 21:38:43 -04:00
} else if ( id === CommonToolTipActions . CoinControl . id ) {
2024-08-04 22:13:25 -04:00
handleCoinControl ( ) ;
2024-10-22 21:38:43 -04:00
} else if ( id === CommonToolTipActions . InsertContact . id ) {
2024-08-04 22:13:25 -04:00
handleInsertContact ( ) ;
2024-10-13 03:08:52 -04:00
} else if ( id === CommonToolTipActions . RemoveAllRecipients . id ) {
handleRemoveAllRecipients ( ) ;
2024-08-04 22:13:25 -04:00
}
} ;
const headerRightActions = ( ) = > {
2024-10-22 22:42:42 -04:00
if ( ! wallet ) return [ ] ;
2024-06-07 09:37:45 -04:00
2024-10-23 17:22:39 -04:00
const walletActions : Action [ ] [ ] = [ ] ;
2021-08-23 15:39:52 -04:00
2024-10-27 22:05:14 -04:00
const recipientActions : Action [ ] = [
CommonToolTipActions . AddRecipient ,
CommonToolTipActions . RemoveRecipient ,
{
. . . CommonToolTipActions . RemoveAllRecipients ,
hidden : ! ( addresses . length > 1 ) ,
} ,
] ;
2024-10-23 17:22:39 -04:00
walletActions . push ( recipientActions ) ;
2024-10-24 02:08:42 -04:00
const isSendMaxUsed = addresses . some ( element = > element . amount === BitcoinUnit . MAX ) ;
const sendMaxAction : Action [ ] = [
{
. . . CommonToolTipActions . SendMax ,
disabled : wallet.getBalance ( ) === 0 || isSendMaxUsed ,
hidden : ! isEditable || ! ( Number ( wallet . getBalance ( ) ) > 0 ) ,
} ,
] ;
walletActions . push ( sendMaxAction ) ;
2024-10-23 17:22:39 -04:00
2024-10-27 22:05:14 -04:00
const rbfAction : Action [ ] = [
{
. . . CommonToolTipActions . AllowRBF ,
menuState : isTransactionReplaceable ,
hidden : ! ( wallet . type === HDSegwitBech32Wallet . type && isTransactionReplaceable !== undefined ) ,
} ,
] ;
walletActions . push ( rbfAction ) ;
2024-10-23 17:22:39 -04:00
2024-10-27 22:05:14 -04:00
const transactionActions : Action [ ] = [
{
. . . CommonToolTipActions . ImportTransaction ,
hidden : ! ( wallet . type === WatchOnlyWallet . type && wallet . isHd ( ) ) ,
} ,
{
. . . CommonToolTipActions . ImportTransactionQR ,
hidden : ! ( wallet . type === WatchOnlyWallet . type && wallet . isHd ( ) ) ,
} ,
{
. . . CommonToolTipActions . ImportTransactionMultsig ,
hidden : ! ( wallet . type === MultisigHDWallet . type ) ,
} ,
{
. . . CommonToolTipActions . CoSignTransaction ,
hidden : ! ( wallet . type === MultisigHDWallet . type && wallet . howManySignaturesCanWeMake ( ) > 0 ) ,
} ,
{
. . . CommonToolTipActions . SignPSBT ,
hidden : ! ( wallet as MultisigHDWallet ) ? . allowCosignPsbt ( ) ,
} ,
] ;
walletActions . push ( transactionActions ) ;
2024-10-23 17:22:39 -04:00
const specificWalletActions : Action [ ] = [
2024-10-22 17:05:09 -04:00
{
. . . CommonToolTipActions . InsertContact ,
2024-10-22 22:42:42 -04:00
hidden : ! ( isEditable && wallet . allowBIP47 ( ) && wallet . isBIP47Enabled ( ) ) ,
2024-10-22 17:05:09 -04:00
} ,
CommonToolTipActions . CoinControl ,
] ;
2024-10-23 17:22:39 -04:00
walletActions . push ( specificWalletActions ) ;
2021-09-30 07:33:50 -04:00
2024-10-23 17:22:39 -04:00
return walletActions ;
2024-08-04 22:13:25 -04:00
} ;
const setHeaderRightOptions = ( ) = > {
navigation . setOptions ( {
// eslint-disable-next-line react/no-unstable-nested-components
2024-09-27 08:39:31 -04:00
headerRight : ( ) = > < HeaderMenuButton disabled = { isLoading } onPressMenuItem = { headerRightOnPress } actions = { headerRightActions ( ) } / > ,
2024-08-04 22:13:25 -04:00
} ) ;
} ;
const onReplaceableFeeSwitchValueChanged = ( value : boolean ) = > {
2024-10-23 18:36:28 -04:00
setParams ( { isTransactionReplaceable : value } ) ;
2024-08-04 22:13:25 -04:00
} ;
const handleRecipientsScroll = ( e : NativeSyntheticEvent < NativeScrollEvent > ) = > {
const contentOffset = e . nativeEvent . contentOffset ;
const viewSize = e . nativeEvent . layoutMeasurement ;
const index = Math . floor ( contentOffset . x / viewSize . width ) ;
scrollIndex . current = index ;
} ;
2021-03-12 17:02:49 +03:00
2024-08-05 06:32:10 -04:00
const onUseAllPressed = ( ) = > {
triggerHapticFeedback ( HapticFeedbackTypes . NotificationWarning ) ;
2024-06-17 14:27:45 -04:00
const message = frozenBalance > 0 ? loc.send.details_adv_full_sure_frozen : loc.send.details_adv_full_sure ;
2024-09-05 19:58:25 -04:00
const anchor = findNodeHandle ( scrollView . current ) ;
const options = {
title : loc.send.details_adv_full ,
2024-06-17 14:27:45 -04:00
message ,
2024-09-05 19:58:25 -04:00
options : [ loc . _ . cancel , loc . _ . ok ] ,
cancelButtonIndex : 0 ,
anchor : anchor ? ? undefined ,
} ;
ActionSheet . showActionSheetWithOptions ( options , buttonIndex = > {
if ( buttonIndex === 1 ) {
Keyboard . dismiss ( ) ;
setAddresses ( addrs = > {
addrs [ scrollIndex . current ] . amount = BitcoinUnit . MAX ;
addrs [ scrollIndex . current ] . amountSats = BitcoinUnit . MAX ;
return [ . . . addrs ] ;
} ) ;
setUnits ( u = > {
u [ scrollIndex . current ] = BitcoinUnit . BTC ;
return [ . . . u ] ;
} ) ;
}
} ) ;
2024-06-17 14:27:45 -04:00
} ;
2024-05-22 20:35:36 +01:00
const formatFee = ( fee : number ) = > formatBalance ( fee , feeUnit ! , true ) ;
2021-02-25 19:13:34 +03:00
const stylesHook = StyleSheet . create ( {
root : {
backgroundColor : colors.elevated ,
} ,
2024-07-30 19:05:58 -04:00
2021-02-25 19:13:34 +03:00
selectLabel : {
color : colors.buttonTextColor ,
} ,
of : {
color : colors.feeText ,
} ,
memo : {
borderColor : colors.formBorder ,
borderBottomColor : colors.formBorder ,
backgroundColor : colors.inputBackgroundColor ,
} ,
feeLabel : {
color : colors.feeText ,
} ,
2024-08-04 15:41:49 -04:00
2021-02-25 19:13:34 +03:00
feeRow : {
backgroundColor : colors.feeLabel ,
} ,
feeValue : {
color : colors.feeValue ,
} ,
} ) ;
2024-06-07 16:11:57 -04:00
const calculateTotalAmount = ( ) = > {
const totalAmount = addresses . reduce ( ( total , item ) = > total + Number ( item . amountSats || 0 ) , 0 ) ;
const totalWithFee = totalAmount + ( feePrecalc . current || 0 ) ;
return totalWithFee ;
} ;
2021-02-25 19:13:34 +03:00
const renderCreateButton = ( ) = > {
2024-06-07 16:11:57 -04:00
const totalWithFee = calculateTotalAmount ( ) ;
const isDisabled = totalWithFee === 0 || totalWithFee > balance || balance === 0 || isLoading || addresses . length === 0 ;
2018-10-20 22:10:21 +01:00
return (
2020-05-24 12:17:26 +03:00
< View style = { styles . createButton } >
2021-02-25 19:13:34 +03:00
{ isLoading ? (
2020-04-27 09:23:56 +01:00
< ActivityIndicator / >
) : (
2024-06-07 16:11:57 -04:00
< Button onPress = { createTransaction } disabled = { isDisabled } title = { loc . send . details_next } testID = "CreateTransactionButton" / >
2020-04-27 09:23:56 +01:00
) }
2018-10-20 22:10:21 +01:00
< / View >
) ;
} ;
2018-01-30 22:42:38 +00:00
2021-02-25 19:13:34 +03:00
const renderWalletSelectionOrCoinsSelected = ( ) = > {
2024-10-23 18:36:28 -04:00
if ( isVisible ) return null ;
if ( utxos !== null ) {
2020-11-09 12:24:17 +03:00
return (
< View style = { styles . select } >
2020-11-22 22:19:46 -05:00
< CoinsSelected
2024-10-23 18:36:28 -04:00
number = { utxos ? . length || 0 }
2021-02-25 19:13:34 +03:00
onContainerPress = { handleCoinControl }
2020-11-22 22:19:46 -05:00
onClose = { ( ) = > {
LayoutAnimation . configureNext ( LayoutAnimation . Presets . easeInEaseOut ) ;
2024-10-23 18:36:28 -04:00
setParams ( { utxos : null } ) ;
2020-11-22 22:19:46 -05:00
} }
/ >
2020-11-09 12:24:17 +03:00
< / View >
) ;
}
2018-12-23 19:44:31 -05:00
return (
2020-05-24 12:17:26 +03:00
< View style = { styles . select } >
2021-09-09 12:00:11 +01:00
{ ! isLoading && isEditable && (
2018-12-23 19:44:31 -05:00
< TouchableOpacity
2021-06-24 08:50:57 -04:00
accessibilityRole = "button"
2020-05-24 12:17:26 +03:00
style = { styles . selectTouch }
2024-06-30 13:17:55 -04:00
onPress = { ( ) = > {
navigation . navigate ( 'SelectWallet' , { chainType : Chain.ONCHAIN } ) ;
} }
2018-12-23 19:44:31 -05:00
>
2020-05-24 12:17:26 +03:00
< Text style = { styles . selectText } > { loc . wallets . select_wallet . toLowerCase ( ) } < / Text >
2021-03-18 22:30:01 -04:00
< Icon name = { I18nManager . isRTL ? 'angle-left' : 'angle-right' } size = { 18 } type = "font-awesome" color = "#9aa0aa" / >
2018-12-23 19:44:31 -05:00
< / TouchableOpacity >
) }
2020-05-24 12:17:26 +03:00
< View style = { styles . selectWrap } >
2019-08-10 02:57:55 -04:00
< TouchableOpacity
2021-06-24 08:50:57 -04:00
accessibilityRole = "button"
2020-05-24 12:17:26 +03:00
style = { styles . selectTouch }
2024-06-30 13:17:55 -04:00
onPress = { ( ) = > {
navigation . navigate ( 'SelectWallet' , { chainType : Chain.ONCHAIN } ) ;
} }
2021-10-26 16:47:09 -04:00
disabled = { ! isEditable || isLoading }
2019-08-06 19:31:23 +02:00
>
2024-05-22 20:35:36 +01:00
< Text style = { [ styles . selectLabel , stylesHook . selectLabel ] } > { wallet ? . getLabel ( ) } < / Text >
2019-08-10 02:57:55 -04:00
< / TouchableOpacity >
2018-12-23 19:44:31 -05:00
< / View >
< / View >
) ;
} ;
2024-05-22 20:35:36 +01:00
const renderBitcoinTransactionInfoFields = ( params : { item : IPaymentDestinations ; index : number } ) = > {
2021-02-25 19:13:34 +03:00
const { item , index } = params ;
2020-11-10 09:21:54 -05:00
return (
2021-02-25 19:13:34 +03:00
< View style = { { width } } testID = { 'Transaction' + index } >
< AmountInput
isLoading = { isLoading }
2020-11-10 09:21:54 -05:00
amount = { item . amount ? item . amount . toString ( ) : null }
2024-05-22 20:35:36 +01:00
onAmountUnitChange = { ( unit : BitcoinUnit ) = > {
2023-07-25 14:50:04 +01:00
setAddresses ( addrs = > {
const addr = addrs [ index ] ;
2021-03-12 19:19:04 +03:00
switch ( unit ) {
case BitcoinUnit . SATS :
2024-05-22 20:35:36 +01:00
addr . amountSats = parseInt ( String ( addr . amount ) , 10 ) ;
2021-03-12 19:19:04 +03:00
break ;
case BitcoinUnit . BTC :
2024-05-22 20:35:36 +01:00
addr . amountSats = btcToSatoshi ( String ( addr . amount ) ) ;
2021-03-12 19:19:04 +03:00
break ;
case BitcoinUnit . LOCAL_CURRENCY :
// also accounting for cached fiat->sat conversion to avoid rounding error
2024-05-22 20:35:36 +01:00
addr . amountSats = AmountInput . getCachedSatoshis ( addr . amount ) || btcToSatoshi ( fiatToBTC ( Number ( addr . amount ) ) ) ;
2021-03-12 19:19:04 +03:00
break ;
}
2023-07-25 14:50:04 +01:00
addrs [ index ] = addr ;
return [ . . . addrs ] ;
2021-03-12 19:19:04 +03:00
} ) ;
2023-07-25 14:50:04 +01:00
setUnits ( u = > {
u [ index ] = unit ;
return [ . . . u ] ;
2021-03-12 19:19:04 +03:00
} ) ;
2020-11-10 09:21:54 -05:00
} }
2024-05-22 20:35:36 +01:00
onChangeText = { ( text : string ) = > {
2023-07-25 14:50:04 +01:00
setAddresses ( addrs = > {
2021-03-12 19:19:04 +03:00
item . amount = text ;
switch ( units [ index ] || amountUnit ) {
case BitcoinUnit . BTC :
2024-01-28 11:11:08 -04:00
item . amountSats = btcToSatoshi ( item . amount ) ;
2021-03-12 19:19:04 +03:00
break ;
case BitcoinUnit . LOCAL_CURRENCY :
2024-05-22 20:35:36 +01:00
item . amountSats = btcToSatoshi ( fiatToBTC ( Number ( item . amount ) ) ) ;
2021-03-12 19:19:04 +03:00
break ;
case BitcoinUnit . SATS :
2021-05-08 09:41:45 +03:00
default :
2023-07-25 14:50:04 +01:00
item . amountSats = parseInt ( text , 10 ) ;
2021-03-12 19:19:04 +03:00
break ;
}
2023-07-25 14:50:04 +01:00
addrs [ index ] = item ;
return [ . . . addrs ] ;
2021-03-12 19:19:04 +03:00
} ) ;
2020-11-10 09:21:54 -05:00
} }
2021-02-25 19:13:34 +03:00
unit = { units [ index ] || amountUnit }
2021-09-09 12:00:11 +01:00
editable = { isEditable }
disabled = { ! isEditable }
2024-08-24 14:06:17 -04:00
inputAccessoryViewID = { InputAccessoryAllFundsAccessoryViewID }
2020-11-10 09:21:54 -05:00
/ >
2021-12-23 12:46:29 +03:00
{ frozenBalance > 0 && (
2023-03-31 17:32:07 +02:00
< TouchableOpacity accessibilityRole = "button" style = { styles . frozenContainer } onPress = { handleCoinControl } >
2021-12-23 12:46:29 +03:00
< BlueText >
{ loc . formatString ( loc . send . details_frozen , { amount : formatBalanceWithoutSuffix ( frozenBalance , BitcoinUnit . BTC , true ) } ) }
< / BlueText >
< / TouchableOpacity >
) }
2021-02-25 19:13:34 +03:00
< AddressInput
2021-03-24 20:17:17 +03:00
onChangeText = { text = > {
2020-11-10 09:21:54 -05:00
text = text . trim ( ) ;
2023-07-25 14:50:04 +01:00
const { address , amount , memo , payjoinUrl : pjUrl } = DeeplinkSchemaMatch . decodeBitcoinUri ( text ) ;
setAddresses ( addrs = > {
2021-03-12 19:19:04 +03:00
item . address = address || text ;
item . amount = amount || item . amount ;
2023-07-25 14:50:04 +01:00
addrs [ index ] = item ;
return [ . . . addrs ] ;
2021-03-12 19:19:04 +03:00
} ) ;
2024-10-23 18:36:28 -04:00
if ( memo ) {
setParams ( { transactionMemo : memo } ) ;
}
2021-02-25 19:13:34 +03:00
setIsLoading ( false ) ;
2024-10-23 18:36:28 -04:00
setParams ( { payjoinUrl : pjUrl } ) ;
2020-11-10 09:21:54 -05:00
} }
2021-02-25 19:13:34 +03:00
onBarScanned = { processAddressData }
2020-11-10 09:21:54 -05:00
address = { item . address }
2021-02-25 19:13:34 +03:00
isLoading = { isLoading }
2024-08-24 16:15:22 -04:00
inputAccessoryViewID = { DismissKeyboardInputAccessoryViewID }
2021-02-25 19:13:34 +03:00
launchedBy = { name }
2021-09-09 12:00:11 +01:00
editable = { isEditable }
2024-11-15 22:32:50 +00:00
style = { styles . addressInput }
2020-11-10 09:21:54 -05:00
/ >
2021-02-25 19:13:34 +03:00
{ addresses . length > 1 && (
2021-06-06 05:38:03 -04:00
< Text style = { [ styles . of , stylesHook . of ] } > { loc . formatString ( loc . _ . of , { number : index + 1 , total : addresses.length } ) } < / Text >
2020-11-10 09:21:54 -05:00
) }
< / View >
) ;
2019-09-02 23:28:52 -04:00
} ;
2024-08-07 20:32:16 -04:00
const getItemLayout = ( _ : any , index : number ) = > ( {
length : width ,
offset : width * index ,
index ,
} ) ;
2021-02-25 19:13:34 +03:00
return (
2024-04-21 20:35:17 -04:00
< View style = { [ styles . root , stylesHook . root ] } onLayout = { e = > setWidth ( e . nativeEvent . layout . width ) } >
< View >
2024-08-20 15:22:11 -04:00
< FlatList
keyboardShouldPersistTaps = "always"
scrollEnabled = { addresses . length > 1 }
data = { addresses }
renderItem = { renderBitcoinTransactionInfoFields }
horizontal
ref = { scrollView }
automaticallyAdjustKeyboardInsets
pagingEnabled
removeClippedSubviews = { false }
onMomentumScrollBegin = { Keyboard . dismiss }
onScroll = { handleRecipientsScroll }
scrollEventThrottle = { 16 }
scrollIndicatorInsets = { styles . scrollViewIndicator }
contentContainerStyle = { styles . scrollViewContent }
getItemLayout = { getItemLayout }
/ >
< View style = { [ styles . memo , stylesHook . memo ] } >
< TextInput
onChangeText = { setTransactionMemo }
placeholder = { loc . send . details_note_placeholder }
placeholderTextColor = "#81868e"
value = { transactionMemo }
numberOfLines = { 1 }
style = { styles . memoText }
editable = { ! isLoading }
onSubmitEditing = { Keyboard . dismiss }
2024-08-24 14:06:17 -04:00
inputAccessoryViewID = { DismissKeyboardInputAccessoryViewID }
2024-08-04 15:41:49 -04:00
/ >
2024-08-20 15:22:11 -04:00
< / View >
< TouchableOpacity
testID = "chooseFee"
accessibilityRole = "button"
onPress = { ( ) = > feeModalRef . current ? . present ( ) }
disabled = { isLoading }
style = { styles . fee }
>
< Text style = { [ styles . feeLabel , stylesHook . feeLabel ] } > { loc . send . create_fee } < / Text >
{ networkTransactionFeesIsLoading ? (
< ActivityIndicator / >
) : (
< View style = { [ styles . feeRow , stylesHook . feeRow ] } >
< Text style = { stylesHook . feeValue } >
{ feePrecalc . current ? formatFee ( feePrecalc . current ) : feeRate + ' ' + loc . units . sat_vbyte }
< / Text >
< / View >
) }
< / TouchableOpacity >
{ renderCreateButton ( ) }
< SelectFeeModal
ref = { feeModalRef }
networkTransactionFees = { networkTransactionFees }
feePrecalc = { feePrecalc }
feeRate = { feeRate }
setCustomFee = { setCustomFee }
setFeePrecalc = { setFeePrecalc }
2024-09-18 22:24:36 -04:00
feeUnit = { units [ scrollIndex . current ] }
2024-08-20 15:22:11 -04:00
/ >
2021-02-25 19:13:34 +03:00
< / View >
2024-08-24 14:06:17 -04:00
< DismissKeyboardInputAccessory / >
2024-04-21 20:35:17 -04:00
{ Platform . select ( {
2024-05-22 20:35:36 +01:00
ios : < InputAccessoryAllFunds canUseAll = { balance > 0 } onUseAllPressed = { onUseAllPressed } balance = { String ( allBalance ) } / > ,
2024-10-23 18:36:28 -04:00
android : isVisible && (
2024-05-22 20:35:36 +01:00
< InputAccessoryAllFunds canUseAll = { balance > 0 } onUseAllPressed = { onUseAllPressed } balance = { String ( allBalance ) } / >
2024-04-21 20:35:17 -04:00
) ,
} ) }
{ renderWalletSelectionOrCoinsSelected ( ) }
< / View >
2021-02-25 19:13:34 +03:00
) ;
2018-03-18 02:48:23 +00:00
} ;
2020-07-15 13:32:59 -04:00
2021-02-25 19:13:34 +03:00
export default SendDetails ;
const styles = StyleSheet . create ( {
root : {
flex : 1 ,
justifyContent : 'space-between' ,
} ,
scrollViewContent : {
flexDirection : 'row' ,
} ,
2021-03-12 17:02:49 +03:00
scrollViewIndicator : {
top : 0 ,
left : 8 ,
bottom : 0 ,
right : 8 ,
} ,
2021-02-25 19:13:34 +03:00
createButton : {
marginVertical : 16 ,
marginHorizontal : 16 ,
alignContent : 'center' ,
minHeight : 44 ,
} ,
select : {
marginBottom : 24 ,
marginHorizontal : 24 ,
alignItems : 'center' ,
} ,
selectTouch : {
flexDirection : 'row' ,
alignItems : 'center' ,
} ,
selectText : {
color : '#9aa0aa' ,
fontSize : 14 ,
marginRight : 8 ,
} ,
selectWrap : {
flexDirection : 'row' ,
alignItems : 'center' ,
marginVertical : 4 ,
} ,
selectLabel : {
fontSize : 14 ,
} ,
of : {
alignSelf : 'flex-end' ,
marginRight : 18 ,
marginVertical : 8 ,
} ,
2024-07-30 19:05:58 -04:00
2021-02-25 19:13:34 +03:00
memo : {
flexDirection : 'row' ,
borderWidth : 1 ,
borderBottomWidth : 0.5 ,
minHeight : 44 ,
height : 44 ,
2024-11-15 22:32:50 +00:00
marginHorizontal : 16 ,
2021-02-25 19:13:34 +03:00
alignItems : 'center' ,
marginVertical : 8 ,
borderRadius : 4 ,
} ,
memoText : {
flex : 1 ,
marginHorizontal : 8 ,
minHeight : 33 ,
color : '#81868e' ,
} ,
fee : {
flexDirection : 'row' ,
2024-11-15 22:32:50 +00:00
marginHorizontal : 16 ,
2021-02-25 19:13:34 +03:00
justifyContent : 'space-between' ,
alignItems : 'center' ,
} ,
feeLabel : {
fontSize : 14 ,
} ,
feeRow : {
minWidth : 40 ,
height : 25 ,
borderRadius : 4 ,
justifyContent : 'space-between' ,
flexDirection : 'row' ,
alignItems : 'center' ,
paddingHorizontal : 10 ,
} ,
2021-12-23 12:46:29 +03:00
frozenContainer : {
flexDirection : 'row' ,
justifyContent : 'center' ,
alignItems : 'center' ,
marginVertical : 8 ,
} ,
2024-11-15 22:32:50 +00:00
addressInput : {
marginHorizontal : 16 ,
marginVertical : 8 ,
} ,
2021-02-25 19:13:34 +03:00
} ) ;