diff --git a/.eslintrc b/.eslintrc
index 7a580420..c1b08fab 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -18,7 +18,8 @@
"prettier/prettier": "error",
"react/jsx-props-no-spreading": "off",
"react/jsx-no-bind": "off",
- "react/forbid-prop-types": "off"
+ "react/forbid-prop-types": "off",
+ "import/prefer-default-export": "off"
},
"settings": {
"import/core-modules": ["test-utils"]
diff --git a/package-lock.json b/package-lock.json
index 803af36a..8d851861 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -24,6 +24,7 @@
"bitcoinjs-lib": "^5.1.7",
"bowser": "^2.6.1",
"buffer": "^6.0.3",
+ "caravan-descriptors": "file:../../caravan-descriptors",
"classnames": "^2.2.6",
"diff": "^4.0.2",
"fs-extra": "^9.0.0",
@@ -87,12 +88,33 @@
"prettier": "^2.0.2",
"standard-version": "^9.0.0",
"vite": "^4.2.3",
- "vite-plugin-node-polyfills": "^0.7.0"
+ "vite-plugin-node-polyfills": "^0.7.0",
+ "vite-plugin-wasm": "^3.2.2"
},
"engines": {
"node": ">=16"
}
},
+ "../../caravan-descriptors": {
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "unchained-bitcoin": "^0.6.0",
+ "unchained-wallets": "^0.6.0"
+ },
+ "devDependencies": {
+ "@jest/globals": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@types/jest": "^29.5.4",
+ "@typescript-eslint/eslint-plugin": "^6.7.0",
+ "@typescript-eslint/parser": "^6.7.0",
+ "eslint": "^8.49.0",
+ "jest": "^29.7.0",
+ "prettier": "^3.0.3",
+ "ts-jest": "^29.1.1",
+ "typescript": "^5.2.2"
+ }
+ },
"node_modules/@adobe/css-tools": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.2.0.tgz",
@@ -5902,6 +5924,10 @@
"node": ">=10.0.0"
}
},
+ "node_modules/caravan-descriptors": {
+ "resolved": "../../caravan-descriptors",
+ "link": true
+ },
"node_modules/cashaddrjs": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/cashaddrjs/-/cashaddrjs-0.4.4.tgz",
@@ -16483,6 +16509,16 @@
"vite": "^2.0.0 || ^3.0.0 || ^4.0.0"
}
},
+ "node_modules/vite-plugin-wasm": {
+ "version": "3.2.2",
+ "resolved": "https://artifacts.unchained-capital.us/repository/npm-dev-group/vite-plugin-wasm/-/vite-plugin-wasm-3.2.2.tgz",
+ "integrity": "sha512-cdbBUNR850AEoMd5nvLmnyeq63CSfoP1ctD/L2vLk/5+wsgAPlAVAzUK5nGKWO/jtehNlrSSHLteN+gFQw7VOA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "vite": "^2 || ^3 || ^4"
+ }
+ },
"node_modules/vite/node_modules/rollup": {
"version": "3.20.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.20.0.tgz",
@@ -21180,6 +21216,23 @@
"svg-pathdata": "^6.0.3"
}
},
+ "caravan-descriptors": {
+ "version": "file:../../caravan-descriptors",
+ "requires": {
+ "@jest/globals": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@types/jest": "^29.5.4",
+ "@typescript-eslint/eslint-plugin": "^6.7.0",
+ "@typescript-eslint/parser": "^6.7.0",
+ "eslint": "^8.49.0",
+ "jest": "^29.7.0",
+ "prettier": "^3.0.3",
+ "ts-jest": "^29.1.1",
+ "typescript": "^5.2.2",
+ "unchained-bitcoin": "^0.6.0",
+ "unchained-wallets": "^0.6.0"
+ }
+ },
"cashaddrjs": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/cashaddrjs/-/cashaddrjs-0.4.4.tgz",
@@ -29292,6 +29345,13 @@
"node-stdlib-browser": "^1.2.0"
}
},
+ "vite-plugin-wasm": {
+ "version": "3.2.2",
+ "resolved": "https://artifacts.unchained-capital.us/repository/npm-dev-group/vite-plugin-wasm/-/vite-plugin-wasm-3.2.2.tgz",
+ "integrity": "sha512-cdbBUNR850AEoMd5nvLmnyeq63CSfoP1ctD/L2vLk/5+wsgAPlAVAzUK5nGKWO/jtehNlrSSHLteN+gFQw7VOA==",
+ "dev": true,
+ "requires": {}
+ },
"vm-browserify": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
diff --git a/package.json b/package.json
index 03cad0d7..fecd4a30 100644
--- a/package.json
+++ b/package.json
@@ -52,7 +52,8 @@
"prettier": "^2.0.2",
"standard-version": "^9.0.0",
"vite": "^4.2.3",
- "vite-plugin-node-polyfills": "^0.7.0"
+ "vite-plugin-node-polyfills": "^0.7.0",
+ "vite-plugin-wasm": "^3.2.2"
},
"scripts": {
"build": "npm run check && VITE_GIT_SHA=`git rev-parse --short HEAD` vite build",
@@ -100,6 +101,7 @@
"bitcoinjs-lib": "^5.1.7",
"bowser": "^2.6.1",
"buffer": "^6.0.3",
+ "caravan-descriptors": "file:../../caravan-descriptors",
"classnames": "^2.2.6",
"diff": "^4.0.2",
"fs-extra": "^9.0.0",
diff --git a/src/components/Wallet/DownloadDescriptors.jsx b/src/components/Wallet/DownloadDescriptors.jsx
new file mode 100644
index 00000000..2a634fb9
--- /dev/null
+++ b/src/components/Wallet/DownloadDescriptors.jsx
@@ -0,0 +1,52 @@
+import React, { useEffect, useState } from "react";
+
+import { useSelector } from "react-redux";
+import { Button } from "@mui/material";
+import { getMaskedDerivation } from "unchained-bitcoin";
+import { encodeDescriptors } from "caravan-descriptors";
+import { getWalletConfig } from "../../selectors/wallet";
+import { downloadFile } from "../../utils";
+
+export const DownloadDescriptors = () => {
+ const walletConfig = useSelector(getWalletConfig);
+ const [descriptors, setDescriptors] = useState({ change: "", receive: "" });
+
+ useEffect(() => {
+ const loadAsync = async () => {
+ const multisigConfig = {
+ requiredSigners: walletConfig.quorum.requiredSigners,
+ keyOrigins: walletConfig.extendedPublicKeys.map(
+ ({ xfp, bip32Path, xpub }) => ({
+ xfp,
+ bip32Path: getMaskedDerivation({ xpub, bip32Path }),
+ xpub,
+ })
+ ),
+ addressType: walletConfig.addressType,
+ network: walletConfig.network,
+ };
+ const { change, receive } = await encodeDescriptors(multisigConfig);
+ setDescriptors({ change, receive });
+ };
+ loadAsync();
+ }, []);
+
+ const handleDownload = () => {
+ if (descriptors.change) {
+ downloadFile(
+ JSON.stringify(descriptors, null, 2),
+ `${walletConfig.uuid}.txt`
+ );
+ }
+ };
+
+ return (
+
+ );
+};
diff --git a/src/components/Wallet/WalletConfigInteractionButtons.jsx b/src/components/Wallet/WalletConfigInteractionButtons.jsx
index 8d38b147..9d9d20ca 100644
--- a/src/components/Wallet/WalletConfigInteractionButtons.jsx
+++ b/src/components/Wallet/WalletConfigInteractionButtons.jsx
@@ -2,6 +2,7 @@ import React from "react";
import PropTypes from "prop-types";
import { Button, Grid } from "@mui/material";
import { CARAVAN_CONFIG } from "./constants";
+import { DownloadDescriptors } from "./DownloadDescriptors";
const WalletConfigInteractionButtons = ({ onClearFn, onDownloadFn }) => {
const handleClearClick = (e) => {
@@ -26,6 +27,9 @@ const WalletConfigInteractionButtons = ({ onClearFn, onDownloadFn }) => {
Clear Wallet
+
+
+
);
};
diff --git a/src/components/Wallet/WalletDescriptorImporter.jsx b/src/components/Wallet/WalletDescriptorImporter.jsx
new file mode 100644
index 00000000..c61b7745
--- /dev/null
+++ b/src/components/Wallet/WalletDescriptorImporter.jsx
@@ -0,0 +1,76 @@
+import React, { useEffect, useState } from "react";
+import { Button } from "@mui/material";
+import { useDispatch, useSelector } from "react-redux";
+
+import { getWalletFromDescriptor, getChecksum } from "caravan-descriptors";
+import {
+ setRequiredSigners,
+ setTotalSigners,
+} from "../../actions/transactionActions";
+import { setAddressType } from "../../actions/settingsActions";
+import {
+ setExtendedPublicKeyImporterBIP32Path,
+ setExtendedPublicKeyImporterExtendedPublicKey,
+ setExtendedPublicKeyImporterExtendedPublicKeyRootFingerprint,
+ setExtendedPublicKeyImporterFinalized,
+ setExtendedPublicKeyImporterMethod,
+ setExtendedPublicKeyImporterName,
+} from "../../actions/extendedPublicKeyImporterActions";
+import { updateWalletUuidAction } from "../../actions/walletActions";
+
+const importWalletDetails = (
+ { keyOrigins, requiredSigners, addressType },
+ dispatch
+) => {
+ dispatch(setTotalSigners(keyOrigins.length));
+ dispatch(setRequiredSigners(requiredSigners));
+ dispatch(setAddressType(addressType));
+ keyOrigins.forEach(({ xfp, bip32Path, xpub }, index) => {
+ const number = index + 1;
+ dispatch(setExtendedPublicKeyImporterName(number, `key_${number}_${xfp}`));
+ dispatch(setExtendedPublicKeyImporterMethod(number, "text"));
+ dispatch(setExtendedPublicKeyImporterBIP32Path(number, bip32Path));
+ dispatch(
+ setExtendedPublicKeyImporterExtendedPublicKeyRootFingerprint(number, xfp)
+ );
+ dispatch(setExtendedPublicKeyImporterExtendedPublicKey(number, xpub));
+ dispatch(setExtendedPublicKeyImporterFinalized(number, true));
+ });
+};
+
+export const WalletDescriptorImporter = () => {
+ const [walletConfig, setWalletConfig] = useState();
+ const network = useSelector((state) => state.quorum.network);
+ const dispatch = useDispatch();
+ useEffect(() => {
+ if (walletConfig) {
+ importWalletDetails(walletConfig, dispatch);
+ }
+ }, [walletConfig]);
+
+ const handleClick = async () => {
+ // eslint-disable-next-line no-alert
+ const descriptor = window.prompt(
+ `Please enter one of the wallet's descriptors (change or receive).
+Make sure any settings that are not in a descriptor are set before submitting.`
+ );
+
+ if (descriptor) {
+ try {
+ const config = await getWalletFromDescriptor(descriptor, network);
+ const checksum = await getChecksum(descriptor);
+ dispatch(updateWalletUuidAction(checksum));
+ setWalletConfig(config);
+ } catch (e) {
+ // eslint-disable-next-line no-alert
+ window.alert(e.message);
+ }
+ }
+ };
+
+ return (
+
+ );
+};
diff --git a/src/components/Wallet/index.jsx b/src/components/Wallet/index.jsx
index 76c8150b..b3cd26b4 100644
--- a/src/components/Wallet/index.jsx
+++ b/src/components/Wallet/index.jsx
@@ -58,6 +58,7 @@ import {
SET_CLIENT_USERNAME,
} from "../../actions/clientActions";
import { clientPropTypes, slicePropTypes } from "../../proptypes";
+import { WalletDescriptorImporter } from "./WalletDescriptorImporter";
class CreateWallet extends React.Component {
static validateProperties(config, properties, key) {
@@ -356,12 +357,7 @@ class CreateWallet extends React.Component {
type="file"
/>
-