mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-01-18 21:35:21 +01:00
ADD: airdrop ms cosigner (#5682)
This commit is contained in:
parent
8ebbdcc459
commit
cc728a01c9
18
App.js
18
App.js
@ -58,8 +58,15 @@ if (Platform.OS === 'android') {
|
||||
}
|
||||
|
||||
const App = () => {
|
||||
const { walletsInitialized, wallets, addWallet, saveToDisk, fetchAndSaveWalletTransactions, refreshAllWalletTransactions } =
|
||||
useContext(BlueStorageContext);
|
||||
const {
|
||||
walletsInitialized,
|
||||
wallets,
|
||||
addWallet,
|
||||
saveToDisk,
|
||||
fetchAndSaveWalletTransactions,
|
||||
refreshAllWalletTransactions,
|
||||
setSharedCosigner,
|
||||
} = useContext(BlueStorageContext);
|
||||
const appState = useRef(AppState.currentState);
|
||||
const clipboardContent = useRef();
|
||||
const colorScheme = useColorScheme();
|
||||
@ -328,7 +335,12 @@ const App = () => {
|
||||
};
|
||||
|
||||
const handleOpenURL = event => {
|
||||
DeeplinkSchemaMatch.navigationRouteFor(event, value => NavigationService.navigate(...value), { wallets, addWallet, saveToDisk });
|
||||
DeeplinkSchemaMatch.navigationRouteFor(event, value => NavigationService.navigate(...value), {
|
||||
wallets,
|
||||
addWallet,
|
||||
saveToDisk,
|
||||
setSharedCosigner,
|
||||
});
|
||||
};
|
||||
|
||||
const showClipboardAlert = ({ contentType }) => {
|
||||
|
@ -30,6 +30,7 @@ export const BlueStorageProvider = ({ children }) => {
|
||||
const [isElectrumDisabled, setIsElectrumDisabled] = useState(true);
|
||||
const [isTorDisabled, setIsTorDisabled] = useState(false);
|
||||
const [isPrivacyBlurEnabled, setIsPrivacyBlurEnabled] = useState(true);
|
||||
const [currentSharedCosigner, setCurrentSharedCosigner] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
BlueElectrum.isDisabled().then(setIsElectrumDisabled);
|
||||
@ -200,6 +201,10 @@ export const BlueStorageProvider = ({ children }) => {
|
||||
setWallets([...BlueApp.getWallets()]);
|
||||
};
|
||||
|
||||
const setSharedCosigner = cosigner => {
|
||||
setCurrentSharedCosigner(cosigner);
|
||||
};
|
||||
|
||||
let txMetadata = BlueApp.tx_metadata || {};
|
||||
const getTransactions = BlueApp.getTransactions;
|
||||
const isAdvancedModeEnabled = BlueApp.isAdvancedModeEnabled;
|
||||
@ -239,6 +244,8 @@ export const BlueStorageProvider = ({ children }) => {
|
||||
setSelectedWallet,
|
||||
addWallet,
|
||||
deleteWallet,
|
||||
currentSharedCosigner,
|
||||
setSharedCosigner,
|
||||
addAndSaveWallet,
|
||||
setItem,
|
||||
getItem,
|
||||
|
@ -31,7 +31,11 @@ class DeeplinkSchemaMatch {
|
||||
* @param event {{url: string}} URL deeplink as passed to app, e.g. `bitcoin:bc1qh6tf004ty7z7un2v5ntu4mkf630545gvhs45u7?amount=666&label=Yo`
|
||||
* @param completionHandler {function} Callback that returns [string, params: object]
|
||||
*/
|
||||
static navigationRouteFor(event, completionHandler, context = { wallets: [], saveToDisk: () => {}, addWallet: () => {} }) {
|
||||
static navigationRouteFor(
|
||||
event,
|
||||
completionHandler,
|
||||
context = { wallets: [], saveToDisk: () => {}, addWallet: () => {}, setSharedCosigner: () => {} },
|
||||
) {
|
||||
if (event.url === null) {
|
||||
return;
|
||||
}
|
||||
@ -104,6 +108,17 @@ class DeeplinkSchemaMatch {
|
||||
})
|
||||
.catch(e => console.warn(e));
|
||||
return;
|
||||
} else if (event.url.endsWith('.json')) {
|
||||
RNFS.readFile(decodeURI(event.url))
|
||||
.then(file => {
|
||||
// checks whether the necessary json keys are present in order to set a cosigner,
|
||||
// doesn't validate the values this happens later
|
||||
if (!file || !this.hasNeededJsonKeysForMultiSigSharing(file)) {
|
||||
return;
|
||||
}
|
||||
context.setSharedCosigner(file);
|
||||
})
|
||||
.catch(e => console.warn(e));
|
||||
}
|
||||
let isBothBitcoinAndLightning;
|
||||
try {
|
||||
@ -377,6 +392,20 @@ class DeeplinkSchemaMatch {
|
||||
return text.startsWith('widget?action=');
|
||||
}
|
||||
|
||||
static hasNeededJsonKeysForMultiSigSharing(str) {
|
||||
let obj;
|
||||
|
||||
// Check if it's a valid JSON
|
||||
try {
|
||||
obj = JSON.parse(str);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for the existence and type of the keys
|
||||
return typeof obj.xfp === 'string' && typeof obj.xpub === 'string' && typeof obj.path === 'string';
|
||||
}
|
||||
|
||||
static isBothBitcoinAndLightning(url) {
|
||||
if (url.includes('lightning') && (url.includes('bitcoin') || url.includes('BITCOIN'))) {
|
||||
const txInfo = url.split(/(bitcoin:\/\/|BITCOIN:\/\/|bitcoin:|BITCOIN:|lightning:|lightning=|bitcoin=)+/);
|
||||
|
@ -56,6 +56,18 @@
|
||||
<string>io.bluewallet.backup</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>JSON File</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.json</string>
|
||||
</array>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
@ -251,6 +263,29 @@
|
||||
</array>
|
||||
<key>UTImportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.text</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>JSON File</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>public.json</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>json</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<array>
|
||||
<string>application/json</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
|
@ -482,6 +482,8 @@
|
||||
"header": "Send",
|
||||
"share": "Share",
|
||||
"view": "View",
|
||||
"shared_key_detected": "Shared cosigner",
|
||||
"shared_key_detected_question": "A cosigner was shared with you, do you want to import it?",
|
||||
"manage_keys": "Manage Keys",
|
||||
"how_many_signatures_can_bluewallet_make": "how many signatures can BlueWallet make",
|
||||
"signatures_required_to_spend": "Signatures required {number}",
|
||||
|
@ -41,6 +41,7 @@ import { BlueStorageContext } from '../../blue_modules/storage-context';
|
||||
import { encodeUR } from '../../blue_modules/ur';
|
||||
import QRCodeComponent from '../../components/QRCodeComponent';
|
||||
import alert from '../../components/Alert';
|
||||
import confirm from '../../helpers/confirm';
|
||||
|
||||
const prompt = require('../../helpers/prompt');
|
||||
const A = require('../../blue_modules/analytics');
|
||||
@ -49,7 +50,8 @@ const isDesktop = getSystemName() === 'Mac OS X';
|
||||
const staticCache = {};
|
||||
|
||||
const WalletsAddMultisigStep2 = () => {
|
||||
const { addWallet, saveToDisk, isElectrumDisabled, isAdvancedModeEnabled, sleep } = useContext(BlueStorageContext);
|
||||
const { addWallet, saveToDisk, isElectrumDisabled, isAdvancedModeEnabled, sleep, currentSharedCosigner, setSharedCosigner } =
|
||||
useContext(BlueStorageContext);
|
||||
const { colors } = useTheme();
|
||||
|
||||
const navigation = useNavigation();
|
||||
@ -75,6 +77,20 @@ const WalletsAddMultisigStep2 = () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(currentSharedCosigner);
|
||||
if (currentSharedCosigner) {
|
||||
(async function () {
|
||||
if (await confirm(loc.multisig.shared_key_detected, loc.multisig.shared_key_detected_question)) {
|
||||
setImportText(currentSharedCosigner);
|
||||
setIsProvideMnemonicsModalVisible(true);
|
||||
setSharedCosigner('');
|
||||
}
|
||||
})();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentSharedCosigner]);
|
||||
|
||||
const handleOnHelpPress = () => {
|
||||
navigation.navigate('WalletsAddMultisigHelp');
|
||||
};
|
||||
@ -244,7 +260,7 @@ const WalletsAddMultisigStep2 = () => {
|
||||
setIsProvideMnemonicsModalVisible(true);
|
||||
};
|
||||
|
||||
const tryUsingXpub = async xpub => {
|
||||
const tryUsingXpub = async (xpub, fp, path) => {
|
||||
if (!MultisigHDWallet.isXpubForMultisig(xpub)) {
|
||||
setIsProvideMnemonicsModalVisible(false);
|
||||
setIsLoading(false);
|
||||
@ -253,25 +269,31 @@ const WalletsAddMultisigStep2 = () => {
|
||||
alert(loc.multisig.not_a_multisignature_xpub);
|
||||
return;
|
||||
}
|
||||
let fp;
|
||||
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 {
|
||||
return setIsLoading(false);
|
||||
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);
|
||||
}
|
||||
}
|
||||
let path;
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
setIsProvideMnemonicsModalVisible(false);
|
||||
@ -291,6 +313,20 @@ const WalletsAddMultisigStep2 = () => {
|
||||
if (MultisigHDWallet.isXpubValid(importText)) {
|
||||
return tryUsingXpub(importText);
|
||||
}
|
||||
try {
|
||||
const jsonText = JSON.parse(importText);
|
||||
let fp;
|
||||
let path;
|
||||
if (jsonText.xpub) {
|
||||
if (jsonText.xfp) {
|
||||
fp = jsonText.xfp;
|
||||
}
|
||||
if (jsonText.path) {
|
||||
path = jsonText.path;
|
||||
}
|
||||
return tryUsingXpub(jsonText.xpub, fp, path);
|
||||
}
|
||||
} catch {}
|
||||
const hd = new HDSegwitBech32Wallet();
|
||||
hd.setSecret(importText);
|
||||
if (!hd.validateMnemonic()) {
|
||||
|
@ -462,4 +462,22 @@ describe.each(['', '//'])('unit - DeepLinkSchemaMatch', function (suffix) {
|
||||
assert.strictEqual(DeeplinkSchemaMatch.getUrlFromSetLndhubUrlAction('gsom?url=https%3A%2F%2Flndhub.herokuapp.com%3A443'), false);
|
||||
assert.strictEqual(DeeplinkSchemaMatch.getUrlFromSetLndhubUrlAction('sdfhserhsthsd'), false);
|
||||
});
|
||||
|
||||
it('should accept only the one valid format', function () {
|
||||
// has all the necessary json keys
|
||||
const isAllowed1 = '{"xfp":"ffffffff", "path":"m/84\'/0\'/0\'", "xpub":"Zpubsnkjansdjnjnekjwcnwkjnc"}';
|
||||
// has all the necessary json keys, different order
|
||||
const isAllowed2 = '{"path":"m/84\'/0\'/0\'", "xpub":"Zpubsnkjansdjnjnekjwcnwkjnc", "xfp":"ffffffff"}';
|
||||
|
||||
//
|
||||
|
||||
assert.strictEqual(DeeplinkSchemaMatch.hasNeededJsonKeysForMultiSigSharing(isAllowed1), true);
|
||||
assert.strictEqual(DeeplinkSchemaMatch.hasNeededJsonKeysForMultiSigSharing(isAllowed2), true);
|
||||
|
||||
const isNotAllowed1 = '{"path":"m/84\'/0\'/0\'", "xpub":"Zpubsnkjansdjnjnekjwcnwkjnc"}';
|
||||
const isNotAllowed2 = '{"path":1233, "xpub":"Zpubsnkjansdjnjnekjwcnwkjnc", "xfp":"ffffffff"}';
|
||||
|
||||
assert.strictEqual(DeeplinkSchemaMatch.hasNeededJsonKeysForMultiSigSharing(isNotAllowed1), false);
|
||||
assert.strictEqual(DeeplinkSchemaMatch.hasNeededJsonKeysForMultiSigSharing(isNotAllowed2), false);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user