mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-01-18 05:12:47 +01:00
Merge branch 'master' into electrumpref
This commit is contained in:
commit
9555e5927e
@ -125,6 +125,13 @@
|
||||
"symbol": "£",
|
||||
"country": "United Kingdom (British Pound)"
|
||||
},
|
||||
"HKD": {
|
||||
"endPointKey": "HKD",
|
||||
"locale": "zh-HK",
|
||||
"source": "CoinGecko",
|
||||
"symbol": "HK$",
|
||||
"country": "Hong Kong (Hong Kong Dollar)"
|
||||
},
|
||||
"HRK": {
|
||||
"endPointKey": "HRK",
|
||||
"locale": "hr-HR",
|
||||
|
@ -58,13 +58,20 @@
|
||||
style="@style/WidgetTextPrimary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:autoSizeMaxTextSize="24sp"
|
||||
android:autoSizeMinTextSize="12sp"
|
||||
android:autoSizeStepGranularity="2sp"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:duplicateParentState="false"
|
||||
android:editable="false"
|
||||
android:lines="1"
|
||||
android:text="Loading..."
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:visibility="gone"
|
||||
android:layout_gravity="end"/>
|
||||
android:visibility="gone" />
|
||||
<LinearLayout
|
||||
android:id="@+id/price_arrow_container"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -132,13 +132,19 @@ export const showImagePickerAndReadImage = async (): Promise<string | undefined>
|
||||
return undefined;
|
||||
} else if (response.errorCode) {
|
||||
throw new Error(response.errorMessage);
|
||||
} else if (response.assets?.[0]?.uri) {
|
||||
} else if (response.assets) {
|
||||
try {
|
||||
const result = await RNQRGenerator.detect({ uri: decodeURI(response.assets[0].uri.toString()) });
|
||||
return result?.values[0];
|
||||
const uri = response.assets[0].uri;
|
||||
if (uri) {
|
||||
const result = await RNQRGenerator.detect({ uri: decodeURI(uri.toString()) });
|
||||
if (result?.values.length > 0) {
|
||||
return result?.values[0];
|
||||
}
|
||||
}
|
||||
throw new Error(loc.send.qr_error_no_qrcode);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new Error(loc.send.qr_error_no_qrcode);
|
||||
presentAlert({ message: loc.send.qr_error_no_qrcode });
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,9 +193,11 @@ export const showFilePickerAndReadFile = async function (): Promise<{ data: stri
|
||||
if (result) {
|
||||
return { data: result.values[0], uri: fileCopyUri };
|
||||
}
|
||||
presentAlert({ message: loc.send.qr_error_no_qrcode });
|
||||
return { data: false, uri: false };
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
presentAlert({ message: loc.send.qr_error_no_qrcode });
|
||||
return { data: false, uri: false };
|
||||
}
|
||||
}
|
||||
|
@ -629,7 +629,11 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||
hexFingerprint = Buffer.from(hexFingerprint, 'hex').toString('hex');
|
||||
}
|
||||
|
||||
const path = 'm/' + m[1].split('/').slice(1).join('/').replace(/[h]/g, "'");
|
||||
let path = 'm/' + m[1].split('/').slice(1).join('/').replace(/[h]/g, "'");
|
||||
if (path === 'm/') {
|
||||
// not considered valid by Bip32 lib
|
||||
path = 'm/0';
|
||||
}
|
||||
let xpub = m[2];
|
||||
if (xpub.indexOf('/') !== -1) {
|
||||
xpub = xpub.substr(0, xpub.indexOf('/'));
|
||||
|
@ -1,15 +1,16 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { Image, Keyboard, Platform, StyleSheet, Text } from 'react-native';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import ToolTipMenu from './TooltipMenu';
|
||||
import loc from '../loc';
|
||||
import { scanQrHelper } from '../helpers/scan-qr';
|
||||
import { showFilePickerAndReadFile, showImagePickerAndReadImage } from '../blue_modules/fs';
|
||||
import presentAlert from './Alert';
|
||||
import { useTheme } from './themes';
|
||||
import RNQRGenerator from 'rn-qr-generator';
|
||||
import { CommonToolTipActions } from '../typings/CommonToolTipActions';
|
||||
import { useSettings } from '../hooks/context/useSettings';
|
||||
import { useRoute } from '@react-navigation/native';
|
||||
import { useExtendedNavigation } from '../hooks/useExtendedNavigation';
|
||||
|
||||
interface AddressInputScanButtonProps {
|
||||
isLoading: boolean;
|
||||
@ -19,6 +20,10 @@ interface AddressInputScanButtonProps {
|
||||
onChangeText: (text: string) => void;
|
||||
}
|
||||
|
||||
interface RouteParams {
|
||||
onBarScanned?: any;
|
||||
}
|
||||
|
||||
export const AddressInputScanButton = ({
|
||||
isLoading,
|
||||
launchedBy,
|
||||
@ -28,6 +33,9 @@ export const AddressInputScanButton = ({
|
||||
}: AddressInputScanButtonProps) => {
|
||||
const { colors } = useTheme();
|
||||
const { isClipboardGetContentEnabled } = useSettings();
|
||||
|
||||
const navigation = useExtendedNavigation();
|
||||
const params = useRoute().params as RouteParams;
|
||||
const stylesHook = StyleSheet.create({
|
||||
scan: {
|
||||
backgroundColor: colors.scanLabel,
|
||||
@ -40,8 +48,10 @@ export const AddressInputScanButton = ({
|
||||
const toolTipOnPress = useCallback(async () => {
|
||||
await scanButtonTapped();
|
||||
Keyboard.dismiss();
|
||||
if (launchedBy) scanQrHelper(launchedBy, true).then(value => onBarScanned({ data: value }));
|
||||
}, [launchedBy, onBarScanned, scanButtonTapped]);
|
||||
navigation.navigate('ScanQRCode', {
|
||||
showFileImportButton: true,
|
||||
});
|
||||
}, [navigation, scanButtonTapped]);
|
||||
|
||||
const actions = useMemo(() => {
|
||||
const availableActions = [
|
||||
@ -57,20 +67,23 @@ export const AddressInputScanButton = ({
|
||||
return availableActions;
|
||||
}, [isClipboardGetContentEnabled]);
|
||||
|
||||
useEffect(() => {
|
||||
const data = params.onBarScanned;
|
||||
if (data) {
|
||||
onBarScanned({ data });
|
||||
navigation.setParams({ onBarScanned: undefined });
|
||||
}
|
||||
});
|
||||
|
||||
const onMenuItemPressed = useCallback(
|
||||
async (action: string) => {
|
||||
if (onBarScanned === undefined) throw new Error('onBarScanned is required');
|
||||
switch (action) {
|
||||
case CommonToolTipActions.ScanQR.id:
|
||||
scanButtonTapped();
|
||||
if (launchedBy) {
|
||||
scanQrHelper(launchedBy)
|
||||
.then(value => onBarScanned({ data: value }))
|
||||
.catch(error => {
|
||||
presentAlert({ message: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
navigation.navigate('ScanQRCode', {
|
||||
showFileImportButton: true,
|
||||
});
|
||||
break;
|
||||
case CommonToolTipActions.PasteFromClipboard.id:
|
||||
try {
|
||||
@ -134,7 +147,7 @@ export const AddressInputScanButton = ({
|
||||
}
|
||||
Keyboard.dismiss();
|
||||
},
|
||||
[launchedBy, onBarScanned, onChangeText, scanButtonTapped],
|
||||
[navigation, onBarScanned, onChangeText, scanButtonTapped],
|
||||
);
|
||||
|
||||
const buttonStyle = useMemo(() => [styles.scan, stylesHook.scan], [stylesHook.scan]);
|
||||
|
@ -183,6 +183,9 @@ const CompanionDelegates = () => {
|
||||
|
||||
if (fileName && /\.(jpe?g|png)$/i.test(fileName)) {
|
||||
try {
|
||||
if (!decodedUrl) {
|
||||
throw new Error(loc.send.qr_error_no_qrcode);
|
||||
}
|
||||
const values = await RNQRGenerator.detect({
|
||||
uri: decodedUrl,
|
||||
});
|
||||
@ -200,11 +203,12 @@ const CompanionDelegates = () => {
|
||||
},
|
||||
);
|
||||
} else {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
presentAlert({ message: loc.send.qr_error_no_qrcode });
|
||||
throw new Error(loc.send.qr_error_no_qrcode);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error detecting QR code:', error);
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
presentAlert({ message: loc.send.qr_error_no_qrcode });
|
||||
}
|
||||
} else {
|
||||
DeeplinkSchemaMatch.navigationRouteFor(event, (value: [string, any]) => navigationRef.navigate(...value), {
|
||||
|
@ -155,9 +155,13 @@ const MultipleStepsListItem = props => {
|
||||
style={[styles.rowPartialRightButton, stylesHook.provideKeyButton, rightButtonOpacity]}
|
||||
onPress={props.button.onPress}
|
||||
>
|
||||
<Text style={[styles.provideKeyButtonText, stylesHook.provideKeyButtonText, styles.rightButton]}>
|
||||
{props.button.text}
|
||||
</Text>
|
||||
{props.button.showActivityIndicator ? (
|
||||
<ActivityIndicator />
|
||||
) : (
|
||||
<Text style={[styles.provideKeyButtonText, stylesHook.provideKeyButtonText, styles.rightButton]}>
|
||||
{props.button.text}
|
||||
</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
@ -171,7 +175,11 @@ const MultipleStepsListItem = props => {
|
||||
style={styles.rightButton}
|
||||
onPress={props.rightButton.onPress}
|
||||
>
|
||||
<Text style={[styles.provideKeyButtonText, stylesHook.provideKeyButtonText]}>{props.rightButton.text}</Text>
|
||||
{props.rightButton.showActivityIndicator ? (
|
||||
<ActivityIndicator />
|
||||
) : (
|
||||
<Text style={[styles.provideKeyButtonText, stylesHook.provideKeyButtonText]}>{props.rightButton.text}</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
@ -194,11 +202,13 @@ MultipleStepsListItem.propTypes = {
|
||||
disabled: PropTypes.bool,
|
||||
buttonType: PropTypes.number,
|
||||
leftText: PropTypes.string,
|
||||
showActivityIndicator: PropTypes.bool,
|
||||
}),
|
||||
rightButton: PropTypes.shape({
|
||||
text: PropTypes.string,
|
||||
onPress: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
showActivityIndicator: PropTypes.bool,
|
||||
}),
|
||||
};
|
||||
|
||||
|
@ -1,54 +1,5 @@
|
||||
import { Platform } from 'react-native';
|
||||
import { check, request, PERMISSIONS, RESULTS } from 'react-native-permissions';
|
||||
import { navigationRef } from '../NavigationService';
|
||||
|
||||
/**
|
||||
* Helper function that navigates to ScanQR screen, and returns promise that will resolve with the result of a scan,
|
||||
* and then navigates back. If QRCode scan was closed, promise resolves to null.
|
||||
*
|
||||
* @param currentScreenName {string}
|
||||
* @param showFileImportButton {boolean}
|
||||
*
|
||||
* @param onDismiss {function} - if camera is closed via X button it gets triggered
|
||||
* @param useMerge {boolean} - if true, will merge the new screen with the current screen, otherwise will replace the current screen
|
||||
* @return {Promise<string>}
|
||||
*/
|
||||
function scanQrHelper(
|
||||
currentScreenName: string,
|
||||
showFileImportButton = true,
|
||||
onDismiss?: () => void,
|
||||
useMerge = true,
|
||||
): Promise<string | null> {
|
||||
return requestCameraAuthorization().then(() => {
|
||||
return new Promise(resolve => {
|
||||
let params = {};
|
||||
|
||||
if (useMerge) {
|
||||
const onBarScanned = function (data: any) {
|
||||
setTimeout(() => resolve(data.data || data), 1);
|
||||
navigationRef.navigate({ name: currentScreenName, params: data, merge: true });
|
||||
};
|
||||
|
||||
params = {
|
||||
showFileImportButton: Boolean(showFileImportButton),
|
||||
onDismiss,
|
||||
onBarScanned,
|
||||
};
|
||||
} else {
|
||||
params = { launchedBy: currentScreenName, showFileImportButton: Boolean(showFileImportButton) };
|
||||
}
|
||||
|
||||
navigationRef.navigate({
|
||||
name: 'ScanQRCodeRoot',
|
||||
params: {
|
||||
screen: 'ScanQRCode',
|
||||
params,
|
||||
},
|
||||
merge: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const isCameraAuthorizationStatusGranted = async () => {
|
||||
const status = await check(Platform.OS === 'android' ? PERMISSIONS.ANDROID.CAMERA : PERMISSIONS.IOS.CAMERA);
|
||||
@ -59,4 +10,4 @@ const requestCameraAuthorization = () => {
|
||||
return request(Platform.OS === 'android' ? PERMISSIONS.ANDROID.CAMERA : PERMISSIONS.IOS.CAMERA);
|
||||
};
|
||||
|
||||
export { scanQrHelper, isCameraAuthorizationStatusGranted, requestCameraAuthorization };
|
||||
export { isCameraAuthorizationStatusGranted, requestCameraAuthorization };
|
||||
|
@ -3,6 +3,7 @@ import { navigationRef } from '../NavigationService';
|
||||
import { presentWalletExportReminder } from '../helpers/presentWalletExportReminder';
|
||||
import { unlockWithBiometrics, useBiometrics } from './useBiometrics';
|
||||
import { useStorage } from './context/useStorage';
|
||||
import { requestCameraAuthorization } from '../helpers/scan-qr';
|
||||
|
||||
// List of screens that require biometrics
|
||||
const requiresBiometrics = ['WalletExportRoot', 'WalletXpubRoot', 'ViewEditMultisigCosignersRoot', 'ExportMultisigCoordinationSetupRoot'];
|
||||
@ -90,6 +91,10 @@ export const useExtendedNavigation = <T extends NavigationProp<ParamListBase>>()
|
||||
return; // Prevent proceeding with the original navigation if the reminder is shown
|
||||
}
|
||||
}
|
||||
|
||||
if (screenName === 'ScanQRCode') {
|
||||
await requestCameraAuthorization();
|
||||
}
|
||||
proceedWithNavigation();
|
||||
})();
|
||||
};
|
||||
|
@ -137,7 +137,7 @@
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<false/>
|
||||
<true/>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
<key>NSExceptionDomains</key>
|
||||
|
@ -193,7 +193,7 @@
|
||||
"outdated_rate": "Rate was last updated: {date}",
|
||||
"psbt_tx_open": "Open Signed Transaction",
|
||||
"psbt_tx_scan": "Scan Signed Transaction",
|
||||
"qr_error_no_qrcode": "We were unable to find a QR Code in the selected image. Make sure the image contains only a QR Code and no additional content such as text or buttons.",
|
||||
"qr_error_no_qrcode": "We were unable to find a valid QR Code in the selected image. Make sure the image contains only a QR Code and no additional content such as text or buttons.",
|
||||
"reset_amount": "Reset Amount",
|
||||
"reset_amount_confirm": "Would you like to reset the amount?",
|
||||
"success_done": "Done",
|
||||
|
@ -125,6 +125,13 @@
|
||||
"symbol": "£",
|
||||
"country": "United Kingdom (British Pound)"
|
||||
},
|
||||
"HKD": {
|
||||
"endPointKey": "HKD",
|
||||
"locale": "zh-HK",
|
||||
"source": "CoinGecko",
|
||||
"symbol": "HK$",
|
||||
"country": "Hong Kong (Hong Kong Dollar)"
|
||||
},
|
||||
"HRK": {
|
||||
"endPointKey": "HRK",
|
||||
"locale": "hr-HR",
|
||||
|
@ -17,13 +17,15 @@ import {
|
||||
WalletsAddMultisigHelpComponent,
|
||||
WalletsAddMultisigStep2Component,
|
||||
} from './LazyLoadAddWalletStack';
|
||||
import { ScanQRCodeComponent } from './LazyLoadScanQRCodeStack';
|
||||
import { ScanQRCodeParamList } from './DetailViewStackParamList';
|
||||
|
||||
export type AddWalletStackParamList = {
|
||||
AddWallet: undefined;
|
||||
ImportWallet?: {
|
||||
label?: string;
|
||||
triggerImport?: boolean;
|
||||
scannedData?: string;
|
||||
onBarScanned?: string;
|
||||
};
|
||||
ImportWalletDiscovery: {
|
||||
importText: string;
|
||||
@ -55,6 +57,7 @@ export type AddWalletStackParamList = {
|
||||
format: string;
|
||||
};
|
||||
WalletsAddMultisigHelp: undefined;
|
||||
ScanQRCode: ScanQRCodeParamList;
|
||||
};
|
||||
|
||||
const Stack = createNativeStackNavigator<AddWalletStackParamList>();
|
||||
@ -138,6 +141,16 @@ const AddWalletStack = () => {
|
||||
headerShadowVisible: false,
|
||||
})(theme)}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="ScanQRCode"
|
||||
component={ScanQRCodeComponent}
|
||||
options={navigationStyle({
|
||||
headerShown: false,
|
||||
statusBarHidden: true,
|
||||
presentation: 'fullScreenModal',
|
||||
headerShadowVisible: false,
|
||||
})(theme)}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
@ -33,7 +33,6 @@ import PaymentCodesListComponent from './LazyLoadPaymentCodeStack';
|
||||
import LNDCreateInvoiceRoot from './LNDCreateInvoiceStack';
|
||||
import ReceiveDetailsStackRoot from './ReceiveDetailsStack';
|
||||
import ScanLndInvoiceRoot from './ScanLndInvoiceStack';
|
||||
import ScanQRCodeStackRoot from './ScanQRCodeStack';
|
||||
import SendDetailsStack from './SendDetailsStack';
|
||||
import SignVerifyStackRoot from './SignVerifyStack';
|
||||
import ViewEditMultisigCosignersStackRoot from './ViewEditMultisigCosignersStack';
|
||||
@ -65,6 +64,7 @@ import SelfTest from '../screen/settings/SelfTest';
|
||||
import ReleaseNotes from '../screen/settings/ReleaseNotes';
|
||||
import ToolsScreen from '../screen/settings/tools';
|
||||
import SettingsPrivacy from '../screen/settings/SettingsPrivacy';
|
||||
import { ScanQRCodeComponent } from './LazyLoadScanQRCodeStack';
|
||||
|
||||
const DetailViewStackScreensStack = () => {
|
||||
const theme = useTheme();
|
||||
@ -358,15 +358,6 @@ const DetailViewStackScreensStack = () => {
|
||||
options={{ ...NavigationDefaultOptions, ...StatusBarLightOptions }}
|
||||
/>
|
||||
<DetailViewStack.Screen name="ReceiveDetailsRoot" component={ReceiveDetailsStackRoot} options={NavigationDefaultOptions} />
|
||||
<DetailViewStack.Screen
|
||||
name="ScanQRCodeRoot"
|
||||
component={ScanQRCodeStackRoot}
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: 'fullScreenModal',
|
||||
statusBarHidden: true,
|
||||
}}
|
||||
/>
|
||||
<DetailViewStack.Screen
|
||||
name="ManageWallets"
|
||||
component={ManageWallets}
|
||||
@ -378,6 +369,16 @@ const DetailViewStackScreensStack = () => {
|
||||
statusBarStyle: 'auto',
|
||||
})(theme)}
|
||||
/>
|
||||
<DetailViewStack.Screen
|
||||
name="ScanQRCode"
|
||||
component={ScanQRCodeComponent}
|
||||
options={navigationStyle({
|
||||
headerShown: false,
|
||||
statusBarHidden: true,
|
||||
presentation: 'fullScreenModal',
|
||||
headerShadowVisible: false,
|
||||
})(theme)}
|
||||
/>
|
||||
</DetailViewStack.Navigator>
|
||||
);
|
||||
};
|
||||
|
@ -2,10 +2,24 @@ import { LightningTransaction, Transaction, TWallet } from '../class/wallets/typ
|
||||
import { ElectrumServerItem } from '../screen/settings/ElectrumSettings';
|
||||
import { SendDetailsParams } from './SendDetailsStackParamList';
|
||||
|
||||
export type ScanQRCodeParamList = {
|
||||
cameraStatusGranted?: boolean;
|
||||
backdoorPressed?: boolean;
|
||||
launchedBy?: string;
|
||||
urTotal?: number;
|
||||
urHave?: number;
|
||||
backdoorText?: string;
|
||||
onDismiss?: () => void;
|
||||
onBarScanned?: (data: string) => void;
|
||||
showFileImportButton?: boolean;
|
||||
backdoorVisible?: boolean;
|
||||
animatedQRCodeData?: Record<string, any>;
|
||||
};
|
||||
|
||||
export type DetailViewStackParamList = {
|
||||
UnlockWithScreen: undefined;
|
||||
WalletsList: { scannedData?: string };
|
||||
WalletTransactions: { isLoading?: boolean; walletID: string; walletType: string };
|
||||
WalletsList: { onBarScanned?: string };
|
||||
WalletTransactions: { isLoading?: boolean; walletID: string; walletType: string; onBarScanned?: string };
|
||||
WalletDetails: { walletID: string };
|
||||
TransactionDetails: { tx: Transaction; hash: string; walletID: string };
|
||||
TransactionStatus: { hash: string; walletID?: string };
|
||||
@ -19,8 +33,8 @@ export type DetailViewStackParamList = {
|
||||
LNDViewInvoice: { invoice: LightningTransaction; walletID: string };
|
||||
LNDViewAdditionalInvoiceInformation: { invoiceId: string };
|
||||
LNDViewAdditionalInvoicePreImage: { invoiceId: string };
|
||||
Broadcast: { scannedData?: string };
|
||||
IsItMyAddress: { address?: string };
|
||||
Broadcast: { onBarScanned?: string };
|
||||
IsItMyAddress: { address?: string; onBarScanned?: string };
|
||||
GenerateWord: undefined;
|
||||
LnurlPay: undefined;
|
||||
LnurlPaySuccess: {
|
||||
@ -57,12 +71,13 @@ export type DetailViewStackParamList = {
|
||||
NetworkSettings: undefined;
|
||||
About: undefined;
|
||||
DefaultView: undefined;
|
||||
ElectrumSettings: { server?: ElectrumServerItem };
|
||||
ElectrumSettings: { server?: ElectrumServerItem; onBarScanned?: string };
|
||||
SettingsBlockExplorer: undefined;
|
||||
EncryptStorage: undefined;
|
||||
Language: undefined;
|
||||
LightningSettings: {
|
||||
url?: string;
|
||||
onBarScanned?: string;
|
||||
};
|
||||
NotificationSettings: undefined;
|
||||
SelfTest: undefined;
|
||||
@ -85,22 +100,7 @@ export type DetailViewStackParamList = {
|
||||
address: string;
|
||||
};
|
||||
};
|
||||
ScanQRCodeRoot: {
|
||||
screen: string;
|
||||
params: {
|
||||
isLoading: false;
|
||||
cameraStatusGranted?: boolean;
|
||||
backdoorPressed?: boolean;
|
||||
launchedBy?: string;
|
||||
urTotal?: number;
|
||||
urHave?: number;
|
||||
backdoorText?: string;
|
||||
onDismiss?: () => void;
|
||||
showFileImportButton: true;
|
||||
backdoorVisible?: boolean;
|
||||
animatedQRCodeData?: Record<string, any>;
|
||||
};
|
||||
};
|
||||
ScanQRCode: ScanQRCodeParamList;
|
||||
PaymentCodeList: {
|
||||
paymentCode: string;
|
||||
walletID: string;
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
LNDViewInvoiceComponent,
|
||||
SelectWalletComponent,
|
||||
} from './LazyLoadLNDCreateInvoiceStack';
|
||||
import { ScanQRCodeComponent } from './LazyLoadScanQRCodeStack';
|
||||
|
||||
const Stack = createNativeStackNavigator();
|
||||
|
||||
@ -54,6 +55,16 @@ const LNDCreateInvoiceRoot = () => {
|
||||
component={LNDViewAdditionalInvoicePreImageComponent}
|
||||
options={navigationStyle({ title: loc.lndViewInvoice.additional_info })(theme)}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="ScanQRCode"
|
||||
component={ScanQRCodeComponent}
|
||||
options={navigationStyle({
|
||||
headerShown: false,
|
||||
statusBarHidden: true,
|
||||
presentation: 'fullScreenModal',
|
||||
headerShadowVisible: false,
|
||||
})(theme)}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
SelectWalletComponent,
|
||||
SuccessComponent,
|
||||
} from './LazyLoadScanLndInvoiceStack';
|
||||
import { ScanQRCodeComponent } from './LazyLoadScanQRCodeStack';
|
||||
|
||||
const Stack = createNativeStackNavigator();
|
||||
|
||||
@ -50,6 +51,16 @@ const ScanLndInvoiceRoot = () => {
|
||||
gestureEnabled: false,
|
||||
})(theme)}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="ScanQRCode"
|
||||
component={ScanQRCodeComponent}
|
||||
options={navigationStyle({
|
||||
headerShown: false,
|
||||
statusBarHidden: true,
|
||||
presentation: 'fullScreenModal',
|
||||
headerShadowVisible: false,
|
||||
})(theme)}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
@ -1,28 +0,0 @@
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import React from 'react';
|
||||
|
||||
import navigationStyle from '../components/navigationStyle';
|
||||
import { useTheme } from '../components/themes';
|
||||
import { ScanQRCodeComponent } from './LazyLoadScanQRCodeStack';
|
||||
|
||||
const Stack = createNativeStackNavigator();
|
||||
|
||||
const ScanQRCodeStackRoot = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Stack.Navigator screenOptions={{ headerShadowVisible: false }}>
|
||||
<Stack.Screen
|
||||
name="ScanQRCode"
|
||||
component={ScanQRCodeComponent}
|
||||
options={navigationStyle({
|
||||
headerShown: false,
|
||||
statusBarHidden: true,
|
||||
presentation: 'fullScreenModal',
|
||||
})(theme)}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScanQRCodeStackRoot;
|
@ -18,6 +18,7 @@ import {
|
||||
import { SendDetailsStackParamList } from './SendDetailsStackParamList';
|
||||
import HeaderRightButton from '../components/HeaderRightButton';
|
||||
import { BitcoinUnit } from '../models/bitcoinUnits';
|
||||
import { ScanQRCodeComponent } from './LazyLoadScanQRCodeStack';
|
||||
|
||||
const Stack = createNativeStackNavigator<SendDetailsStackParamList>();
|
||||
|
||||
@ -81,6 +82,16 @@ const SendDetailsStack = () => {
|
||||
component={PaymentCodesListComponent}
|
||||
options={navigationStyle({ title: loc.bip47.contacts })(theme)}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="ScanQRCode"
|
||||
component={ScanQRCodeComponent}
|
||||
options={navigationStyle({
|
||||
headerShown: false,
|
||||
statusBarHidden: true,
|
||||
presentation: 'fullScreenModal',
|
||||
headerShadowVisible: false,
|
||||
})(theme)}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Psbt } from 'bitcoinjs-lib';
|
||||
import { CreateTransactionTarget, CreateTransactionUtxo, TWallet } from '../class/wallets/types';
|
||||
import { BitcoinUnit, Chain } from '../models/bitcoinUnits';
|
||||
import { ScanQRCodeParamList } from './DetailViewStackParamList';
|
||||
|
||||
export type SendDetailsParams = {
|
||||
transactionMemo?: string;
|
||||
@ -12,6 +13,7 @@ export type SendDetailsParams = {
|
||||
address?: string;
|
||||
amount?: number;
|
||||
amountSats?: number;
|
||||
onBarScanned?: string;
|
||||
unit?: BitcoinUnit;
|
||||
noRbf?: boolean;
|
||||
walletID: string;
|
||||
@ -84,4 +86,5 @@ export type SendDetailsStackParamList = {
|
||||
PaymentCodeList: {
|
||||
walletID: string;
|
||||
};
|
||||
ScanQRCode: ScanQRCodeParamList;
|
||||
};
|
||||
|
@ -5,11 +5,15 @@ import navigationStyle from '../components/navigationStyle';
|
||||
import { useTheme } from '../components/themes';
|
||||
import loc from '../loc';
|
||||
import { ViewEditMultisigCosignersComponent } from './LazyLoadViewEditMultisigCosignersStack';
|
||||
import { ScanQRCodeComponent } from './LazyLoadScanQRCodeStack';
|
||||
import { ScanQRCodeParamList } from './DetailViewStackParamList';
|
||||
|
||||
export type ViewEditMultisigCosignersStackParamList = {
|
||||
ViewEditMultisigCosigners: {
|
||||
walletID: string;
|
||||
onBarScanned?: string;
|
||||
};
|
||||
ScanQRCode: ScanQRCodeParamList;
|
||||
};
|
||||
|
||||
const Stack = createNativeStackNavigator<ViewEditMultisigCosignersStackParamList>();
|
||||
@ -27,6 +31,16 @@ const ViewEditMultisigCosignersStackRoot = () => {
|
||||
title: loc.multisig.manage_keys,
|
||||
})(theme)}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="ScanQRCode"
|
||||
component={ScanQRCodeComponent}
|
||||
options={navigationStyle({
|
||||
headerShown: false,
|
||||
statusBarHidden: true,
|
||||
presentation: 'fullScreenModal',
|
||||
headerShadowVisible: false,
|
||||
})(theme)}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
@ -24,7 +24,6 @@ import AmountInput from '../../components/AmountInput';
|
||||
import Button from '../../components/Button';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { presentWalletExportReminder } from '../../helpers/presentWalletExportReminder';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import loc, { formatBalance, formatBalancePlain, formatBalanceWithoutSuffix } from '../../loc';
|
||||
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
|
||||
import * as NavigationService from '../../NavigationService';
|
||||
@ -36,7 +35,7 @@ const LNDCreateInvoice = () => {
|
||||
const { wallets, saveToDisk, setSelectedWalletID } = useStorage();
|
||||
const { walletID, uri } = useRoute().params;
|
||||
const wallet = useRef(wallets.find(item => item.getID() === walletID) || wallets.find(item => item.chain === Chain.OFFCHAIN));
|
||||
const { name } = useRoute();
|
||||
const { params } = useRoute();
|
||||
const { colors } = useTheme();
|
||||
const { navigate, getParent, goBack, pop, setParams } = useNavigation();
|
||||
const [unit, setUnit] = useState(wallet.current?.getPreferredBalanceUnit() || BitcoinUnit.BTC);
|
||||
@ -75,6 +74,100 @@ const LNDCreateInvoice = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const processLnurl = useCallback(
|
||||
async data => {
|
||||
setIsLoading(true);
|
||||
if (!wallet.current) {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
presentAlert({ message: loc.wallets.no_ln_wallet_error });
|
||||
return goBack();
|
||||
}
|
||||
|
||||
// decoding the lnurl
|
||||
const url = Lnurl.getUrlFromLnurl(data);
|
||||
const { query } = parse(url, true);
|
||||
|
||||
if (query.tag === Lnurl.TAG_LOGIN_REQUEST) {
|
||||
navigate('LnurlAuth', {
|
||||
lnurl: data,
|
||||
walletID: walletID ?? wallet.current.getID(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// calling the url
|
||||
try {
|
||||
const resp = await fetch(url, { method: 'GET' });
|
||||
if (resp.status >= 300) {
|
||||
throw new Error('Bad response from server');
|
||||
}
|
||||
const reply = await resp.json();
|
||||
if (reply.status === 'ERROR') {
|
||||
throw new Error('Reply from server: ' + reply.reason);
|
||||
}
|
||||
|
||||
if (reply.tag === Lnurl.TAG_PAY_REQUEST) {
|
||||
// we are here by mistake. user wants to SEND to lnurl-pay, but he is on a screen that creates
|
||||
// invoices (including through lnurl-withdraw)
|
||||
navigate('ScanLndInvoiceRoot', {
|
||||
screen: 'LnurlPay',
|
||||
params: {
|
||||
lnurl: data,
|
||||
walletID: walletID ?? wallet.current.getID(),
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (reply.tag !== Lnurl.TAG_WITHDRAW_REQUEST) {
|
||||
throw new Error('Unsupported lnurl');
|
||||
}
|
||||
|
||||
// amount that comes from lnurl is always in sats
|
||||
let newAmount = (reply.maxWithdrawable / 1000).toString();
|
||||
const sats = newAmount;
|
||||
switch (unit) {
|
||||
case BitcoinUnit.SATS:
|
||||
// nop
|
||||
break;
|
||||
case BitcoinUnit.BTC:
|
||||
newAmount = satoshiToBTC(newAmount);
|
||||
break;
|
||||
case BitcoinUnit.LOCAL_CURRENCY:
|
||||
newAmount = formatBalancePlain(newAmount, BitcoinUnit.LOCAL_CURRENCY);
|
||||
AmountInput.setCachedSatoshis(newAmount, sats);
|
||||
break;
|
||||
}
|
||||
|
||||
// setting the invoice creating screen with the parameters
|
||||
setLNURLParams({
|
||||
k1: reply.k1,
|
||||
callback: reply.callback,
|
||||
fixed: reply.minWithdrawable === reply.maxWithdrawable,
|
||||
min: (reply.minWithdrawable || 0) / 1000,
|
||||
max: reply.maxWithdrawable / 1000,
|
||||
});
|
||||
setAmount(newAmount);
|
||||
setDescription(reply.defaultDescription);
|
||||
setIsLoading(false);
|
||||
} catch (Err) {
|
||||
Keyboard.dismiss();
|
||||
setIsLoading(false);
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
presentAlert({ message: Err.message });
|
||||
}
|
||||
},
|
||||
[goBack, navigate, unit, walletID],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const data = params.onBarScanned;
|
||||
if (data) {
|
||||
processLnurl(data);
|
||||
setParams({ onBarScanned: undefined });
|
||||
}
|
||||
}, [params.onBarScanned, processLnurl, setParams]);
|
||||
|
||||
useEffect(() => {
|
||||
const showSubscription = Keyboard.addListener(Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow', _keyboardDidShow);
|
||||
const hideSubscription = Keyboard.addListener(Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide', _keyboardDidHide);
|
||||
@ -228,89 +321,6 @@ const LNDCreateInvoice = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const processLnurl = async data => {
|
||||
setIsLoading(true);
|
||||
if (!wallet.current) {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
presentAlert({ message: loc.wallets.no_ln_wallet_error });
|
||||
return goBack();
|
||||
}
|
||||
|
||||
// decoding the lnurl
|
||||
const url = Lnurl.getUrlFromLnurl(data);
|
||||
const { query } = parse(url, true);
|
||||
|
||||
if (query.tag === Lnurl.TAG_LOGIN_REQUEST) {
|
||||
navigate('LnurlAuth', {
|
||||
lnurl: data,
|
||||
walletID: walletID ?? wallet.current.getID(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// calling the url
|
||||
try {
|
||||
const resp = await fetch(url, { method: 'GET' });
|
||||
if (resp.status >= 300) {
|
||||
throw new Error('Bad response from server');
|
||||
}
|
||||
const reply = await resp.json();
|
||||
if (reply.status === 'ERROR') {
|
||||
throw new Error('Reply from server: ' + reply.reason);
|
||||
}
|
||||
|
||||
if (reply.tag === Lnurl.TAG_PAY_REQUEST) {
|
||||
// we are here by mistake. user wants to SEND to lnurl-pay, but he is on a screen that creates
|
||||
// invoices (including through lnurl-withdraw)
|
||||
navigate('ScanLndInvoiceRoot', {
|
||||
screen: 'LnurlPay',
|
||||
params: {
|
||||
lnurl: data,
|
||||
walletID: walletID ?? wallet.current.getID(),
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (reply.tag !== Lnurl.TAG_WITHDRAW_REQUEST) {
|
||||
throw new Error('Unsupported lnurl');
|
||||
}
|
||||
|
||||
// amount that comes from lnurl is always in sats
|
||||
let newAmount = (reply.maxWithdrawable / 1000).toString();
|
||||
const sats = newAmount;
|
||||
switch (unit) {
|
||||
case BitcoinUnit.SATS:
|
||||
// nop
|
||||
break;
|
||||
case BitcoinUnit.BTC:
|
||||
newAmount = satoshiToBTC(newAmount);
|
||||
break;
|
||||
case BitcoinUnit.LOCAL_CURRENCY:
|
||||
newAmount = formatBalancePlain(newAmount, BitcoinUnit.LOCAL_CURRENCY);
|
||||
AmountInput.setCachedSatoshis(newAmount, sats);
|
||||
break;
|
||||
}
|
||||
|
||||
// setting the invoice creating screen with the parameters
|
||||
setLNURLParams({
|
||||
k1: reply.k1,
|
||||
callback: reply.callback,
|
||||
fixed: reply.minWithdrawable === reply.maxWithdrawable,
|
||||
min: (reply.minWithdrawable || 0) / 1000,
|
||||
max: reply.maxWithdrawable / 1000,
|
||||
});
|
||||
setAmount(newAmount);
|
||||
setDescription(reply.defaultDescription);
|
||||
setIsLoading(false);
|
||||
} catch (Err) {
|
||||
Keyboard.dismiss();
|
||||
setIsLoading(false);
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
presentAlert({ message: Err.message });
|
||||
}
|
||||
};
|
||||
|
||||
const renderCreateButton = () => {
|
||||
return (
|
||||
<View style={styles.createButton}>
|
||||
@ -320,7 +330,9 @@ const LNDCreateInvoice = () => {
|
||||
};
|
||||
|
||||
const navigateToScanQRCode = () => {
|
||||
scanQrHelper(name, true, processLnurl);
|
||||
navigate('ScanQRCode', {
|
||||
showFileImportButton: true,
|
||||
});
|
||||
Keyboard.dismiss();
|
||||
};
|
||||
|
||||
|
@ -18,13 +18,14 @@ import loc, { formatBalanceWithoutSuffix } from '../../loc';
|
||||
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { DismissKeyboardInputAccessory, DismissKeyboardInputAccessoryViewID } from '../../components/DismissKeyboardInputAccessory';
|
||||
import DeeplinkSchemaMatch from '../../class/deeplink-schema-match';
|
||||
|
||||
const ScanLndInvoice = () => {
|
||||
const { wallets, fetchAndSaveWalletTransactions } = useStorage();
|
||||
const { isBiometricUseCapableAndEnabled } = useBiometrics();
|
||||
const { colors } = useTheme();
|
||||
const route = useRoute();
|
||||
const { walletID, uri, invoice } = useRoute().params;
|
||||
const name = useRoute().name;
|
||||
/** @type {LightningCustodianWallet} */
|
||||
const [wallet, setWallet] = useState(
|
||||
wallets.find(item => item.getID() === walletID) || wallets.find(item => item.chain === Chain.OFFCHAIN),
|
||||
@ -281,6 +282,25 @@ const ScanLndInvoice = () => {
|
||||
pop();
|
||||
};
|
||||
|
||||
const onBarScanned = useCallback(
|
||||
value => {
|
||||
if (!value) return;
|
||||
DeeplinkSchemaMatch.navigationRouteFor({ url: value }, completionValue => {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
|
||||
navigate(...completionValue);
|
||||
});
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const data = route.params?.onBarScanned;
|
||||
if (data) {
|
||||
onBarScanned(data);
|
||||
setParams({ onBarScanned: undefined });
|
||||
}
|
||||
}, [navigate, onBarScanned, route.params?.onBarScanned, setParams]);
|
||||
|
||||
if (wallet === undefined || !wallet) {
|
||||
return (
|
||||
<View style={[styles.loadingIndicator, stylesHook.root]}>
|
||||
@ -323,7 +343,6 @@ const ScanLndInvoice = () => {
|
||||
isLoading={isLoading}
|
||||
placeholder={loc.lnd.placeholder}
|
||||
inputAccessoryViewID={DismissKeyboardInputAccessoryViewID}
|
||||
launchedBy={name}
|
||||
onBlur={onBlur}
|
||||
keyboardType="email-address"
|
||||
style={styles.addressInput}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useRoute, RouteProp } from '@react-navigation/native';
|
||||
import * as bitcoin from 'bitcoinjs-lib';
|
||||
import { ActivityIndicator, Keyboard, Linking, StyleSheet, TextInput, View } from 'react-native';
|
||||
@ -19,11 +19,12 @@ import presentAlert from '../../components/Alert';
|
||||
import Button from '../../components/Button';
|
||||
import SafeArea from '../../components/SafeArea';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import loc from '../../loc';
|
||||
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
import { majorTomToGroundControl } from '../../blue_modules/notifications';
|
||||
import { navigate } from '../../NavigationService';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
|
||||
const BROADCAST_RESULT = Object.freeze({
|
||||
none: 'Input transaction hex',
|
||||
@ -35,12 +36,13 @@ const BROADCAST_RESULT = Object.freeze({
|
||||
type RouteProps = RouteProp<DetailViewStackParamList, 'Broadcast'>;
|
||||
|
||||
const Broadcast: React.FC = () => {
|
||||
const { name, params } = useRoute<RouteProps>();
|
||||
const { params } = useRoute<RouteProps>();
|
||||
const [tx, setTx] = useState<string | undefined>();
|
||||
const [txHex, setTxHex] = useState<string | undefined>();
|
||||
const { colors } = useTheme();
|
||||
const [broadcastResult, setBroadcastResult] = useState<string>(BROADCAST_RESULT.none);
|
||||
const { selectedBlockExplorer } = useSettings();
|
||||
const { setParams } = useExtendedNavigation();
|
||||
|
||||
const stylesHooks = StyleSheet.create({
|
||||
input: {
|
||||
@ -50,13 +52,26 @@ const Broadcast: React.FC = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const handleScannedData = useCallback((scannedData: string) => {
|
||||
if (scannedData.indexOf('+') === -1 && scannedData.indexOf('=') === -1 && scannedData.indexOf('=') === -1) {
|
||||
// this looks like NOT base64, so maybe its transaction's hex
|
||||
return handleUpdateTxHex(scannedData);
|
||||
}
|
||||
|
||||
try {
|
||||
// should be base64 encoded PSBT
|
||||
const validTx = bitcoin.Psbt.fromBase64(scannedData).extractTransaction();
|
||||
return handleUpdateTxHex(validTx.toHex());
|
||||
} catch (e) {}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const scannedData = params?.scannedData;
|
||||
const scannedData = params?.onBarScanned;
|
||||
if (scannedData) {
|
||||
handleScannedData(scannedData);
|
||||
setParams({ onBarScanned: undefined });
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [params?.scannedData]);
|
||||
}, [handleScannedData, params?.onBarScanned, setParams]);
|
||||
|
||||
const handleUpdateTxHex = (nextValue: string) => setTxHex(nextValue.trim());
|
||||
|
||||
@ -88,21 +103,8 @@ const Broadcast: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleScannedData = (scannedData: string) => {
|
||||
if (scannedData.indexOf('+') === -1 && scannedData.indexOf('=') === -1 && scannedData.indexOf('=') === -1) {
|
||||
// this looks like NOT base64, so maybe its transaction's hex
|
||||
return handleUpdateTxHex(scannedData);
|
||||
}
|
||||
|
||||
try {
|
||||
// should be base64 encoded PSBT
|
||||
const validTx = bitcoin.Psbt.fromBase64(scannedData).extractTransaction();
|
||||
return handleUpdateTxHex(validTx.toHex());
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
const handleQRScan = () => {
|
||||
scanQrHelper(name, true, undefined, false);
|
||||
navigate('ScanQRCode');
|
||||
};
|
||||
|
||||
let status;
|
||||
|
@ -5,20 +5,16 @@ import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { Alert, Image, Platform, StyleSheet, TextInput, TouchableOpacity, View } from 'react-native';
|
||||
import { CameraScreen } from 'react-native-camera-kit';
|
||||
import { Icon } from '@rneui/themed';
|
||||
import { launchImageLibrary } from 'react-native-image-picker';
|
||||
|
||||
import Base43 from '../../blue_modules/base43';
|
||||
import * as fs from '../../blue_modules/fs';
|
||||
import { BlueURDecoder, decodeUR, extractSingleWorkload } from '../../blue_modules/ur';
|
||||
import { BlueLoading, BlueSpacing40, BlueText } from '../../BlueComponents';
|
||||
import { openPrivacyDesktopSettings } from '../../class/camera';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import Button from '../../components/Button';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { isCameraAuthorizationStatusGranted } from '../../helpers/scan-qr';
|
||||
import loc from '../../loc';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
import RNQRGenerator from 'rn-qr-generator';
|
||||
|
||||
let decoder = false;
|
||||
|
||||
@ -89,7 +85,11 @@ const ScanQRCode = () => {
|
||||
const { setIsDrawerShouldHide } = useSettings();
|
||||
const navigation = useNavigation();
|
||||
const route = useRoute();
|
||||
const { launchedBy, onBarScanned, onDismiss, showFileImportButton } = route.params;
|
||||
const navigationState = navigation.getState();
|
||||
const previousRoute = navigationState.routes[navigationState.routes.length - 2];
|
||||
const defaultLaunchedBy = previousRoute ? previousRoute.name : undefined;
|
||||
|
||||
const { launchedBy = defaultLaunchedBy, onBarScanned, onDismiss, showFileImportButton } = route.params || {};
|
||||
const scannedCache = {};
|
||||
const { colors } = useTheme();
|
||||
const isFocused = useIsFocused();
|
||||
@ -139,13 +139,11 @@ const ScanQRCode = () => {
|
||||
const data = decoder.toString();
|
||||
decoder = false; // nullify for future use (?)
|
||||
if (launchedBy) {
|
||||
let merge = true;
|
||||
if (typeof onBarScanned !== 'function') {
|
||||
merge = false;
|
||||
}
|
||||
navigation.navigate({ name: launchedBy, params: { scannedData: data }, merge });
|
||||
const merge = true;
|
||||
navigation.navigate({ name: launchedBy, params: { onBarScanned: data }, merge });
|
||||
} else {
|
||||
onBarScanned && onBarScanned({ data });
|
||||
}
|
||||
onBarScanned && onBarScanned({ data });
|
||||
} else {
|
||||
setUrTotal(100);
|
||||
setUrHave(Math.floor(decoder.estimatedPercentComplete() * 100));
|
||||
@ -192,13 +190,11 @@ const ScanQRCode = () => {
|
||||
data = Buffer.from(payload, 'hex').toString();
|
||||
}
|
||||
if (launchedBy) {
|
||||
let merge = true;
|
||||
if (typeof onBarScanned !== 'function') {
|
||||
merge = false;
|
||||
}
|
||||
navigation.navigate({ name: launchedBy, params: { scannedData: data }, merge });
|
||||
const merge = true;
|
||||
navigation.navigate({ name: launchedBy, params: { onBarScanned: data }, merge });
|
||||
} else {
|
||||
onBarScanned && onBarScanned({ data });
|
||||
}
|
||||
onBarScanned && onBarScanned({ data });
|
||||
} else {
|
||||
setAnimatedQRCodeData(animatedQRCodeData);
|
||||
}
|
||||
@ -259,13 +255,12 @@ const ScanQRCode = () => {
|
||||
bitcoin.Psbt.fromHex(hex); // if it doesnt throw - all good
|
||||
const data = Buffer.from(hex, 'hex').toString('base64');
|
||||
if (launchedBy) {
|
||||
let merge = true;
|
||||
if (typeof onBarScanned !== 'function') {
|
||||
merge = false;
|
||||
}
|
||||
navigation.navigate({ name: launchedBy, params: { scannedData: data }, merge });
|
||||
const merge = true;
|
||||
|
||||
navigation.navigate({ name: launchedBy, params: { onBarScanned: data }, merge });
|
||||
} else {
|
||||
onBarScanned && onBarScanned({ data });
|
||||
}
|
||||
onBarScanned && onBarScanned({ data });
|
||||
return;
|
||||
} catch (_) {}
|
||||
|
||||
@ -273,13 +268,12 @@ const ScanQRCode = () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (launchedBy) {
|
||||
let merge = true;
|
||||
if (typeof onBarScanned !== 'function') {
|
||||
merge = false;
|
||||
}
|
||||
navigation.navigate({ name: launchedBy, params: { scannedData: ret.data }, merge });
|
||||
const merge = true;
|
||||
|
||||
navigation.navigate({ name: launchedBy, params: { onBarScanned: ret.data }, merge });
|
||||
} else {
|
||||
onBarScanned && onBarScanned(ret.data);
|
||||
}
|
||||
onBarScanned && onBarScanned(ret.data);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
@ -297,42 +291,11 @@ const ScanQRCode = () => {
|
||||
const showImagePicker = () => {
|
||||
if (!isLoading) {
|
||||
setIsLoading(true);
|
||||
launchImageLibrary(
|
||||
{
|
||||
title: null,
|
||||
mediaType: 'photo',
|
||||
takePhotoButtonTitle: null,
|
||||
maxHeight: 800,
|
||||
maxWidth: 600,
|
||||
selectionLimit: 1,
|
||||
},
|
||||
response => {
|
||||
if (response.didCancel) {
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
const asset = response.assets[0];
|
||||
if (asset.uri) {
|
||||
RNQRGenerator.detect({
|
||||
uri: decodeURI(asset.uri.toString()),
|
||||
})
|
||||
.then(result => {
|
||||
if (result) {
|
||||
onBarCodeRead({ data: result.values[0] });
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
presentAlert({ message: loc.send.qr_error_no_qrcode });
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
fs.showImagePickerAndReadImage()
|
||||
.then(data => {
|
||||
if (data) onBarCodeRead({ data });
|
||||
})
|
||||
.finally(() => setIsLoading(false));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { RouteProp, StackActions, useFocusEffect, useRoute } from '@react-navigation/native';
|
||||
import { RouteProp, useFocusEffect, useRoute } from '@react-navigation/native';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import * as bitcoin from 'bitcoinjs-lib';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
@ -39,7 +39,6 @@ import Button from '../../components/Button';
|
||||
import CoinsSelected from '../../components/CoinsSelected';
|
||||
import InputAccessoryAllFunds, { InputAccessoryAllFundsAccessoryViewID } from '../../components/InputAccessoryAllFunds';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import loc, { formatBalance, formatBalanceWithoutSuffix } from '../../loc';
|
||||
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
|
||||
import NetworkTransactionFees, { NetworkTransactionFee } from '../../models/networkTransactionFees';
|
||||
@ -56,7 +55,7 @@ import { useKeyboard } from '../../hooks/useKeyboard';
|
||||
import { DismissKeyboardInputAccessory, DismissKeyboardInputAccessoryViewID } from '../../components/DismissKeyboardInputAccessory';
|
||||
import ActionSheet from '../ActionSheet';
|
||||
import HeaderMenuButton from '../../components/HeaderMenuButton';
|
||||
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
|
||||
import { CommonToolTipActions, ToolTipAction } from '../../typings/CommonToolTipActions';
|
||||
import { Action } from '../../components/types';
|
||||
|
||||
interface IPaymentDestinations {
|
||||
@ -79,6 +78,7 @@ type RouteProps = RouteProp<SendDetailsStackParamList, 'SendDetails'>;
|
||||
const SendDetails = () => {
|
||||
const { wallets, setSelectedWalletID, sleep, txMetadata, saveToDisk } = useStorage();
|
||||
const navigation = useExtendedNavigation<NavigationProps>();
|
||||
const selectedDataProcessor = useRef<ToolTipAction | undefined>();
|
||||
const setParams = navigation.setParams;
|
||||
const route = useRoute<RouteProps>();
|
||||
const name = route.name;
|
||||
@ -93,7 +93,6 @@ const SendDetails = () => {
|
||||
const scrollView = useRef<FlatList<any>>(null);
|
||||
const scrollIndex = useRef(0);
|
||||
const { colors } = useTheme();
|
||||
const popAction = StackActions.pop(1);
|
||||
|
||||
// state
|
||||
const [width, setWidth] = useState(Dimensions.get('window').width);
|
||||
@ -665,34 +664,35 @@ const SendDetails = () => {
|
||||
return presentAlert({ title: loc.errors.error, message: 'Importing transaction in non-watchonly wallet (this should never happen)' });
|
||||
}
|
||||
|
||||
const data = await scanQrHelper(route.name, true);
|
||||
importQrTransactionOnBarScanned(data);
|
||||
navigateToQRCodeScanner();
|
||||
};
|
||||
|
||||
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?
|
||||
const importQrTransactionOnBarScanned = useCallback(
|
||||
(ret: any) => {
|
||||
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);
|
||||
// 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,
|
||||
walletID: wallet.getID(),
|
||||
psbt,
|
||||
});
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
navigation.navigate('PsbtWithHardwareWallet', {
|
||||
memo: transactionMemo,
|
||||
walletID: wallet.getID(),
|
||||
psbt,
|
||||
});
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[navigation, transactionMemo, wallet],
|
||||
);
|
||||
|
||||
/**
|
||||
* watch-only wallets with enabled HW wallet support have different flow. we have to show PSBT to user as QR code
|
||||
@ -776,53 +776,126 @@ const SendDetails = () => {
|
||||
});
|
||||
};
|
||||
|
||||
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
|
||||
const _importTransactionMultisig = useCallback(
|
||||
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);
|
||||
}
|
||||
if ((wallet as MultisigHDWallet)?.howManySignaturesCanWeMake() > 0 && (await askCosignThisTransaction())) {
|
||||
setIsLoading(true);
|
||||
await sleep(100);
|
||||
(wallet as MultisigHDWallet).cosignPsbt(psbt);
|
||||
setIsLoading(false);
|
||||
await sleep(100);
|
||||
}
|
||||
|
||||
if (wallet) {
|
||||
navigation.navigate('PsbtMultisig', {
|
||||
memo: transactionMemo,
|
||||
psbtBase64: psbt.toBase64(),
|
||||
walletID: wallet.getID(),
|
||||
});
|
||||
if (wallet) {
|
||||
navigation.navigate('PsbtMultisig', {
|
||||
memo: transactionMemo,
|
||||
psbtBase64: psbt.toBase64(),
|
||||
walletID: wallet.getID(),
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
presentAlert({ title: loc.send.problem_with_psbt, message: error.message });
|
||||
}
|
||||
} catch (error: any) {
|
||||
presentAlert({ title: loc.send.problem_with_psbt, message: error.message });
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
setIsLoading(false);
|
||||
},
|
||||
[navigation, sleep, transactionMemo, wallet],
|
||||
);
|
||||
|
||||
const importTransactionMultisig = () => {
|
||||
return _importTransactionMultisig(false);
|
||||
};
|
||||
|
||||
const onBarScanned = (ret: any) => {
|
||||
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 onBarScanned = useCallback(
|
||||
(ret: any) => {
|
||||
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);
|
||||
}
|
||||
},
|
||||
[_importTransactionMultisig],
|
||||
);
|
||||
|
||||
const importTransactionMultisigScanQr = async () => {
|
||||
const data = await scanQrHelper(route.name, true);
|
||||
onBarScanned(data);
|
||||
const handlePsbtSign = useCallback(
|
||||
async (psbtBase64: string) => {
|
||||
let tx;
|
||||
let psbt;
|
||||
try {
|
||||
psbt = bitcoin.Psbt.fromBase64(psbtBase64);
|
||||
tx = (wallet as MultisigHDWallet).cosignPsbt(psbt).tx;
|
||||
} catch (e: any) {
|
||||
presentAlert({ title: loc.errors.error, message: e.message });
|
||||
return;
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
if (!tx || !wallet) return setIsLoading(false);
|
||||
|
||||
// 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++) {
|
||||
// @ts-ignore hacky
|
||||
changeAddresses.push(wallet._getInternalAddressByIndex(c));
|
||||
}
|
||||
const recipients = psbt.txOutputs.filter(({ address }) => !changeAddresses.includes(String(address)));
|
||||
|
||||
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,
|
||||
});
|
||||
},
|
||||
[navigation, wallet],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const data = routeParams.onBarScanned;
|
||||
if (data) {
|
||||
if (selectedDataProcessor.current) {
|
||||
if (
|
||||
selectedDataProcessor.current === CommonToolTipActions.ImportTransactionQR ||
|
||||
selectedDataProcessor.current === CommonToolTipActions.CoSignTransaction ||
|
||||
selectedDataProcessor.current === CommonToolTipActions.SignPSBT
|
||||
) {
|
||||
if (selectedDataProcessor.current === CommonToolTipActions.ImportTransactionQR) {
|
||||
importQrTransactionOnBarScanned(data);
|
||||
} else if (
|
||||
selectedDataProcessor.current === CommonToolTipActions.CoSignTransaction ||
|
||||
selectedDataProcessor.current === CommonToolTipActions.SignPSBT
|
||||
) {
|
||||
handlePsbtSign(data);
|
||||
} else {
|
||||
onBarScanned(data);
|
||||
}
|
||||
} else {
|
||||
console.log('Unknown selectedDataProcessor:', selectedDataProcessor.current);
|
||||
}
|
||||
}
|
||||
setParams({ onBarScanned: undefined });
|
||||
}
|
||||
}, [handlePsbtSign, importQrTransactionOnBarScanned, onBarScanned, routeParams.onBarScanned, setParams]);
|
||||
|
||||
const navigateToQRCodeScanner = () => {
|
||||
navigation.navigate('ScanQRCode', {
|
||||
showFileImportButton: true,
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddRecipient = () => {
|
||||
@ -891,48 +964,6 @@ const SendDetails = () => {
|
||||
navigation.navigate('PaymentCodeList', { walletID: wallet.getID() });
|
||||
};
|
||||
|
||||
const handlePsbtSign = async () => {
|
||||
setIsLoading(true);
|
||||
await new Promise(resolve => setTimeout(resolve, 100)); // sleep for animations
|
||||
|
||||
const scannedData = await scanQrHelper(name, true, undefined);
|
||||
if (!scannedData) return setIsLoading(false);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (!tx || !wallet) return setIsLoading(false);
|
||||
|
||||
// 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++) {
|
||||
// @ts-ignore hacky
|
||||
changeAddresses.push(wallet._getInternalAddressByIndex(c));
|
||||
}
|
||||
const recipients = psbt.txOutputs.filter(({ address }) => !changeAddresses.includes(String(address)));
|
||||
|
||||
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,
|
||||
});
|
||||
};
|
||||
|
||||
// Header Right Button
|
||||
|
||||
const headerRightOnPress = (id: string) => {
|
||||
@ -941,7 +972,8 @@ const SendDetails = () => {
|
||||
} else if (id === CommonToolTipActions.RemoveRecipient.id) {
|
||||
handleRemoveRecipient();
|
||||
} else if (id === CommonToolTipActions.SignPSBT.id) {
|
||||
handlePsbtSign();
|
||||
selectedDataProcessor.current = CommonToolTipActions.SignPSBT;
|
||||
navigateToQRCodeScanner();
|
||||
} else if (id === CommonToolTipActions.SendMax.id) {
|
||||
onUseAllPressed();
|
||||
} else if (id === CommonToolTipActions.AllowRBF.id) {
|
||||
@ -949,11 +981,13 @@ const SendDetails = () => {
|
||||
} else if (id === CommonToolTipActions.ImportTransaction.id) {
|
||||
importTransaction();
|
||||
} else if (id === CommonToolTipActions.ImportTransactionQR.id) {
|
||||
selectedDataProcessor.current = CommonToolTipActions.ImportTransactionQR;
|
||||
importQrTransaction();
|
||||
} else if (id === CommonToolTipActions.ImportTransactionMultsig.id) {
|
||||
importTransactionMultisig();
|
||||
} else if (id === CommonToolTipActions.CoSignTransaction.id) {
|
||||
importTransactionMultisigScanQr();
|
||||
selectedDataProcessor.current = CommonToolTipActions.CoSignTransaction;
|
||||
navigateToQRCodeScanner();
|
||||
} else if (id === CommonToolTipActions.CoinControl.id) {
|
||||
handleCoinControl();
|
||||
} else if (id === CommonToolTipActions.InsertContact.id) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useIsFocused, useNavigation, useRoute } from '@react-navigation/native';
|
||||
import * as bitcoin from 'bitcoinjs-lib';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { ActivityIndicator, ScrollView, StyleSheet, View } from 'react-native';
|
||||
|
||||
import { BlueSpacing20 } from '../../BlueComponents';
|
||||
@ -10,15 +10,14 @@ import SafeArea from '../../components/SafeArea';
|
||||
import SaveFileButton from '../../components/SaveFileButton';
|
||||
import { SquareButton } from '../../components/SquareButton';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import loc from '../../loc';
|
||||
|
||||
const PsbtMultisigQRCode = () => {
|
||||
const { navigate } = useNavigation();
|
||||
const { colors } = useTheme();
|
||||
const openScannerButton = useRef();
|
||||
const { psbtBase64, isShowOpenScanner } = useRoute().params;
|
||||
const { name } = useRoute();
|
||||
const { params } = useRoute();
|
||||
const { psbtBase64, isShowOpenScanner } = params;
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const dynamicQRCode = useRef();
|
||||
const isFocused = useIsFocused();
|
||||
@ -45,23 +44,34 @@ const PsbtMultisigQRCode = () => {
|
||||
}
|
||||
}, [isFocused]);
|
||||
|
||||
const onBarScanned = ret => {
|
||||
if (!ret.data) ret = { data: ret };
|
||||
if (ret.data.toUpperCase().startsWith('UR')) {
|
||||
presentAlert({ 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
|
||||
presentAlert({ message: loc.wallets.import_error });
|
||||
} else {
|
||||
// psbt base64?
|
||||
navigate({ name: 'PsbtMultisig', params: { receivedPSBTBase64: ret.data }, merge: true });
|
||||
}
|
||||
};
|
||||
const onBarScanned = useCallback(
|
||||
ret => {
|
||||
if (!ret.data) ret = { data: ret };
|
||||
if (ret.data.toUpperCase().startsWith('UR')) {
|
||||
presentAlert({ 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
|
||||
presentAlert({ message: loc.wallets.import_error });
|
||||
} else {
|
||||
// psbt base64?
|
||||
navigate({ name: 'PsbtMultisig', params: { receivedPSBTBase64: ret.data }, merge: true });
|
||||
}
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const openScanner = async () => {
|
||||
const scanned = await scanQrHelper(name, true);
|
||||
onBarScanned({ data: scanned });
|
||||
useEffect(() => {
|
||||
const data = params.onBarScanned;
|
||||
if (data) {
|
||||
onBarScanned({ data });
|
||||
}
|
||||
}, [onBarScanned, params.onBarScanned]);
|
||||
|
||||
const openScanner = () => {
|
||||
navigate('ScanQRCode', {
|
||||
showFileImportButton: true,
|
||||
});
|
||||
};
|
||||
|
||||
const saveFileButtonBeforeOnPress = () => {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import { useIsFocused, useRoute } from '@react-navigation/native';
|
||||
import * as bitcoin from 'bitcoinjs-lib';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { ActivityIndicator, Linking, Platform, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
||||
import DocumentPicker from 'react-native-document-picker';
|
||||
import RNFS from 'react-native-fs';
|
||||
@ -14,7 +14,6 @@ import { DynamicQRCode } from '../../components/DynamicQRCode';
|
||||
import SaveFileButton from '../../components/SaveFileButton';
|
||||
import { SecondButton } from '../../components/SecondButton';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import { useBiometrics, unlockWithBiometrics } from '../../hooks/useBiometrics';
|
||||
import loc from '../../loc';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
@ -62,34 +61,40 @@ const PsbtWithHardwareWallet = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const _combinePSBT = receivedPSBT => {
|
||||
return wallet.combinePsbt(psbt, receivedPSBT);
|
||||
};
|
||||
const _combinePSBT = useCallback(
|
||||
receivedPSBT => {
|
||||
return wallet.combinePsbt(psbt, receivedPSBT);
|
||||
},
|
||||
[psbt, wallet],
|
||||
);
|
||||
|
||||
const onBarScanned = ret => {
|
||||
if (ret && !ret.data) ret = { data: ret };
|
||||
if (ret.data.toUpperCase().startsWith('UR')) {
|
||||
presentAlert({ message: 'BC-UR not decoded. This should never happen' });
|
||||
}
|
||||
if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) {
|
||||
// this looks like NOT base64, so maybe its transaction's hex
|
||||
setTxHex(ret.data);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const Tx = _combinePSBT(ret.data);
|
||||
setTxHex(Tx.toHex());
|
||||
if (launchedBy) {
|
||||
// we must navigate back to the screen who requested psbt (instead of broadcasting it ourselves)
|
||||
// most likely for LN channel opening
|
||||
navigation.navigate({ name: launchedBy, params: { psbt }, merge: true });
|
||||
// ^^^ we just use `psbt` variable sinse it was finalized in the above _combinePSBT()
|
||||
// (passed by reference)
|
||||
const onBarScanned = useCallback(
|
||||
ret => {
|
||||
if (ret && !ret.data) ret = { data: ret };
|
||||
if (ret.data.toUpperCase().startsWith('UR')) {
|
||||
presentAlert({ message: 'BC-UR not decoded. This should never happen' });
|
||||
}
|
||||
} catch (Err) {
|
||||
presentAlert({ message: Err.message });
|
||||
}
|
||||
};
|
||||
if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) {
|
||||
// this looks like NOT base64, so maybe its transaction's hex
|
||||
setTxHex(ret.data);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const Tx = _combinePSBT(ret.data);
|
||||
setTxHex(Tx.toHex());
|
||||
if (launchedBy) {
|
||||
// we must navigate back to the screen who requested psbt (instead of broadcasting it ourselves)
|
||||
// most likely for LN channel opening
|
||||
navigation.navigate({ name: launchedBy, params: { psbt }, merge: true });
|
||||
// ^^^ we just use `psbt` variable sinse it was finalized in the above _combinePSBT()
|
||||
// (passed by reference)
|
||||
}
|
||||
} catch (Err) {
|
||||
presentAlert({ message: Err.message });
|
||||
}
|
||||
},
|
||||
[_combinePSBT, launchedBy, navigation, psbt],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isFocused) {
|
||||
@ -217,11 +222,18 @@ const PsbtWithHardwareWallet = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const openScanner = async () => {
|
||||
const data = await scanQrHelper(route.name, true);
|
||||
useEffect(() => {
|
||||
const data = route.params.onBarScanned;
|
||||
if (data) {
|
||||
onBarScanned(data);
|
||||
navigation.setParams({ onBarScanned: undefined });
|
||||
}
|
||||
}, [navigation, onBarScanned, route.params.onBarScanned]);
|
||||
|
||||
const openScanner = async () => {
|
||||
navigation.navigate('ScanQRCode', {
|
||||
showFileImportButton: true,
|
||||
});
|
||||
};
|
||||
|
||||
if (txHex) return _renderBroadcastHex();
|
||||
|
@ -6,7 +6,6 @@ import { BlueCard, BlueSpacing10, BlueSpacing20, BlueText } from '../../BlueComp
|
||||
import DeeplinkSchemaMatch from '../../class/deeplink-schema-match';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import Button from '../../components/Button';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import loc from '../../loc';
|
||||
import {
|
||||
DoneAndDismissKeyboardInputAccessory,
|
||||
@ -42,8 +41,9 @@ const DELETE_PREFIX = 'delete_';
|
||||
|
||||
const ElectrumSettings: React.FC = () => {
|
||||
const { colors } = useTheme();
|
||||
const { server } = useRoute<RouteProps>().params;
|
||||
const { setOptions } = useExtendedNavigation();
|
||||
const params = useRoute<RouteProps>().params;
|
||||
const { server } = params;
|
||||
const navigation = useExtendedNavigation();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [serverHistory, setServerHistory] = useState<Set<ElectrumServerItem>>(new Set());
|
||||
const [config, setConfig] = useState<{ connected?: number; host?: string; port?: string }>({});
|
||||
@ -423,10 +423,10 @@ const ElectrumSettings: React.FC = () => {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setOptions({
|
||||
navigation.setOptions({
|
||||
headerRight: isElectrumDisabled ? null : () => HeaderRight,
|
||||
});
|
||||
}, [HeaderRight, isElectrumDisabled, setOptions]);
|
||||
}, [HeaderRight, isElectrumDisabled, navigation]);
|
||||
|
||||
const checkServer = async () => {
|
||||
setIsLoading(true);
|
||||
@ -458,12 +458,17 @@ const ElectrumSettings: React.FC = () => {
|
||||
};
|
||||
|
||||
const importScan = async () => {
|
||||
const scanned = await scanQrHelper('ElectrumSettings', true);
|
||||
if (scanned) {
|
||||
onBarScanned(scanned);
|
||||
}
|
||||
navigation.navigate('ScanQRCode');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const data = params.onBarScanned;
|
||||
if (data) {
|
||||
onBarScanned(data);
|
||||
navigation.setParams({ onBarScanned: undefined });
|
||||
}
|
||||
}, [navigation, params.onBarScanned]);
|
||||
|
||||
const onSSLPortChange = (value: boolean) => {
|
||||
Keyboard.dismiss();
|
||||
if (value) {
|
||||
|
@ -4,7 +4,6 @@ import { Keyboard, StyleSheet, TextInput, View, ScrollView, TouchableOpacity, Te
|
||||
import { BlueButtonLink, BlueCard, BlueSpacing10, BlueSpacing20, BlueSpacing40, BlueText } from '../../BlueComponents';
|
||||
import Button from '../../components/Button';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import loc from '../../loc';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { TWallet } from '../../class/wallets/types';
|
||||
@ -15,6 +14,7 @@ import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import { Divider } from '@rneui/themed';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import { navigate } from '../../NavigationService';
|
||||
|
||||
type RouteProps = RouteProp<DetailViewStackParamList, 'IsItMyAddress'>;
|
||||
type NavigationProp = NativeStackNavigationProp<DetailViewStackParamList, 'IsItMyAddress'>;
|
||||
@ -109,11 +109,16 @@ const IsItMyAddress: React.FC = () => {
|
||||
};
|
||||
|
||||
const importScan = async () => {
|
||||
const data = await scanQrHelper(route.name, true, undefined, true);
|
||||
navigate('ScanQRCode');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const data = route.params?.onBarScanned;
|
||||
if (data) {
|
||||
onBarScanned(data);
|
||||
navigation.setParams({ onBarScanned: undefined });
|
||||
}
|
||||
};
|
||||
}, [navigation, route.name, route.params?.onBarScanned]);
|
||||
|
||||
const viewQRCode = () => {
|
||||
if (!resultCleanAddress) return;
|
||||
|
@ -9,11 +9,12 @@ import { LightningCustodianWallet } from '../../class/wallets/lightning-custodia
|
||||
import presentAlert, { AlertType } from '../../components/Alert';
|
||||
import { Button } from '../../components/Button';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import loc from '../../loc';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import { GROUP_IO_BLUEWALLET } from '../../blue_modules/currency';
|
||||
import { clearLNDHub, getLNDHub, setLNDHub } from '../../helpers/lndHub';
|
||||
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
uri: {
|
||||
@ -38,21 +39,14 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
type LightingSettingsRouteProps = RouteProp<
|
||||
{
|
||||
params?: {
|
||||
url?: string;
|
||||
};
|
||||
},
|
||||
'params'
|
||||
>;
|
||||
type LightingSettingsRouteProps = RouteProp<DetailViewStackParamList, 'LightningSettings'>;
|
||||
|
||||
const LightningSettings: React.FC = () => {
|
||||
const params = useRoute<LightingSettingsRouteProps>().params;
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [URI, setURI] = useState<string>();
|
||||
const { colors } = useTheme();
|
||||
const route = useRoute();
|
||||
const { navigate, setParams } = useExtendedNavigation();
|
||||
const styleHook = StyleSheet.create({
|
||||
uri: {
|
||||
borderColor: colors.formBorder,
|
||||
@ -106,38 +100,43 @@ const LightningSettings: React.FC = () => {
|
||||
|
||||
setURI(typeof setLndHubUrl === 'string' ? setLndHubUrl.trim() : value.trim());
|
||||
};
|
||||
const save = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
|
||||
if (URI) {
|
||||
const normalizedURI = new URL(URI.replace(/([^:]\/)\/+/g, '$1')).toString();
|
||||
const save = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
|
||||
if (URI) {
|
||||
const normalizedURI = new URL(URI.replace(/([^:]\/)\/+/g, '$1')).toString();
|
||||
await LightningCustodianWallet.isValidNodeAddress(normalizedURI);
|
||||
|
||||
await LightningCustodianWallet.isValidNodeAddress(normalizedURI);
|
||||
|
||||
await setLNDHub(normalizedURI);
|
||||
} else {
|
||||
await clearLNDHub();
|
||||
}
|
||||
|
||||
presentAlert({ message: loc.settings.lightning_saved, type: AlertType.Toast });
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
|
||||
} catch (error) {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
presentAlert({ message: loc.settings.lightning_error_lndhub_uri });
|
||||
console.log(error);
|
||||
await setLNDHub(normalizedURI);
|
||||
} else {
|
||||
await clearLNDHub();
|
||||
}
|
||||
setIsLoading(false);
|
||||
}, [URI]);
|
||||
|
||||
presentAlert({ message: loc.settings.lightning_saved, type: AlertType.Toast });
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
|
||||
} catch (error) {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
presentAlert({ message: loc.settings.lightning_error_lndhub_uri });
|
||||
console.log(error);
|
||||
}
|
||||
setIsLoading(false);
|
||||
}, [URI]);
|
||||
|
||||
const importScan = () => {
|
||||
scanQrHelper(route.name).then(data => {
|
||||
if (data) {
|
||||
setLndhubURI(data);
|
||||
}
|
||||
navigate('ScanQRCode', {
|
||||
showFileImportButton: true,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const data = params?.onBarScanned;
|
||||
if (data) {
|
||||
setLndhubURI(data);
|
||||
setParams({ onBarScanned: undefined });
|
||||
}
|
||||
}, [params?.onBarScanned, setParams]);
|
||||
|
||||
return (
|
||||
<ScrollView automaticallyAdjustContentInsets contentInsetAdjustmentBehavior="automatic">
|
||||
<BlueCard>
|
||||
|
@ -11,7 +11,6 @@ import {
|
||||
} from '../../components/DoneAndDismissKeyboardInputAccessory';
|
||||
import HeaderMenuButton from '../../components/HeaderMenuButton';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
import { useKeyboard } from '../../hooks/useKeyboard';
|
||||
@ -30,7 +29,6 @@ const ImportWallet = () => {
|
||||
const route = useRoute<RouteProps>();
|
||||
const label = route?.params?.label ?? '';
|
||||
const triggerImport = route?.params?.triggerImport ?? false;
|
||||
const scannedData = route?.params?.scannedData ?? '';
|
||||
const [importText, setImportText] = useState<string>(label);
|
||||
const [isToolbarVisibleForAndroid, setIsToolbarVisibleForAndroid] = useState<boolean>(false);
|
||||
const [, setSpeedBackdoor] = useState<number>(0);
|
||||
@ -108,11 +106,18 @@ const ImportWallet = () => {
|
||||
);
|
||||
|
||||
const importScan = useCallback(async () => {
|
||||
const data = await scanQrHelper(route.name, true);
|
||||
navigation.navigate('ScanQRCode', {
|
||||
showFileImportButton: true,
|
||||
});
|
||||
}, [navigation]);
|
||||
|
||||
useEffect(() => {
|
||||
const data = route.params?.onBarScanned;
|
||||
if (data) {
|
||||
onBarScanned(data);
|
||||
navigation.setParams({ onBarScanned: undefined });
|
||||
}
|
||||
}, [route.name, onBarScanned]);
|
||||
}, [route.name, onBarScanned, route.params?.onBarScanned, navigation]);
|
||||
|
||||
const speedBackdoorTap = () => {
|
||||
setSpeedBackdoor(v => {
|
||||
@ -162,12 +167,6 @@ const ImportWallet = () => {
|
||||
if (triggerImport) handleImport();
|
||||
}, [triggerImport, handleImport]);
|
||||
|
||||
useEffect(() => {
|
||||
if (scannedData) {
|
||||
onBarScanned(scannedData);
|
||||
}
|
||||
}, [scannedData, onBarScanned]);
|
||||
|
||||
// Adding the ToolTipMenu to the header
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { useFocusEffect, useRoute } from '@react-navigation/native';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { CommonActions, RouteProp, useFocusEffect, useRoute } from '@react-navigation/native';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
@ -38,7 +38,6 @@ import QRCodeComponent from '../../components/QRCodeComponent';
|
||||
import SquareEnumeratedWords, { SquareEnumeratedWordsContentAlign } from '../../components/SquareEnumeratedWords';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import prompt from '../../helpers/prompt';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import { unlockWithBiometrics, useBiometrics } from '../../hooks/useBiometrics';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
import { disallowScreenshot } from 'react-native-screen-capture';
|
||||
@ -48,6 +47,13 @@ import { useStorage } from '../../hooks/context/useStorage';
|
||||
import ToolTipMenu from '../../components/TooltipMenu';
|
||||
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
|
||||
import { useSettings } from '../../hooks/context/useSettings';
|
||||
import { ViewEditMultisigCosignersStackParamList } from '../../navigation/ViewEditMultisigCosignersStack';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { navigationRef } from '../../NavigationService';
|
||||
import SafeArea from '../../components/SafeArea';
|
||||
|
||||
type RouteParams = RouteProp<ViewEditMultisigCosignersStackParamList, 'ViewEditMultisigCosigners'>;
|
||||
type NavigationProp = NativeStackNavigationProp<ViewEditMultisigCosignersStackParamList, 'ViewEditMultisigCosigners'>;
|
||||
|
||||
const ViewEditMultisigCosigners: React.FC = () => {
|
||||
const hasLoaded = useRef(false);
|
||||
@ -55,10 +61,10 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
||||
const { wallets, setWalletsWithNewOrder } = useStorage();
|
||||
const { isBiometricUseCapableAndEnabled } = useBiometrics();
|
||||
const { isElectrumDisabled, isPrivacyBlurEnabled } = useSettings();
|
||||
const { navigate, dispatch, addListener } = useExtendedNavigation();
|
||||
const { navigate, dispatch, addListener, setParams } = useExtendedNavigation<NavigationProp>();
|
||||
const openScannerButtonRef = useRef();
|
||||
const route = useRoute();
|
||||
const { walletID } = route.params as { walletID: string };
|
||||
const route = useRoute<RouteParams>();
|
||||
const { walletID } = route.params;
|
||||
const w = useRef(wallets.find(wallet => wallet.getID() === walletID));
|
||||
const tempWallet = useRef(new MultisigHDWallet());
|
||||
const [wallet, setWallet] = useState<MultisigHDWallet>();
|
||||
@ -73,6 +79,7 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
||||
const [exportStringURv2, setExportStringURv2] = useState(''); // used in QR
|
||||
const [exportFilename, setExportFilename] = useState('bw-cosigner.json');
|
||||
const [vaultKeyData, setVaultKeyData] = useState({ keyIndex: 1, xpub: '', seed: '', passphrase: '', path: '', fp: '', isLoading: false }); // string rendered in modal
|
||||
const [isVaultKeyIndexDataLoading, setIsVaultKeyIndexDataLoading] = useState<number | undefined>(undefined);
|
||||
const [askPassphrase, setAskPassphrase] = useState(false);
|
||||
const data = useRef<any[]>();
|
||||
/* discardChangesRef is only so the action sheet can be shown on mac catalyst when a
|
||||
@ -183,7 +190,8 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
||||
setIsSaveButtonDisabled(true);
|
||||
setTimeout(() => {
|
||||
setWalletsWithNewOrder(newWallets);
|
||||
navigate('WalletsList');
|
||||
// dismiss this modal
|
||||
navigationRef.dispatch(CommonActions.navigate({ name: 'WalletsList' }));
|
||||
}, 500);
|
||||
};
|
||||
useFocusEffect(
|
||||
@ -269,6 +277,24 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const resetModalData = () => {
|
||||
setVaultKeyData({
|
||||
keyIndex: 1,
|
||||
xpub: '',
|
||||
seed: '',
|
||||
passphrase: '',
|
||||
path: '',
|
||||
fp: '',
|
||||
isLoading: false,
|
||||
});
|
||||
setImportText('');
|
||||
setExportString('{}');
|
||||
setExportStringURv2('');
|
||||
setExportFilename('');
|
||||
setIsSaveButtonDisabled(false);
|
||||
setAskPassphrase(false);
|
||||
};
|
||||
|
||||
const _renderKeyItem = (el: ListRenderItemInfo<any>) => {
|
||||
if (!wallet) {
|
||||
// failsafe
|
||||
@ -306,29 +332,34 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
||||
buttonType: MultipleStepsListItemButtohType.partial,
|
||||
leftText,
|
||||
text: loc.multisig.view,
|
||||
showActivityIndicator: isVaultKeyIndexDataLoading === el.index + 1,
|
||||
disabled: vaultKeyData.isLoading,
|
||||
onPress: () => {
|
||||
const keyIndex = el.index + 1;
|
||||
const xpub = wallet.getCosigner(keyIndex);
|
||||
const fp = wallet.getFingerprint(keyIndex);
|
||||
const path = wallet.getCustomDerivationPathForCosigner(keyIndex);
|
||||
if (!path) {
|
||||
presentAlert({ message: 'Cannot find derivation path for this cosigner' });
|
||||
return;
|
||||
}
|
||||
setVaultKeyData({
|
||||
keyIndex,
|
||||
seed: '',
|
||||
passphrase: '',
|
||||
xpub,
|
||||
fp,
|
||||
path,
|
||||
isLoading: false,
|
||||
});
|
||||
setExportString(MultisigCosigner.exportToJson(fp, xpub, path));
|
||||
setExportStringURv2(encodeUR(MultisigCosigner.exportToJson(fp, xpub, path))[0]);
|
||||
setExportFilename('bw-cosigner-' + fp + '.json');
|
||||
mnemonicsModalRef.current?.present();
|
||||
setIsVaultKeyIndexDataLoading(el.index + 1);
|
||||
setTimeout(() => {
|
||||
const keyIndex = el.index + 1;
|
||||
const xpub = wallet.getCosigner(keyIndex);
|
||||
const fp = wallet.getFingerprint(keyIndex);
|
||||
const path = wallet.getCustomDerivationPathForCosigner(keyIndex);
|
||||
if (!path) {
|
||||
presentAlert({ message: 'Cannot find derivation path for this cosigner' });
|
||||
return;
|
||||
}
|
||||
setVaultKeyData({
|
||||
keyIndex,
|
||||
seed: '',
|
||||
passphrase: '',
|
||||
xpub,
|
||||
fp,
|
||||
path,
|
||||
isLoading: false,
|
||||
});
|
||||
setExportString(MultisigCosigner.exportToJson(fp, xpub, path));
|
||||
setExportStringURv2(encodeUR(MultisigCosigner.exportToJson(fp, xpub, path))[0]);
|
||||
setExportFilename('bw-cosigner-' + fp + '.json');
|
||||
mnemonicsModalRef.current?.present();
|
||||
setIsVaultKeyIndexDataLoading(undefined);
|
||||
}, 100);
|
||||
},
|
||||
}}
|
||||
dashes={MultipleStepsListItemDashType.topAndBottom}
|
||||
@ -357,31 +388,36 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
||||
leftText,
|
||||
text: loc.multisig.view,
|
||||
disabled: vaultKeyData.isLoading,
|
||||
showActivityIndicator: isVaultKeyIndexDataLoading === el.index + 1,
|
||||
buttonType: MultipleStepsListItemButtohType.partial,
|
||||
onPress: () => {
|
||||
const keyIndex = el.index + 1;
|
||||
const seed = wallet.getCosigner(keyIndex);
|
||||
const passphrase = wallet.getCosignerPassphrase(keyIndex);
|
||||
setVaultKeyData({
|
||||
keyIndex,
|
||||
seed,
|
||||
xpub: '',
|
||||
fp: '',
|
||||
path: '',
|
||||
passphrase: passphrase ?? '',
|
||||
isLoading: false,
|
||||
});
|
||||
mnemonicsModalRef.current?.present();
|
||||
const fp = wallet.getFingerprint(keyIndex);
|
||||
const path = wallet.getCustomDerivationPathForCosigner(keyIndex);
|
||||
if (!path) {
|
||||
presentAlert({ message: 'Cannot find derivation path for this cosigner' });
|
||||
return;
|
||||
}
|
||||
const xpub = wallet.convertXpubToMultisignatureXpub(MultisigHDWallet.seedToXpub(seed, path, passphrase));
|
||||
setExportString(MultisigCosigner.exportToJson(fp, xpub, path));
|
||||
setExportStringURv2(encodeUR(MultisigCosigner.exportToJson(fp, xpub, path))[0]);
|
||||
setExportFilename('bw-cosigner-' + fp + '.json');
|
||||
setIsVaultKeyIndexDataLoading(el.index + 1);
|
||||
setTimeout(() => {
|
||||
const keyIndex = el.index + 1;
|
||||
const seed = wallet.getCosigner(keyIndex);
|
||||
const passphrase = wallet.getCosignerPassphrase(keyIndex);
|
||||
setVaultKeyData({
|
||||
keyIndex,
|
||||
seed,
|
||||
xpub: '',
|
||||
fp: '',
|
||||
path: '',
|
||||
passphrase: passphrase ?? '',
|
||||
isLoading: false,
|
||||
});
|
||||
const fp = wallet.getFingerprint(keyIndex);
|
||||
const path = wallet.getCustomDerivationPathForCosigner(keyIndex);
|
||||
if (!path) {
|
||||
presentAlert({ message: 'Cannot find derivation path for this cosigner' });
|
||||
return;
|
||||
}
|
||||
const xpub = wallet.convertXpubToMultisignatureXpub(MultisigHDWallet.seedToXpub(seed, path, passphrase));
|
||||
setExportString(MultisigCosigner.exportToJson(fp, xpub, path));
|
||||
setExportStringURv2(encodeUR(MultisigCosigner.exportToJson(fp, xpub, path))[0]);
|
||||
setExportFilename('bw-cosigner-' + fp + '.json');
|
||||
mnemonicsModalRef.current?.present();
|
||||
setIsVaultKeyIndexDataLoading(undefined);
|
||||
}, 100);
|
||||
},
|
||||
}}
|
||||
dashes={MultipleStepsListItemDashType.topAndBottom}
|
||||
@ -436,6 +472,7 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
||||
await provideMnemonicsModalRef.current?.dismiss();
|
||||
await shareModalRef.current?.dismiss();
|
||||
await mnemonicsModalRef.current?.dismiss();
|
||||
resetModalData();
|
||||
};
|
||||
const handleUseMnemonicPhrase = async () => {
|
||||
let passphrase;
|
||||
@ -472,9 +509,7 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
setWallet(wallet);
|
||||
provideMnemonicsModalRef.current?.dismiss();
|
||||
setIsSaveButtonDisabled(false);
|
||||
setImportText('');
|
||||
setAskPassphrase(false);
|
||||
resetModalData();
|
||||
};
|
||||
|
||||
const xpubInsteadOfSeed = (index: number): Promise<void> => {
|
||||
@ -496,16 +531,22 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
||||
|
||||
const scanOrOpenFile = async () => {
|
||||
await provideMnemonicsModalRef.current?.dismiss();
|
||||
const scanned = await scanQrHelper(route.name, true, undefined);
|
||||
setImportText(String(scanned));
|
||||
provideMnemonicsModalRef.current?.present();
|
||||
navigate('ScanQRCode', { showFileImportButton: true });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const scannedData = route.params.onBarScanned;
|
||||
if (scannedData) {
|
||||
setImportText(String(scannedData));
|
||||
setParams({ onBarScanned: undefined });
|
||||
provideMnemonicsModalRef.current?.present();
|
||||
}
|
||||
}, [route.params.onBarScanned, setParams]);
|
||||
|
||||
const hideProvideMnemonicsModal = () => {
|
||||
Keyboard.dismiss();
|
||||
provideMnemonicsModalRef.current?.dismiss();
|
||||
setImportText('');
|
||||
setAskPassphrase(false);
|
||||
resetModalData();
|
||||
};
|
||||
|
||||
const hideShareModal = () => {};
|
||||
@ -570,13 +611,15 @@ const ViewEditMultisigCosigners: React.FC = () => {
|
||||
backgroundColor={colors.elevated}
|
||||
shareContent={{ fileName: exportFilename, fileContent: exportString }}
|
||||
>
|
||||
<View style={styles.alignItemsCenter}>
|
||||
<Text style={[styles.headerText, stylesHook.textDestination]}>
|
||||
{loc.multisig.this_is_cosigners_xpub} {Platform.OS === 'ios' ? loc.multisig.this_is_cosigners_xpub_airdrop : ''}
|
||||
</Text>
|
||||
<BlueSpacing20 />
|
||||
<QRCodeComponent value={exportStringURv2} size={260} isLogoRendered={false} />
|
||||
</View>
|
||||
<SafeArea>
|
||||
<View style={styles.alignItemsCenter}>
|
||||
<Text style={[styles.headerText, stylesHook.textDestination]}>
|
||||
{loc.multisig.this_is_cosigners_xpub} {Platform.OS === 'ios' ? loc.multisig.this_is_cosigners_xpub_airdrop : ''}
|
||||
</Text>
|
||||
<BlueSpacing20 />
|
||||
<QRCodeComponent value={exportStringURv2} size={260} isLogoRendered={false} />
|
||||
</View>
|
||||
</SafeArea>
|
||||
</BottomModal>
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useFocusEffect, useRoute } from '@react-navigation/native';
|
||||
import { RouteProp, useFocusEffect, useRoute } from '@react-navigation/native';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
@ -26,7 +26,6 @@ import { FButton, FContainer } from '../../components/FloatButtons';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { TransactionListItem } from '../../components/TransactionListItem';
|
||||
import TransactionsNavigationHeader, { actionKeys } from '../../components/TransactionsNavigationHeader';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import { unlockWithBiometrics, useBiometrics } from '../../hooks/useBiometrics';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
import loc from '../../loc';
|
||||
@ -53,14 +52,15 @@ const buttonFontSize =
|
||||
: PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26);
|
||||
|
||||
type WalletTransactionsProps = NativeStackScreenProps<DetailViewStackParamList, 'WalletTransactions'>;
|
||||
type RouteProps = RouteProp<DetailViewStackParamList, 'WalletTransactions'>;
|
||||
|
||||
const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
|
||||
const { wallets, saveToDisk, setSelectedWalletID } = useStorage();
|
||||
const { setReloadTransactionsMenuActionFunction } = useMenuElements();
|
||||
const { isBiometricUseCapableAndEnabled } = useBiometrics();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { walletID } = route.params;
|
||||
const { name } = useRoute();
|
||||
const { params, name } = useRoute<RouteProps>();
|
||||
const { walletID } = params;
|
||||
const wallet = useMemo(() => wallets.find(w => w.getID() === walletID), [walletID, wallets]);
|
||||
const [limit, setLimit] = useState(15);
|
||||
const [pageSize] = useState(20);
|
||||
@ -85,6 +85,33 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
|
||||
}, [route, setOptions]),
|
||||
);
|
||||
|
||||
const onBarCodeRead = useCallback(
|
||||
(ret?: { data?: any }) => {
|
||||
if (!isLoading) {
|
||||
setIsLoading(true);
|
||||
const parameters = {
|
||||
walletID,
|
||||
uri: ret?.data ? ret.data : ret,
|
||||
};
|
||||
if (wallet?.chain === Chain.ONCHAIN) {
|
||||
navigate('SendDetailsRoot', { screen: 'SendDetails', params: parameters });
|
||||
} else {
|
||||
navigate('ScanLndInvoiceRoot', { screen: 'ScanLndInvoice', params: parameters });
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[isLoading, walletID, wallet?.chain, navigate],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const data = route.params?.onBarScanned;
|
||||
if (data) {
|
||||
onBarCodeRead({ data });
|
||||
navigation.setParams({ onBarScanned: undefined });
|
||||
}
|
||||
}, [navigation, onBarCodeRead, route.params]);
|
||||
|
||||
const getTransactions = useCallback(
|
||||
(lmt = Infinity): Transaction[] => {
|
||||
if (!wallet) return [];
|
||||
@ -259,25 +286,6 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
|
||||
<TransactionListItem item={item.item} itemPriceUnit={wallet?.preferredBalanceUnit} walletID={walletID} />
|
||||
);
|
||||
|
||||
const onBarCodeRead = useCallback(
|
||||
(ret?: { data?: any }) => {
|
||||
if (!isLoading) {
|
||||
setIsLoading(true);
|
||||
const params = {
|
||||
walletID,
|
||||
uri: ret?.data ? ret.data : ret,
|
||||
};
|
||||
if (wallet?.chain === Chain.ONCHAIN) {
|
||||
navigate('SendDetailsRoot', { screen: 'SendDetails', params });
|
||||
} else {
|
||||
navigate('ScanLndInvoiceRoot', { screen: 'ScanLndInvoice', params });
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[isLoading, walletID, wallet?.chain, navigate],
|
||||
);
|
||||
|
||||
const choosePhoto = () => {
|
||||
fs.showImagePickerAndReadImage()
|
||||
.then(data => {
|
||||
@ -351,10 +359,9 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
const data = await scanQrHelper(name, true);
|
||||
if (data) {
|
||||
onBarCodeRead({ data });
|
||||
}
|
||||
navigate('ScanQRCode', {
|
||||
showImportFileButton: true,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
|
@ -13,7 +13,6 @@ import { FButton, FContainer } from '../../components/FloatButtons';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { TransactionListItem } from '../../components/TransactionListItem';
|
||||
import WalletsCarousel from '../../components/WalletsCarousel';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import { useIsLargeScreen } from '../../hooks/useIsLargeScreen';
|
||||
import loc from '../../loc';
|
||||
import ActionSheet from '../ActionSheet';
|
||||
@ -104,10 +103,9 @@ const WalletsList: React.FC = () => {
|
||||
const { isTotalBalanceEnabled, isElectrumDisabled } = useSettings();
|
||||
const { width } = useWindowDimensions();
|
||||
const { colors, scanImage } = useTheme();
|
||||
const { navigate } = useExtendedNavigation<NavigationProps>();
|
||||
const navigation = useExtendedNavigation<NavigationProps>();
|
||||
const isFocused = useIsFocused();
|
||||
const route = useRoute<RouteProps>();
|
||||
const routeName = route.name;
|
||||
const dataSource = getTransactions(undefined, 10);
|
||||
const walletsCount = useRef<number>(wallets.length);
|
||||
const walletActionButtonsRef = useRef<any>();
|
||||
@ -179,13 +177,25 @@ const WalletsList: React.FC = () => {
|
||||
walletsCount.current = wallets.length;
|
||||
}, [wallets]);
|
||||
|
||||
const onBarScanned = useCallback(
|
||||
(value: any) => {
|
||||
if (!value) return;
|
||||
DeeplinkSchemaMatch.navigationRouteFor({ url: value }, completionValue => {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
|
||||
// @ts-ignore: for now
|
||||
navigation.navigate(...completionValue);
|
||||
});
|
||||
},
|
||||
[navigation],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const scannedData = route.params?.scannedData;
|
||||
if (scannedData) {
|
||||
onBarScanned(scannedData);
|
||||
const data = route.params?.onBarScanned;
|
||||
if (data) {
|
||||
onBarScanned(data);
|
||||
navigation.setParams({ onBarScanned: undefined });
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [route.params?.scannedData]);
|
||||
}, [navigation, onBarScanned, route.params?.onBarScanned]);
|
||||
|
||||
useEffect(() => {
|
||||
refreshTransactions(false, true);
|
||||
@ -196,15 +206,15 @@ const WalletsList: React.FC = () => {
|
||||
(item?: TWallet) => {
|
||||
if (item?.getID) {
|
||||
const walletID = item.getID();
|
||||
navigate('WalletTransactions', {
|
||||
navigation.navigate('WalletTransactions', {
|
||||
walletID,
|
||||
walletType: item.type,
|
||||
});
|
||||
} else {
|
||||
navigate('AddWalletRoot');
|
||||
navigation.navigate('AddWalletRoot');
|
||||
}
|
||||
},
|
||||
[navigate],
|
||||
[navigation],
|
||||
);
|
||||
|
||||
const setIsLoading = useCallback((value: boolean) => {
|
||||
@ -240,8 +250,8 @@ const WalletsList: React.FC = () => {
|
||||
}, [stylesHook.listHeaderBack, stylesHook.listHeaderText]);
|
||||
|
||||
const handleLongPress = useCallback(() => {
|
||||
navigate('ManageWallets');
|
||||
}, [navigate]);
|
||||
navigation.navigate('ManageWallets');
|
||||
}, [navigation]);
|
||||
|
||||
const renderTransactionListsRow = useCallback(
|
||||
(item: ExtendedTransaction) => (
|
||||
@ -349,20 +359,10 @@ const WalletsList: React.FC = () => {
|
||||
};
|
||||
|
||||
const onScanButtonPressed = useCallback(() => {
|
||||
scanQrHelper(routeName, true, undefined, false);
|
||||
}, [routeName]);
|
||||
|
||||
const onBarScanned = useCallback(
|
||||
(value: any) => {
|
||||
if (!value) return;
|
||||
DeeplinkSchemaMatch.navigationRouteFor({ url: value }, completionValue => {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
|
||||
// @ts-ignore: for now
|
||||
navigate(...completionValue);
|
||||
});
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
navigation.navigate('ScanQRCode', {
|
||||
showFileImportButton: true,
|
||||
});
|
||||
}, [navigation]);
|
||||
|
||||
const pasteFromClipboard = useCallback(async () => {
|
||||
onBarScanned(await getClipboardContent());
|
||||
@ -397,7 +397,9 @@ const WalletsList: React.FC = () => {
|
||||
});
|
||||
break;
|
||||
case 2:
|
||||
scanQrHelper(routeName, true, undefined, false);
|
||||
navigation.navigate('ScanQRCode', {
|
||||
showFileImportButton: true,
|
||||
});
|
||||
break;
|
||||
case 3:
|
||||
if (!isClipboardEmpty) {
|
||||
@ -406,7 +408,7 @@ const WalletsList: React.FC = () => {
|
||||
break;
|
||||
}
|
||||
});
|
||||
}, [pasteFromClipboard, onBarScanned, routeName]);
|
||||
}, [onBarScanned, navigation, pasteFromClipboard]);
|
||||
|
||||
const refreshProps = isDesktop || isElectrumDisabled ? {} : { refreshing: isLoading, onRefresh };
|
||||
|
||||
|
@ -32,7 +32,6 @@ import prompt from '../../helpers/prompt';
|
||||
import { disallowScreenshot } from 'react-native-screen-capture';
|
||||
import loc from '../../loc';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { scanQrHelper } from '../../helpers/scan-qr';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
import ToolTipMenu from '../../components/TooltipMenu';
|
||||
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
|
||||
@ -46,10 +45,9 @@ const WalletsAddMultisigStep2 = () => {
|
||||
const { addWallet, saveToDisk, isElectrumDisabled, sleep, currentSharedCosigner, setSharedCosigner } = useStorage();
|
||||
const { colors } = useTheme();
|
||||
|
||||
const { navigate, navigateToWalletsList } = useExtendedNavigation();
|
||||
const { m, n, format, walletLabel } = useRoute().params;
|
||||
const { name } = useRoute();
|
||||
|
||||
const { navigate, navigateToWalletsList, setParams } = useExtendedNavigation();
|
||||
const params = useRoute().params;
|
||||
const { m, n, format, walletLabel } = params;
|
||||
const [cosigners, setCosigners] = useState([]); // array of cosigners user provided. if format [cosigner, fp, path]
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const mnemonicsModalRef = useRef(null);
|
||||
@ -203,7 +201,7 @@ const WalletsAddMultisigStep2 = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const getPath = () => {
|
||||
const getPath = useCallback(() => {
|
||||
let path = '';
|
||||
switch (format) {
|
||||
case MultisigHDWallet.FORMAT_P2WSH:
|
||||
@ -221,7 +219,7 @@ const WalletsAddMultisigStep2 = () => {
|
||||
throw new Error('This should never happen');
|
||||
}
|
||||
return path;
|
||||
};
|
||||
}, [format]);
|
||||
|
||||
const viewKey = cosigner => {
|
||||
if (MultisigHDWallet.isXpubValid(cosigner[0])) {
|
||||
@ -267,52 +265,55 @@ const WalletsAddMultisigStep2 = () => {
|
||||
provideMnemonicsModalRef.current.present();
|
||||
};
|
||||
|
||||
const tryUsingXpub = async (xpub, fp, path) => {
|
||||
if (!MultisigHDWallet.isXpubForMultisig(xpub)) {
|
||||
const tryUsingXpub = useCallback(
|
||||
async (xpub, fp, path) => {
|
||||
if (!MultisigHDWallet.isXpubForMultisig(xpub)) {
|
||||
provideMnemonicsModalRef.current.dismiss();
|
||||
setIsLoading(false);
|
||||
setImportText('');
|
||||
setAskPassphrase(false);
|
||||
presentAlert({ message: loc.multisig.not_a_multisignature_xpub });
|
||||
return;
|
||||
}
|
||||
if (fp) {
|
||||
// do nothing, it's already set
|
||||
} else {
|
||||
try {
|
||||
fp = await prompt(loc.multisig.input_fp, loc.multisig.input_fp_explain, true, 'plain-text');
|
||||
fp = (fp + '').toUpperCase();
|
||||
if (!MultisigHDWallet.isFpValid(fp)) fp = '00000000';
|
||||
} catch (e) {
|
||||
return setIsLoading(false);
|
||||
}
|
||||
}
|
||||
if (path) {
|
||||
// do nothing, it's already set
|
||||
} else {
|
||||
try {
|
||||
path = await prompt(
|
||||
loc.multisig.input_path,
|
||||
loc.formatString(loc.multisig.input_path_explain, { default: getPath() }),
|
||||
true,
|
||||
'plain-text',
|
||||
);
|
||||
if (!MultisigHDWallet.isPathValid(path)) path = getPath();
|
||||
} catch {
|
||||
return setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
provideMnemonicsModalRef.current.dismiss();
|
||||
setIsLoading(false);
|
||||
setImportText('');
|
||||
setAskPassphrase(false);
|
||||
presentAlert({ message: loc.multisig.not_a_multisignature_xpub });
|
||||
return;
|
||||
}
|
||||
if (fp) {
|
||||
// do nothing, it's already set
|
||||
} else {
|
||||
try {
|
||||
fp = await prompt(loc.multisig.input_fp, loc.multisig.input_fp_explain, true, 'plain-text');
|
||||
fp = (fp + '').toUpperCase();
|
||||
if (!MultisigHDWallet.isFpValid(fp)) fp = '00000000';
|
||||
} catch (e) {
|
||||
return setIsLoading(false);
|
||||
}
|
||||
}
|
||||
if (path) {
|
||||
// do nothing, it's already set
|
||||
} else {
|
||||
try {
|
||||
path = await prompt(
|
||||
loc.multisig.input_path,
|
||||
loc.formatString(loc.multisig.input_path_explain, { default: getPath() }),
|
||||
true,
|
||||
'plain-text',
|
||||
);
|
||||
if (!MultisigHDWallet.isPathValid(path)) path = getPath();
|
||||
} catch {
|
||||
return setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
provideMnemonicsModalRef.current.dismiss();
|
||||
setIsLoading(false);
|
||||
setImportText('');
|
||||
setAskPassphrase(false);
|
||||
|
||||
const cosignersCopy = [...cosigners];
|
||||
cosignersCopy.push([xpub, fp, path]);
|
||||
if (Platform.OS !== 'android') LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
setCosigners(cosignersCopy);
|
||||
};
|
||||
const cosignersCopy = [...cosigners];
|
||||
cosignersCopy.push([xpub, fp, path]);
|
||||
if (Platform.OS !== 'android') LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
setCosigners(cosignersCopy);
|
||||
},
|
||||
[cosigners, getPath],
|
||||
);
|
||||
|
||||
const useMnemonicPhrase = async () => {
|
||||
setIsLoading(true);
|
||||
@ -371,114 +372,122 @@ const WalletsAddMultisigStep2 = () => {
|
||||
return hd.validateMnemonic();
|
||||
};
|
||||
|
||||
const onBarScanned = ret => {
|
||||
if (!ret.data) ret = { data: ret };
|
||||
const onBarScanned = useCallback(
|
||||
ret => {
|
||||
if (!ret.data) ret = { data: ret };
|
||||
|
||||
try {
|
||||
let retData = JSON.parse(ret.data);
|
||||
if (Array.isArray(retData) && retData.length === 1) {
|
||||
// UR:CRYPTO-ACCOUNT now parses as an array of accounts, even if it is just one,
|
||||
// so in case of cosigner data its gona be an array of 1 cosigner account. lets pop it for
|
||||
// the code that expects it
|
||||
retData = retData.pop();
|
||||
ret.data = JSON.stringify(retData);
|
||||
}
|
||||
} catch (_) {}
|
||||
try {
|
||||
let retData = JSON.parse(ret.data);
|
||||
if (Array.isArray(retData) && retData.length === 1) {
|
||||
// UR:CRYPTO-ACCOUNT now parses as an array of accounts, even if it is just one,
|
||||
// so in case of cosigner data its gona be an array of 1 cosigner account. lets pop it for
|
||||
// the code that expects it
|
||||
retData = retData.pop();
|
||||
ret.data = JSON.stringify(retData);
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
if (ret.data.toUpperCase().startsWith('UR')) {
|
||||
presentAlert({ message: 'BC-UR not decoded. This should never happen' });
|
||||
} else if (isValidMnemonicSeed(ret.data)) {
|
||||
setImportText(ret.data);
|
||||
setTimeout(() => {
|
||||
provideMnemonicsModalRef.current.present().then(() => {});
|
||||
}, 100);
|
||||
} else {
|
||||
if (MultisigHDWallet.isXpubValid(ret.data) && !MultisigHDWallet.isXpubForMultisig(ret.data)) {
|
||||
return presentAlert({ message: loc.multisig.not_a_multisignature_xpub });
|
||||
}
|
||||
if (MultisigHDWallet.isXpubValid(ret.data)) {
|
||||
return tryUsingXpub(ret.data);
|
||||
}
|
||||
let cosigner = new MultisigCosigner(ret.data);
|
||||
if (!cosigner.isValid()) return presentAlert({ message: loc.multisig.invalid_cosigner });
|
||||
provideMnemonicsModalRef.current.dismiss();
|
||||
if (cosigner.howManyCosignersWeHave() > 1) {
|
||||
// lets look for the correct cosigner. thats probably gona be the one with specific corresponding path,
|
||||
// for example m/48'/0'/0'/2' if user chose to setup native segwit in BW
|
||||
for (const cc of cosigner.getAllCosigners()) {
|
||||
switch (format) {
|
||||
case MultisigHDWallet.FORMAT_P2WSH:
|
||||
if (cc.getPath().startsWith('m/48') && cc.getPath().endsWith("/2'")) {
|
||||
// found it
|
||||
cosigner = cc;
|
||||
}
|
||||
break;
|
||||
case MultisigHDWallet.FORMAT_P2SH_P2WSH:
|
||||
case MultisigHDWallet.FORMAT_P2SH_P2WSH_ALT:
|
||||
if (cc.getPath().startsWith('m/48') && cc.getPath().endsWith("/1'")) {
|
||||
// found it
|
||||
cosigner = cc;
|
||||
}
|
||||
break;
|
||||
case MultisigHDWallet.FORMAT_P2SH:
|
||||
if (cc.getPath().startsWith('m/45')) {
|
||||
// found it
|
||||
cosigner = cc;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.error('Unexpected format:', format);
|
||||
throw new Error('This should never happen');
|
||||
if (ret.data.toUpperCase().startsWith('UR')) {
|
||||
presentAlert({ message: 'BC-UR not decoded. This should never happen' });
|
||||
} else if (isValidMnemonicSeed(ret.data)) {
|
||||
setImportText(ret.data);
|
||||
setTimeout(() => {
|
||||
provideMnemonicsModalRef.current.present().then(() => {});
|
||||
}, 100);
|
||||
} else {
|
||||
if (MultisigHDWallet.isXpubValid(ret.data) && !MultisigHDWallet.isXpubForMultisig(ret.data)) {
|
||||
return presentAlert({ message: loc.multisig.not_a_multisignature_xpub });
|
||||
}
|
||||
if (MultisigHDWallet.isXpubValid(ret.data)) {
|
||||
return tryUsingXpub(ret.data);
|
||||
}
|
||||
let cosigner = new MultisigCosigner(ret.data);
|
||||
if (!cosigner.isValid()) return presentAlert({ message: loc.multisig.invalid_cosigner });
|
||||
provideMnemonicsModalRef.current.dismiss();
|
||||
if (cosigner.howManyCosignersWeHave() > 1) {
|
||||
// lets look for the correct cosigner. thats probably gona be the one with specific corresponding path,
|
||||
// for example m/48'/0'/0'/2' if user chose to setup native segwit in BW
|
||||
for (const cc of cosigner.getAllCosigners()) {
|
||||
switch (format) {
|
||||
case MultisigHDWallet.FORMAT_P2WSH:
|
||||
if (cc.getPath().startsWith('m/48') && cc.getPath().endsWith("/2'")) {
|
||||
// found it
|
||||
cosigner = cc;
|
||||
}
|
||||
break;
|
||||
case MultisigHDWallet.FORMAT_P2SH_P2WSH:
|
||||
case MultisigHDWallet.FORMAT_P2SH_P2WSH_ALT:
|
||||
if (cc.getPath().startsWith('m/48') && cc.getPath().endsWith("/1'")) {
|
||||
// found it
|
||||
cosigner = cc;
|
||||
}
|
||||
break;
|
||||
case MultisigHDWallet.FORMAT_P2SH:
|
||||
if (cc.getPath().startsWith('m/45')) {
|
||||
// found it
|
||||
cosigner = cc;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.error('Unexpected format:', format);
|
||||
throw new Error('This should never happen');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const existingCosigner of cosigners) {
|
||||
if (existingCosigner[0] === cosigner.getXpub()) return presentAlert({ message: loc.multisig.this_cosigner_is_already_imported });
|
||||
}
|
||||
|
||||
// now, validating that cosigner is in correct format:
|
||||
|
||||
let correctFormat = false;
|
||||
switch (format) {
|
||||
case MultisigHDWallet.FORMAT_P2WSH:
|
||||
if (cosigner.getPath().startsWith('m/48') && cosigner.getPath().endsWith("/2'")) {
|
||||
correctFormat = true;
|
||||
}
|
||||
break;
|
||||
case MultisigHDWallet.FORMAT_P2SH_P2WSH:
|
||||
case MultisigHDWallet.FORMAT_P2SH_P2WSH_ALT:
|
||||
if (cosigner.getPath().startsWith('m/48') && cosigner.getPath().endsWith("/1'")) {
|
||||
correctFormat = true;
|
||||
}
|
||||
break;
|
||||
case MultisigHDWallet.FORMAT_P2SH:
|
||||
if (cosigner.getPath().startsWith('m/45')) {
|
||||
correctFormat = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.error('Unexpected format:', format);
|
||||
throw new Error('This should never happen');
|
||||
}
|
||||
|
||||
if (!correctFormat) return presentAlert({ message: loc.formatString(loc.multisig.invalid_cosigner_format, { format }) });
|
||||
|
||||
const cosignersCopy = [...cosigners];
|
||||
cosignersCopy.push([cosigner.getXpub(), cosigner.getFp(), cosigner.getPath()]);
|
||||
if (Platform.OS !== 'android') LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
setCosigners(cosignersCopy);
|
||||
}
|
||||
|
||||
for (const existingCosigner of cosigners) {
|
||||
if (existingCosigner[0] === cosigner.getXpub()) return presentAlert({ message: loc.multisig.this_cosigner_is_already_imported });
|
||||
}
|
||||
|
||||
// now, validating that cosigner is in correct format:
|
||||
|
||||
let correctFormat = false;
|
||||
switch (format) {
|
||||
case MultisigHDWallet.FORMAT_P2WSH:
|
||||
if (cosigner.getPath().startsWith('m/48') && cosigner.getPath().endsWith("/2'")) {
|
||||
correctFormat = true;
|
||||
}
|
||||
break;
|
||||
case MultisigHDWallet.FORMAT_P2SH_P2WSH:
|
||||
case MultisigHDWallet.FORMAT_P2SH_P2WSH_ALT:
|
||||
if (cosigner.getPath().startsWith('m/48') && cosigner.getPath().endsWith("/1'")) {
|
||||
correctFormat = true;
|
||||
}
|
||||
break;
|
||||
case MultisigHDWallet.FORMAT_P2SH:
|
||||
if (cosigner.getPath().startsWith('m/45')) {
|
||||
correctFormat = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.error('Unexpected format:', format);
|
||||
throw new Error('This should never happen');
|
||||
}
|
||||
|
||||
if (!correctFormat) return presentAlert({ message: loc.formatString(loc.multisig.invalid_cosigner_format, { format }) });
|
||||
|
||||
const cosignersCopy = [...cosigners];
|
||||
cosignersCopy.push([cosigner.getXpub(), cosigner.getFp(), cosigner.getPath()]);
|
||||
if (Platform.OS !== 'android') LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
setCosigners(cosignersCopy);
|
||||
}
|
||||
};
|
||||
},
|
||||
[cosigners, format, tryUsingXpub],
|
||||
);
|
||||
|
||||
const scanOrOpenFile = async () => {
|
||||
await provideMnemonicsModalRef.current.dismiss();
|
||||
const scanned = await scanQrHelper(name, true);
|
||||
if (scanned) {
|
||||
onBarScanned(scanned);
|
||||
}
|
||||
navigate('ScanQRCode');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const scannedData = params.onBarScanned;
|
||||
if (scannedData) {
|
||||
onBarScanned(scannedData);
|
||||
setParams({ onBarScanned: undefined });
|
||||
}
|
||||
}, [onBarScanned, params.onBarScanned, setParams]);
|
||||
|
||||
const dashType = ({ index, lastIndex, isChecked, isFocus }) => {
|
||||
if (isChecked) {
|
||||
if (index === lastIndex) {
|
||||
|
@ -2005,6 +2005,20 @@ describe('multisig-wallet (native segwit)', () => {
|
||||
assert.strictEqual(w._getInternalAddressByIndex(0), w3coordinator._getInternalAddressByIndex(0));
|
||||
});
|
||||
|
||||
it('can import wallet descriptor from Casa multisig', () => {
|
||||
const w = new MultisigHDWallet();
|
||||
w.setSecret(
|
||||
'sh(wsh(sortedmulti(3,[35282ae3]Ypub6j9gr7f7uvWfTHdwVwhYRowZH9oqkh9DZz2hdbeKJw7yjQbsfqaruLva2piTZqN7jSKT43LpsnAqHU4vufUGAyGBzD6UJpwTeKiG5JRYUnm/<0;1>/*,[1b373e31/49/0/5]Ypub6iPAfqqinG5VHf2sJ3XfRjFh8BXm4C6x4U3DaPyu4RKocnqzb1peStw8Q624jwXpMZ5G81s7uwq8qLQ8rW55aZ3LvBbr9XVLTYxG78k28PE/<0;1>/*,[0d7bb846/49/0/5]Ypub6hwdyxEzktht47xgyyFY7TFrAzW7XgGbMSqS3QzfosYekG9LibhpohY6LrsjfP55VyU8i7iWS2s2Vs6RkAeze6bSpU3rNHsmESymE8X3J3k/<0;1>/*,[faa45c74/49/0/5]Ypub6iSiDkVrTTEh6eFbkCvohn1FfuhGr1Co9MCCHi6VPTT6crnX9Avq3PnZj7JcqhDTkWoDLEyeWmaPyxDY22b7k652Sg2eMc2g5tU6GDTvSng/<0;1>/*,[0551354e/49/0/5]Ypub6ipvk7JbDUXormRsSxXRf9eTUAVFwcjDfZXWYxRdN6nV7MNnnuD3WZBYjjWnMTncHhKJsnfkUGVYMRgQthZqY2wfHfMaJoVYBBhHT7MACGs/<0;1>/*)))',
|
||||
);
|
||||
|
||||
assert.strictEqual(w._getExternalAddressByIndex(0), '3HJiAohE25FFBLPLZGVDwv7ZbSXVsSiZH7');
|
||||
assert.strictEqual(w._getExternalAddressByIndex(1), '3GBvQK1iHJ9dw7H8datM4REtYdAu9iQjeh');
|
||||
assert.strictEqual(w._getExternalAddressByIndex(2), '3KrAqZwVwND5XFNEAnCuWyf4nPNAfF2JCF');
|
||||
assert.strictEqual(w._getInternalAddressByIndex(0), '36oNF12VNk4G5hPRR5zuZixnNjJikkTSWD');
|
||||
assert.strictEqual(w._getInternalAddressByIndex(1), '3Q7SnCQK9DYPpFviiEB7NQHa1FJqprCWNX');
|
||||
assert.strictEqual(w._getInternalAddressByIndex(2), '33Rkb8XURWkxZBN6dpdk64qa4raK2bRFJS');
|
||||
});
|
||||
|
||||
it('can import descriptor from Sparrow', () => {
|
||||
const payload =
|
||||
'UR:CRYPTO-OUTPUT/TAADMETAADMSOEADAOAOLSTAADDLOLAOWKAXHDCLAOCEBDFLNNTKJTIOJSFSURBNFXRPEEHKDLGYRTEMRPYTGYZOCASWENCYMKPAVWJKHYAAHDCXJEFTGSZOIMFEYNDYHYZEJTBAMSJEHLDSRDDIYLSRFYTSZTKNRNYLRNDPAMTLDPZCAHTAADEHOEADAEAOAEAMTAADDYOTADLOCSDYYKAEYKAEYKAOYKAOCYUOHFJPKOAXAAAYCYCSYASAVDTAADDLOLAOWKAXHDCLAXMSZTWZDIGERYDKFSFWTYDPFNDKLNAYSWTTMUHYZTOXHSETPEWSFXPEAYWLJSDEMTAAHDCXSPLTSTDPNTLESANSUTTLPRPFHNVSPFCNMHESOYGASTLRPYVAATNNDKFYHLQZPKLEAHTAADEHOEADAEAOAEAMTAADDYOTADLOCSDYYKAEYKAEYKAOYKAOCYWZFEPLETAXAAAYCYCPCKRENBTAADDLOLAOWKAXHDCLAOLSFWYKYLKTFHJLPYEMGLCEDPFNSNRDDSRFASEOZTGWIALFLUIYDNFXHGVESFEMMEAAHDCXHTZETLJNKPHHAYLSCXWPNDSWPSTPGTEOJKKGHDAELSKPNNBKBSYAWZJTFWNNBDKTAHTAADEHOEADAEAOAEAMTAADDYOTADLOCSDYYKAEYKAEYKAOYKAOCYSKTPJPMSAXAAAYCYCEBKWLAMTDWZGRZE\n';
|
||||
|
Loading…
Reference in New Issue
Block a user