Merge branch 'master' into scanlndhook

This commit is contained in:
marcosrdz 2020-12-11 16:18:10 -05:00
commit ba907b6ceb
15 changed files with 236 additions and 127 deletions

View File

@ -345,6 +345,7 @@ export class BlueWalletNavigationHeader extends Component {
<LinearGradient
colors={WalletGradient.gradientsFor(this.state.wallet.type)}
style={{ padding: 15, minHeight: 140, justifyContent: 'center' }}
{...WalletGradient.linearGradientProps(this.state.wallet.type)}
>
<Image
source={(() => {
@ -452,6 +453,34 @@ export class BlueWalletNavigationHeader extends Component {
</View>
</TouchableOpacity>
)}
{this.state.wallet.type === MultisigHDWallet.type && (
<TouchableOpacity onPress={this.manageFundsPressed}>
<View
style={{
marginTop: 14,
marginBottom: 10,
backgroundColor: 'rgba(255,255,255,0.2)',
borderRadius: 9,
minHeight: 39,
alignSelf: 'flex-start',
paddingHorizontal: 12,
height: 39,
justifyContent: 'center',
alignItems: 'center',
}}
>
<Text
style={{
fontWeight: '500',
fontSize: 14,
color: '#FFFFFF',
}}
>
{loc.multisig.manage_keys}
</Text>
</View>
</TouchableOpacity>
)}
</LinearGradient>
);
}
@ -915,15 +944,13 @@ export const BlueSpacing40 = props => {
return <View {...props} style={{ height: 50 }} />;
};
export class BlueSpacingVariable extends Component {
render() {
if (isIpad) {
return <BlueSpacing40 {...this.props} />;
} else {
return <BlueSpacing {...this.props} />;
}
export const BlueSpacingVariable = props => {
if (isIpad) {
return <BlueSpacing40 {...props} />;
} else {
return <BlueSpacing {...props} />;
}
}
};
export class is {
static ipad() {
@ -1015,61 +1042,59 @@ export class BlueUseAllFundsButton extends Component {
}
}
export class BlueDismissKeyboardInputAccessory extends Component {
static InputAccessoryViewID = 'BlueDismissKeyboardInputAccessory';
export const BlueDismissKeyboardInputAccessory = () => {
const { colors } = useTheme();
BlueDismissKeyboardInputAccessory.InputAccessoryViewID = 'BlueDismissKeyboardInputAccessory';
render() {
return Platform.OS !== 'ios' ? null : (
<InputAccessoryView nativeID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}>
<View
style={{
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
height: 44,
flex: 1,
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'center',
}}
>
<BlueButtonLink title={loc.send.input_done} onPress={() => Keyboard.dismiss()} />
</View>
</InputAccessoryView>
);
}
}
export class BlueDoneAndDismissKeyboardInputAccessory extends Component {
static InputAccessoryViewID = 'BlueDoneAndDismissKeyboardInputAccessory';
onPasteTapped = async () => {
const clipboard = await Clipboard.getString();
this.props.onPasteTapped(clipboard);
};
render() {
const inputView = (
return Platform.OS !== 'ios' ? null : (
<InputAccessoryView nativeID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}>
<View
style={{
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
backgroundColor: colors.inputBackgroundColor,
height: 44,
flex: 1,
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'center',
maxHeight: 44,
}}
>
<BlueButtonLink title={loc.send.input_clear} onPress={this.props.onClearTapped} />
<BlueButtonLink title={loc.send.input_paste} onPress={this.onPasteTapped} />
<BlueButtonLink title={loc.send.input_done} onPress={() => Keyboard.dismiss()} />
<BlueButtonLink title={loc.send.input_done} onPress={Keyboard.dismiss} />
</View>
);
</InputAccessoryView>
);
};
if (Platform.OS === 'ios') {
return <InputAccessoryView nativeID={BlueDoneAndDismissKeyboardInputAccessory.InputAccessoryViewID}>{inputView}</InputAccessoryView>;
} else {
return <KeyboardAvoidingView>{inputView}</KeyboardAvoidingView>;
}
export const BlueDoneAndDismissKeyboardInputAccessory = props => {
const { colors } = useTheme();
BlueDoneAndDismissKeyboardInputAccessory.InputAccessoryViewID = 'BlueDoneAndDismissKeyboardInputAccessory';
const onPasteTapped = async () => {
const clipboard = await Clipboard.getString();
props.onPasteTapped(clipboard);
};
const inputView = (
<View
style={{
backgroundColor: colors.inputBackgroundColor,
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'center',
maxHeight: 44,
}}
>
<BlueButtonLink title={loc.send.input_clear} onPress={props.onClearTapped} />
<BlueButtonLink title={loc.send.input_paste} onPress={onPasteTapped} />
<BlueButtonLink title={loc.send.input_done} onPress={Keyboard.dismiss} />
</View>
);
if (Platform.OS === 'ios') {
return <InputAccessoryView nativeID={BlueDoneAndDismissKeyboardInputAccessory.InputAccessoryViewID}>{inputView}</InputAccessoryView>;
} else {
return <KeyboardAvoidingView>{inputView}</KeyboardAvoidingView>;
}
}
};
export const BlueLoading = props => {
return (

View File

@ -9,7 +9,7 @@ const createHash = require('create-hash');
*/
export default class Lnurl {
static TAG_PAY_REQUEST = 'payRequest'; // type of LNURL
static TAG_WITHDRAW_REQUEST = "withdrawRequest"; // type of LNURL
static TAG_WITHDRAW_REQUEST = 'withdrawRequest'; // type of LNURL
constructor(url, AsyncStorage) {
this._lnurl = url;
@ -31,12 +31,12 @@ export default class Lnurl {
const found = Lnurl.findlnurl(lnurlExample);
if (!found) return false;
const decoded = bech32.decode(lnurlExample, 10000);
const decoded = bech32.decode(found, 10000);
return Buffer.from(bech32.fromWords(decoded.words)).toString();
}
static isLnurl(url) {
return Lnurl.findlnurl(url) !== null
return Lnurl.findlnurl(url) !== null;
}
async fetchGet(url) {

View File

@ -72,6 +72,21 @@ export default class WalletGradient {
return gradient;
}
static linearGradientProps(type) {
let props;
switch (type) {
case MultisigHDWallet.type:
/* Example
props = { start: { x: 0, y: 0 } };
https://github.com/react-native-linear-gradient/react-native-linear-gradient
*/
break;
default:
break;
}
return props;
}
static headerColorFor(type) {
let gradient;
switch (type) {

View File

@ -171,6 +171,9 @@ export class AbstractWallet {
// It is a ColdCard Hardware Wallet
masterFingerprint = Number(parsedSecret.keystore.ckcc_xfp);
}
if (parsedSecret.keystore.label) {
this.setLabel(parsedSecret.keystore.label);
}
this.secret = parsedSecret.keystore.xpub;
this.masterFingerprint = masterFingerprint;
}

View File

@ -278,7 +278,7 @@ PODS:
- react-native-tcp-socket (3.7.1):
- CocoaAsyncSocket
- React
- react-native-webview (10.10.0):
- react-native-webview (11.0.0):
- React-Core
- react-native-widget-center (0.0.4):
- React
@ -359,7 +359,7 @@ PODS:
- React-Core
- RNDefaultPreference (1.4.3):
- React
- RNDeviceInfo (6.2.0):
- RNDeviceInfo (7.0.2):
- React-Core
- RNFS (2.16.6):
- React
@ -709,7 +709,7 @@ SPEC CHECKSUMS:
react-native-safe-area-context: 01158a92c300895d79dee447e980672dc3fb85a6
react-native-slider: b733e17fdd31186707146debf1f04b5d94aa1a93
react-native-tcp-socket: 96a4f104cdcc9c6621aafe92937f163d88447c5b
react-native-webview: 2e330b109bfd610e9818bf7865d1979f898960a7
react-native-webview: f0da708d7e471b60ebdbf861c114d2c5d7f7af2d
react-native-widget-center: 0f81d17beb163e7fb5848b06754d7d277fe7d99a
React-RCTActionSheet: 53ea72699698b0b47a6421cb1c8b4ab215a774aa
React-RCTAnimation: 1befece0b5183c22ae01b966f5583f42e69a83c2
@ -729,7 +729,7 @@ SPEC CHECKSUMS:
RNCMaskedView: f5c7d14d6847b7b44853f7acb6284c1da30a3459
RNCPushNotificationIOS: eaf01f848a0b872b194d31bcad94bb864299e01e
RNDefaultPreference: 21816c0a6f61a2829ccc0cef034392e9b509ee5f
RNDeviceInfo: 980848feea8d74412b16f2e3e8758c8294d63ca2
RNDeviceInfo: a37a15a98822c31c3982cb28eba6d336ba4d0968
RNFS: 2bd9eb49dc82fa9676382f0585b992c424cd59df
RNGestureHandler: 7a5833d0f788dbd107fbb913e09aa0c1ff333c39
RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa

View File

@ -406,6 +406,7 @@
"xpub_copiedToClipboard": "Copied to clipboard.",
"pull_to_refresh": "Pull to Refresh",
"warning_do_not_disclose": "Warning! Do not disclose",
"add_ln_wallet_first": "You must first add a Lightning wallet.",
"xpub_title": "Wallet XPUB"
},
"multisig": {
@ -419,6 +420,7 @@
"confirm": "Confirm",
"header": "Send",
"share": "Share",
"manage_keys": "Manage Keys",
"how_many_signatures_can_bluewallet_make": "How Many Signatures Can BlueWallet Make",
"scan_or_import_file": "Scan or import file",
"export_coordination_setup": "Export Coordination Setup",

View File

@ -42,7 +42,7 @@ const LNDCreateInvoice = () => {
const { name } = useRoute();
const { colors } = useTheme();
const { navigate, dangerouslyGetParent, goBack, pop, setParams } = useNavigation();
const [unit, setUnit] = useState(wallet.current.getPreferredBalanceUnit());
const [unit, setUnit] = useState(wallet.current?.getPreferredBalanceUnit() || BitcoinUnit.BTC);
const [amount, setAmount] = useState();
const [renderWalletSelectionButtonHidden, setRenderWalletSelectionButtonHidden] = useState(false);
const [isLoading, setIsLoading] = useState(true);
@ -131,6 +131,10 @@ const LNDCreateInvoice = () => {
},
});
}
} else {
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
alert(loc.wallets.add_ln_wallet_first);
goBack();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet]),
@ -194,7 +198,7 @@ const LNDCreateInvoice = () => {
navigate('LNDViewInvoice', {
invoice: invoiceRequest,
walletID,
walletID: wallet.current.getID(),
isModal: true,
});
} catch (Err) {
@ -206,7 +210,7 @@ const LNDCreateInvoice = () => {
const processLnurl = async data => {
setIsLoading(true);
if (!wallet) {
if (!wallet.current) {
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
alert(loc.wallets.no_ln_wallet_error);
return goBack();
@ -233,7 +237,7 @@ const LNDCreateInvoice = () => {
screen: 'LnurlPay',
params: {
lnurl: data,
fromWalletID: walletID || wallet.current.getID(),
fromWalletID: wallet.current.getID(),
},
});
return;
@ -342,7 +346,7 @@ const LNDCreateInvoice = () => {
pop();
};
if (wallet.current === undefined || !walletID) {
if (!wallet.current) {
return (
<View style={[styles.root, styleHooks.root]}>
<StatusBar barStyle="light-content" />

View File

@ -189,6 +189,7 @@ const WalletsAddMultisigStep2 = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setCosigners(cosignersCopy);
setVaultKeyData({ keyIndex: cosignersCopy.length, seed: w.getSecret(), xpub: w.getXpub(), isLoading: false });
setIsLoading(true);
setIsMnemonicsModalVisible(true);
if (cosignersCopy.length === n) setIsOnCreateButtonEnabled(true);
// filling cache
@ -196,6 +197,7 @@ const WalletsAddMultisigStep2 = () => {
// filling cache
setXpubCacheForMnemonics(w.getSecret());
setFpCacheForMnemonics(w.getSecret());
setIsLoading(false);
}, 500);
});
};
@ -570,7 +572,11 @@ const WalletsAddMultisigStep2 = () => {
<BlueSpacing10 />
<View style={styles.secretContainer}>{renderSecret(vaultKeyData.seed.split(' '))}</View>
<BlueSpacing20 />
<BlueButton title={loc.send.success_done} onPress={() => setIsMnemonicsModalVisible(false)} />
{isLoading ? (
<ActivityIndicator />
) : (
<BlueButton title={loc.send.success_done} onPress={() => setIsMnemonicsModalVisible(false)} />
)}
</View>
</BottomModal>
);

View File

@ -12,7 +12,7 @@ import { BlueStorageContext } from '../../blue_modules/storage-context';
const styles = StyleSheet.create({
loading: {
flex: 1,
paddingTop: 20,
justifyContent: 'center',
},
root: {
flex: 1,

View File

@ -1,4 +1,4 @@
import React, { useCallback, useContext, useState } from 'react';
import React, { useCallback, useContext, useRef, useState } from 'react';
import { ActivityIndicator, InteractionManager, ScrollView, StatusBar, StyleSheet, View } from 'react-native';
import { BlueNavigationStyle, BlueSpacing20, BlueText, SafeBlueArea } from '../../BlueComponents';
import { DynamicQRCode } from '../../components/DynamicQRCode';
@ -14,29 +14,32 @@ const ExportMultisigCoordinationSetup = () => {
const walletId = useRoute().params.walletId;
const { wallets } = useContext(BlueStorageContext);
const wallet = wallets.find(w => w.getID() === walletId);
const qrCodeContents = Buffer.from(wallet.getXpub(), 'ascii').toString('hex');
const qrCodeContents = useRef();
const [isLoading, setIsLoading] = useState(true);
const [isShareButtonTapped, setIsShareButtonTapped] = useState(false);
const { goBack } = useNavigation();
const { colors } = useTheme();
const stylesHook = {
...styles,
const stylesHook = StyleSheet.create({
loading: {
...styles.loading,
backgroundColor: colors.elevated,
},
root: {
...styles.root,
backgroundColor: colors.elevated,
},
type: { ...styles.type, color: colors.foregroundColor },
secret: { ...styles.secret, color: colors.foregroundColor },
type: { color: colors.foregroundColor },
secret: { color: colors.foregroundColor },
exportButton: {
backgroundColor: colors.buttonDisabledBackgroundColor,
},
};
});
const exportTxtFile = async () => {
await fs.writeFileAndExport(wallet.getLabel() + '.txt', wallet.getXpub());
setIsShareButtonTapped(true);
setTimeout(() => {
fs.writeFileAndExport(wallet.getLabel() + '.txt', wallet.getXpub()).finally(() => {
setIsShareButtonTapped(false);
});
}, 10);
};
useFocusEffect(
@ -51,7 +54,7 @@ const ExportMultisigCoordinationSetup = () => {
return goBack();
}
}
qrCodeContents.current = Buffer.from(wallet.getXpub(), 'ascii').toString('hex');
setIsLoading(false);
}
});
@ -63,22 +66,26 @@ const ExportMultisigCoordinationSetup = () => {
);
return isLoading ? (
<View style={stylesHook.loading}>
<View style={[styles.loading, stylesHook.loading]}>
<ActivityIndicator />
</View>
) : (
<SafeBlueArea style={stylesHook.root}>
<SafeBlueArea style={[styles.root, stylesHook.root]}>
<StatusBar barStyle="light-content" />
<ScrollView contentContainerStyle={styles.scrollViewContent}>
<View>
<BlueText style={stylesHook.type}>{wallet.getLabel()}</BlueText>
<BlueText style={[styles.type, stylesHook.type]}>{wallet.getLabel()}</BlueText>
</View>
<BlueSpacing20 />
<DynamicQRCode value={qrCodeContents} capacity={400} />
<DynamicQRCode value={qrCodeContents.current} capacity={400} />
<BlueSpacing20 />
<SquareButton style={[styles.exportButton, stylesHook.exportButton]} onPress={exportTxtFile} title={loc.multisig.share} />
{isShareButtonTapped ? (
<ActivityIndicator />
) : (
<SquareButton style={[styles.exportButton, stylesHook.exportButton]} onPress={exportTxtFile} title={loc.multisig.share} />
)}
<BlueSpacing20 />
<BlueText style={stylesHook.secret}>{wallet.getXpub()}</BlueText>
<BlueText style={[styles.secret, stylesHook.secret]}>{wallet.getXpub()}</BlueText>
</ScrollView>
</SafeBlueArea>
);
@ -87,7 +94,7 @@ const ExportMultisigCoordinationSetup = () => {
const styles = StyleSheet.create({
loading: {
flex: 1,
paddingTop: 20,
justifyContent: 'center',
},
root: {
flex: 1,

View File

@ -1,27 +1,18 @@
import React, { Component } from 'react';
import React from 'react';
import { WebView } from 'react-native-webview';
import { BlueNavigationStyle, SafeBlueArea } from '../../BlueComponents';
import PropTypes from 'prop-types';
import { useRoute } from '@react-navigation/native';
export default class HodlHodlWebview extends Component {
constructor(props) {
super(props);
const HodlHodlWebview = () => {
const { uri } = useRoute().params;
const uri = props.route.params.uri;
this.state = {
uri,
};
}
render() {
return (
<SafeBlueArea>
<WebView source={{ uri: this.state.uri }} incognito />
</SafeBlueArea>
);
}
}
return (
<SafeBlueArea>
<WebView source={{ uri }} incognito />
</SafeBlueArea>
);
};
HodlHodlWebview.propTypes = {
route: PropTypes.shape({
@ -31,6 +22,8 @@ HodlHodlWebview.propTypes = {
}),
};
export default HodlHodlWebview;
HodlHodlWebview.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
title: '',

View File

@ -26,7 +26,7 @@ import { useRoute, useNavigation, useTheme, useFocusEffect } from '@react-naviga
import { Chain } from '../../models/bitcoinUnits';
import { BlueTransactionListItem, BlueWalletNavigationHeader, BlueAlertWalletExportReminder, BlueListItem } from '../../BlueComponents';
import WalletGradient from '../../class/wallet-gradient';
import { LightningCustodianWallet, WatchOnlyWallet } from '../../class';
import { LightningCustodianWallet, MultisigHDWallet, WatchOnlyWallet } from '../../class';
import HandoffSettings from '../../class/handoff';
import ActionSheet from '../ActionSheet';
import loc from '../../loc';
@ -569,6 +569,12 @@ const WalletTransactions = () => {
}
};
const navigateToViewEditCosigners = () => {
navigate('ViewEditMultisigCosigners', {
walletId: wallet.current.getID(),
});
};
return (
<View style={styles.flex}>
<StatusBar barStyle="light-content" backgroundColor={WalletGradient.headerColorFor(wallet.current.type)} />
@ -588,23 +594,27 @@ const WalletTransactions = () => {
})
}
onManageFundsPressed={() => {
if (wallet.current.getUserHasSavedExport()) {
setIsManageFundsModalVisible(true);
} else {
BlueAlertWalletExportReminder({
onSuccess: async () => {
wallet.current.setUserHasSavedExport(true);
await saveToDisk();
setIsManageFundsModalVisible(true);
},
onFailure: () =>
navigate('WalletExportRoot', {
screen: 'WalletExport',
params: {
walletID: wallet.current.getID(),
},
}),
});
if (wallet.current.type === MultisigHDWallet.type) {
navigateToViewEditCosigners();
} else if (wallet.current.type === LightningCustodianWallet.type) {
if (wallet.current.getUserHasSavedExport()) {
setIsManageFundsModalVisible(true);
} else {
BlueAlertWalletExportReminder({
onSuccess: async () => {
wallet.current.setUserHasSavedExport(true);
await saveToDisk();
setIsManageFundsModalVisible(true);
},
onFailure: () =>
navigate('WalletExportRoot', {
screen: 'WalletExport',
params: {
walletID: wallet.current.getID(),
},
}),
});
}
}
}}
/>

View File

@ -41,6 +41,8 @@ import MultipleStepsListItem, {
MultipleStepsListItemDashType,
} from '../../components/MultipleStepsListItem';
import ScanQRCode from '../send/ScanQRCode';
import Privacy from '../../Privacy';
import Biometric from '../../class/biometrics';
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
const isDesktop = getSystemName() === 'Mac OS X';
@ -48,7 +50,7 @@ const isDesktop = getSystemName() === 'Mac OS X';
const ViewEditMultisigCosigners = () => {
const { colors } = useTheme();
const { wallets, setWalletsWithNewOrder } = useContext(BlueStorageContext);
const { navigate, goBack } = useNavigation();
const { navigate } = useNavigation();
const route = useRoute();
const { walletId } = route.params;
const w = useRef(wallets.find(wallet => wallet.getID() === walletId));
@ -121,6 +123,16 @@ const ViewEditMultisigCosigners = () => {
const onSave = async () => {
setIsLoading(true);
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
if (isBiometricsEnabled) {
if (!(await Biometric.unlockWithBiometrics())) {
setIsLoading(false);
return;
}
}
// eslint-disable-next-line prefer-const
let newWallets = wallets.filter(w => {
return w.getID() !== walletId;
@ -128,14 +140,22 @@ const ViewEditMultisigCosigners = () => {
await wallet.fetchBalance();
newWallets.push(wallet);
setWalletsWithNewOrder(newWallets);
goBack();
goBack();
goBack();
navigate('WalletsList');
};
useFocusEffect(
useCallback(() => {
setIsLoading(true);
const task = InteractionManager.runAfterInteractions(() => {
Privacy.enableBlur();
const task = InteractionManager.runAfterInteractions(async () => {
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
if (isBiometricsEnabled) {
if (!(await Biometric.unlockWithBiometrics())) {
return goBack();
}
}
if (!w.current) {
// lets create fake wallet so renderer wont throw any errors
w.current = new MultisigHDWallet();
@ -148,9 +168,11 @@ const ViewEditMultisigCosigners = () => {
setIsLoading(false);
});
return () => {
Privacy.disableBlur();
task.cancel();
};
}, []),
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [walletId]),
);
const hideMnemonicsModal = () => {

View File

@ -165,6 +165,22 @@ describe('unit - DeepLinkSchemaMatch', function () {
},
],
},
{
argument: {
url:
'https://lnbits.com/?lightning=LNURL1DP68GURN8GHJ7MRWVF5HGUEWVDHK6TMHD96XSERJV9MJ7CTSDYHHVVF0D3H82UNV9UM9JDENFPN5SMMK2359J5RKWVMKZ5ZVWAV4VJD63TM',
},
expected: [
'LNDCreateInvoiceRoot',
{
screen: 'LNDCreateInvoice',
params: {
uri:
'https://lnbits.com/?lightning=LNURL1DP68GURN8GHJ7MRWVF5HGUEWVDHK6TMHD96XSERJV9MJ7CTSDYHHVVF0D3H82UNV9UM9JDENFPN5SMMK2359J5RKWVMKZ5ZVWAV4VJD63TM',
},
},
],
},
];
const asyncNavigationRouteFor = async function (event) {

View File

@ -21,6 +21,12 @@ describe('LNURL', function () {
Lnurl.getUrlFromLnurl('LNURL1DP68GURN8GHJ7MRWW3UXYMM59E3XJEMNW4HZU7RE0GHKCMN4WFKZ7URP0YLH2UM9WF5KG0FHXYCNV9G9W58'),
'https://lntxbot.bigsun.xyz/lnurl/pay?userid=7116',
);
assert.strictEqual(
Lnurl.getUrlFromLnurl(
'https://lnbits.com/?lightning=LNURL1DP68GURN8GHJ7MRWVF5HGUEWVDHK6TMHD96XSERJV9MJ7CTSDYHHVVF0D3H82UNV9UM9JDENFPN5SMMK2359J5RKWVMKZ5ZVWAV4VJD63TM',
),
'https://lnbits.com/withdraw/api/v1/lnurl/6Y73HgHovThYPvs7aPLwYV',
);
assert.strictEqual(Lnurl.getUrlFromLnurl('bs'), false);
});