From 36a2091f8bbc14c0ed1ef0b14c54acfb45cc1344 Mon Sep 17 00:00:00 2001 From: JoeGruff Date: Fri, 2 Jun 2023 17:28:30 +0900 Subject: [PATCH] multi: Add ledger ui. Copy trezor elements and apply to ledger where possible. --- app/actions/DaemonActions.js | 7 +- app/actions/LedgerActions.js | 21 ++---- app/actions/TrezorActions.js | 11 --- app/actions/WalletLoaderActions.js | 12 ++- app/components/SideBar/MenuLinks/Links.jsx | 2 + app/components/SideBar/MenuLinks/hooks.js | 17 ++++- .../SendTransactionButton.jsx | 19 ++++- .../buttons/SendTransactionButton/hooks.js | 8 +- .../shared/SendTransaction/Form.jsx | 7 +- .../SendTransaction/SendTransaction.jsx | 2 + .../shared/SendTransaction/hooks.js | 2 + .../CreateWalletPage/CreateWalletPage.jsx | 27 ++++--- .../GetStartedPage/CreateWalletPage/hooks.js | 11 ++- .../CreateLedgerWalletForm.jsx | 75 +++++++++++++++++++ .../CreateLedgerWalletForm.module.css | 59 +++++++++++++++ .../CreateLedgerWalletForm/index.js | 1 + .../LedgerLoaderBarContainer.jsx | 23 ++++++ .../LedgerLoaderBarContainer.module.css | 63 ++++++++++++++++ .../LedgerLoaderBarContainer/hooks.js | 7 ++ .../LedgerLoaderBarContainer/index.js | 1 + .../PreCreateWallet/PreCreateWallet.jsx | 55 ++++++++++++-- .../GetStartedPage/PreCreateWallet/hooks.js | 57 ++++++++++++-- .../views/GetStartedPage/SetupWallet/hooks.js | 4 +- .../WalletSelection/Form/Form.jsx | 28 +++++++ .../WalletSelection/WalletSelection.jsx | 5 ++ app/components/views/GetStartedPage/hooks.js | 34 ++++++--- .../GetStartedPage/messages/messages.jsx | 9 +++ app/components/views/HomePage/HomePage.jsx | 3 +- .../LedgerPage/NoDevicePage/NoDevicePage.jsx | 19 +++++ .../NoDevicePage/NoDevicePage.module.css | 29 +++++++ .../views/LedgerPage/NoDevicePage/index.js | 1 + .../views/PrivacyPage/PrivacyPage.jsx | 38 ++++++---- .../ReceiveTab/ReceivePage/ReceivePage.jsx | 3 +- app/constants/config.js | 2 + app/helpers/ledger.js | 11 ++- app/hooks/useDaemonStartup.js | 25 +++++++ app/hooks/useLedger.js | 30 ++++++++ app/hooks/useSettings.js | 2 + .../docs/en/Ledger/PreCreateLedgerWallet1.md | 3 + .../docs/en/Ledger/PreCreateLedgerWallet2.md | 1 + app/i18n/docs/en/index.js | 3 + app/index.js | 5 ++ app/main_dev/externalRequests.js | 2 +- app/main_dev/ipc.js | 2 + app/reducers/index.js | 2 + app/reducers/ledger.js | 61 +++++++++++++++ app/reducers/snackbar.js | 16 ++++ app/reducers/walletLoader.js | 3 +- app/selectors.js | 16 +++- app/stateMachines/CreateWalletStateMachine.js | 13 +--- app/stateMachines/GetStartedStateMachine.js | 18 ++++- package.json | 7 +- test/unit/actions/DaemonActions.spec.js | 2 +- .../views/GetStaredPage/CreateWallet.spec.js | 1 + .../GetStaredPage/PreCreateWallet.spec.js | 7 +- test/unit/helpers/ledger.spec.js | 4 +- yarn.lock | 48 ++++++------ 57 files changed, 804 insertions(+), 140 deletions(-) create mode 100644 app/components/views/GetStartedPage/PreCreateWallet/CreateLedgerWalletForm/CreateLedgerWalletForm.jsx create mode 100644 app/components/views/GetStartedPage/PreCreateWallet/CreateLedgerWalletForm/CreateLedgerWalletForm.module.css create mode 100644 app/components/views/GetStartedPage/PreCreateWallet/CreateLedgerWalletForm/index.js create mode 100644 app/components/views/GetStartedPage/PreCreateWallet/LedgerLoaderBarContainer/LedgerLoaderBarContainer.jsx create mode 100644 app/components/views/GetStartedPage/PreCreateWallet/LedgerLoaderBarContainer/LedgerLoaderBarContainer.module.css create mode 100644 app/components/views/GetStartedPage/PreCreateWallet/LedgerLoaderBarContainer/hooks.js create mode 100644 app/components/views/GetStartedPage/PreCreateWallet/LedgerLoaderBarContainer/index.js create mode 100644 app/components/views/LedgerPage/NoDevicePage/NoDevicePage.jsx create mode 100644 app/components/views/LedgerPage/NoDevicePage/NoDevicePage.module.css create mode 100644 app/components/views/LedgerPage/NoDevicePage/index.js create mode 100644 app/hooks/useLedger.js create mode 100644 app/i18n/docs/en/Ledger/PreCreateLedgerWallet1.md create mode 100644 app/i18n/docs/en/Ledger/PreCreateLedgerWallet2.md create mode 100644 app/reducers/ledger.js diff --git a/app/actions/DaemonActions.js b/app/actions/DaemonActions.js index 7c05e98534..b658b6123c 100644 --- a/app/actions/DaemonActions.js +++ b/app/actions/DaemonActions.js @@ -9,6 +9,7 @@ import { stopNotifcations } from "./NotificationActions"; import { saveSettings, updateStateSettingsChanged } from "./SettingsActions"; import { rescanCancel, showCantCloseModal } from "./ControlActions"; import { enableTrezor } from "./TrezorActions"; +import { enableLedger } from "./LedgerActions"; import { DEX_LOGOUT_ATTEMPT, DEX_LOGOUT_SUCCESS, @@ -309,7 +310,7 @@ export const removeWallet = (selectedWallet) => (dispatch) => { // selectedWallet = { // label: newWalletName, // value: { -// wallet: newWalletName, isWatchingOnly, isTrezor, isNew, +// wallet: newWalletName, isWatchingOnly, isTrezor, isLedger, isNew // network: isTestNet ? "testnet" : "mainnet" // } // } @@ -336,7 +337,8 @@ export const createWallet = (selectedWallet) => (dispatch, getState) => dispatch({ isWatchingOnly: selectedWallet.value.isWatchingOnly, createNewWallet: selectedWallet.value.isNew, - isTrezor: selectedWallet.value.istrezor, + isTrezor: selectedWallet.value.isTrezor, + isLedger: selectedWallet.value.isLedger, type: WALLETCREATED }); dispatch(setSelectedWallet(selectedWallet)); @@ -509,6 +511,7 @@ export const startWallet = confirmDexSeed }); selectedWallet.value.isTrezor && dispatch(enableTrezor()); + selectedWallet.value.isLedger && dispatch(enableLedger()); await dispatch(getVersionServiceAttempt()); await dispatch(openWalletAttempt("", false, selectedWallet)); return discoverAccountsComplete; diff --git a/app/actions/LedgerActions.js b/app/actions/LedgerActions.js index 4c9d7e1b09..7e742d1543 100644 --- a/app/actions/LedgerActions.js +++ b/app/actions/LedgerActions.js @@ -14,7 +14,6 @@ import { const coin = "decred"; import * as selectors from "selectors"; -import * as cfgConstants from "constants/config"; export const LDG_LEDGER_ENABLED = "LDG_LEDGER_ENABLED"; export const LDG_WALLET_CLOSED = "LDG_WALLET_CLOSED"; @@ -26,18 +25,7 @@ export const LDG_WALLET_CLOSED = "LDG_WALLET_CLOSED"; // enableLedger only sets a value in the config. Ledger connections are made // per action then dropped. export const enableLedger = () => (dispatch, getState) => { - const walletName = selectors.getWalletName(getState()); - - if (walletName) { - const config = wallet.getWalletCfg( - selectors.isTestNet(getState()), - walletName - ); - config.set(cfgConstants.LEDGER, true); - } - dispatch({ type: LDG_LEDGER_ENABLED }); - connect()(dispatch, getState); }; @@ -143,13 +131,16 @@ export const LDG_GETWALLETCREATIONMASTERPUBKEY_SUCCESS = "LDG_GETWALLETCREATIONMASTERPUBKEY_SUCCESS"; export const getWalletCreationMasterPubKey = - () => async (dispatch /*, getState*/) => { + () => async (dispatch, getState) => { dispatch({ type: LDG_GETWALLETCREATIONMASTERPUBKEY_ATTEMPT }); // TODO: Enable on mainnet. - const isTestnet = true; + const isTestnet = selectors.isTestNet(getState()); + if (!isTestnet) { + throw "disabled on mainnet"; + } try { const payload = await getPubKey(isTestnet); - const hdpk = ledgerHelpers.fixPubKeyChecksum(payload); + const hdpk = ledgerHelpers.fixPubKeyChecksum(payload, isTestnet); dispatch({ type: LDG_GETWALLETCREATIONMASTERPUBKEY_SUCCESS }); return hdpk; } catch (error) { diff --git a/app/actions/TrezorActions.js b/app/actions/TrezorActions.js index b43a067f00..96e1175897 100644 --- a/app/actions/TrezorActions.js +++ b/app/actions/TrezorActions.js @@ -9,7 +9,6 @@ import { addressPath } from "helpers/trezor"; import { publishTransactionAttempt } from "./ControlActions"; -import * as cfgConstants from "constants/config"; import { MODEL1_DECRED_HOMESCREEN, MODELT_DECRED_HOMESCREEN @@ -49,16 +48,6 @@ export const TRZ_TREZOR_ENABLED = "TRZ_TREZOR_ENABLED"; // enableTrezor attepts to start a connection with connect if none exist and // connect to a trezor device. export const enableTrezor = () => (dispatch, getState) => { - const walletName = selectors.getWalletName(getState()); - - if (walletName) { - const config = wallet.getWalletCfg( - selectors.isTestNet(getState()), - walletName - ); - config.set(cfgConstants.TREZOR, true); - } - dispatch({ type: TRZ_TREZOR_ENABLED }); if (!setListeners) { diff --git a/app/actions/WalletLoaderActions.js b/app/actions/WalletLoaderActions.js index b3b6c77e7d..2a4134b8bb 100644 --- a/app/actions/WalletLoaderActions.js +++ b/app/actions/WalletLoaderActions.js @@ -18,7 +18,7 @@ import { getBestBlockHeightAttempt } from "./ClientActions"; import { WALLETREMOVED_FAILED } from "./DaemonActions"; -import { isTestNet, trezorDevice } from "selectors"; +import { isTestNet, trezorDevice, ledgerDevice } from "selectors"; import { walletrpc as api } from "middleware/walletrpc/api_pb"; import { push as pushHistory } from "connected-react-router"; import { stopNotifcations } from "./NotificationActions"; @@ -28,6 +28,7 @@ import * as cfgConstants from "constants/config"; import { RESCAN_PROGRESS } from "./ControlActions"; import { stopAccountMixer } from "./AccountMixerActions"; import { TRZ_WALLET_CLOSED } from "actions/TrezorActions"; +import { LDG_WALLET_CLOSED } from "actions/LedgerActions"; import { saveSettings, updateStateSettingsChanged } from "./SettingsActions"; const { SyncNotificationType } = api; @@ -156,7 +157,7 @@ export const CREATEWATCHONLYWALLET_FAILED = "CREATEWATCHONLYWALLET_FAILED"; export const CREATEWATCHONLYWALLET_SUCCESS = "CREATEWATCHONLYWALLET_SUCCESS"; export const createWatchOnlyWalletRequest = - (extendedPubKey, pubPass = "") => + (extendedPubKey, isLedger, isTrezor, pubPass = "") => (dispatch, getState) => new Promise((resolve, reject) => { dispatch({ type: CREATEWATCHONLYWALLET_ATTEMPT }); @@ -172,6 +173,12 @@ export const createWatchOnlyWalletRequest = } = getState(); const config = wallet.getWalletCfg(isTestNet(getState()), walletName); config.set(cfgConstants.IS_WATCH_ONLY, true); + if (isTrezor) { + config.set(cfgConstants.TREZOR, true); + } + if (isLedger) { + config.set(cfgConstants.LEDGER, true); + } config.delete(cfgConstants.DISCOVER_ACCOUNTS); wallet.setIsWatchingOnly(true); dispatch({ response: {}, type: CREATEWATCHONLYWALLET_SUCCESS }); @@ -269,6 +276,7 @@ const finalCloseWallet = () => async (dispatch, getState) => { await wallet.stopWallet(); dispatch({ type: CLOSEWALLET_SUCCESS }); if (trezorDevice(getState())) dispatch({ type: TRZ_WALLET_CLOSED }); + if (ledgerDevice(getState())) dispatch({ type: LDG_WALLET_CLOSED }); dispatch(pushHistory("/getstarted/initial")); } catch (error) { dispatch({ error, type: CLOSEWALLET_FAILED }); diff --git a/app/components/SideBar/MenuLinks/Links.jsx b/app/components/SideBar/MenuLinks/Links.jsx index 5e88fa800f..56a2b96a9c 100644 --- a/app/components/SideBar/MenuLinks/Links.jsx +++ b/app/components/SideBar/MenuLinks/Links.jsx @@ -4,6 +4,7 @@ export const LN_KEY = "ln"; export const DEX_KEY = "dex"; export const TICKETS_KEY = "tickets"; export const GOV_KEY = "governance"; +export const PRIV_KEY = "privacy"; export const linkList = [ { @@ -48,6 +49,7 @@ export const linkList = [ path: "/privacy", link: , icon: "securitycntr", + key: PRIV_KEY, ariaLabel: "Privacy" }, { diff --git a/app/components/SideBar/MenuLinks/hooks.js b/app/components/SideBar/MenuLinks/hooks.js index c9ec883b8a..bd2e53768c 100644 --- a/app/components/SideBar/MenuLinks/hooks.js +++ b/app/components/SideBar/MenuLinks/hooks.js @@ -1,7 +1,14 @@ import { useEffect, useState, useMemo } from "react"; import { useSelector } from "react-redux"; import * as sel from "selectors"; -import { linkList, LN_KEY, DEX_KEY, TICKETS_KEY, GOV_KEY } from "./Links"; +import { + linkList, + LN_KEY, + DEX_KEY, + TICKETS_KEY, + GOV_KEY, + PRIV_KEY +} from "./Links"; import { useHistory } from "react-router-dom"; import { cloneDeep } from "fp"; @@ -10,6 +17,7 @@ export function useMenuLinks() { const sidebarOnBottom = useSelector(sel.sidebarOnBottom); const expandSideBar = useSelector(sel.expandSideBar); const isTrezor = useSelector(sel.isTrezor); + const isLedger = useSelector(sel.isLedger); const lnEnabled = useSelector(sel.lnEnabled); const newActiveVoteProposalsCount = useSelector( @@ -47,11 +55,14 @@ export function useMenuLinks() { links = links.filter((l) => l.key !== LN_KEY); } // TODO: Enable ticket purchacing for Trezor. - if (isTrezor) { + if (isTrezor || isLedger) { links = links.filter( (l) => l.key !== DEX_KEY && l.key !== TICKETS_KEY && l.key !== GOV_KEY ); } + if (isLedger) { + links = links.filter((l) => l.key !== PRIV_KEY); + } return links.map((link) => ({ ...link, notifProp: link.notifProp?.reduce( @@ -59,7 +70,7 @@ export function useMenuLinks() { 0 ) })); - }, [notifProps, isTrezor, lnEnabled]); + }, [notifProps, isTrezor, lnEnabled, isLedger]); const [activeTabIndex, setActiveTabIndex] = useState(-1); const history = useHistory(); diff --git a/app/components/buttons/SendTransactionButton/SendTransactionButton.jsx b/app/components/buttons/SendTransactionButton/SendTransactionButton.jsx index ba2cf5b9ba..317241ca79 100644 --- a/app/components/buttons/SendTransactionButton/SendTransactionButton.jsx +++ b/app/components/buttons/SendTransactionButton/SendTransactionButton.jsx @@ -16,8 +16,10 @@ const SendTransactionButton = ({ unsignedTransaction, isSendingTransaction, isTrezor, + isLedger, onAttemptSignTransaction, - onAttemptSignTransactionTrezor + onAttemptSignTransactionTrezor, + onAttemptSignTransactionLedger } = useSendTransactionButton(); const signTransaction = (privpass) => { @@ -31,6 +33,12 @@ const SendTransactionButton = ({ onSubmit?.(); }; + const signTransactionLedger = () => { + if (disabled) return; + onAttemptSignTransactionLedger?.(unsignedTransaction); + onSubmit?.(); + }; + if (isTrezor) { return ( } ); + } else if (isLedger) { + return ( + + {buttonLabel ? buttonLabel : } + + ); } else { return ( { @@ -15,13 +17,17 @@ export function useSendTransactionButton() { }; const onAttemptSignTransactionTrezor = (rawUnsigTx, constructTxResponse) => dispatch(tza.signTransactionAttemptTrezor(rawUnsigTx, constructTxResponse)); + const onAttemptSignTransactionLedger = (rawUnsigTx) => + dispatch(ldgr.signTransactionAttemptLedger(rawUnsigTx)); return { unsignedTransaction, constructTxResponse, isSendingTransaction, isTrezor, + isLedger, onAttemptSignTransaction, - onAttemptSignTransactionTrezor + onAttemptSignTransactionTrezor, + onAttemptSignTransactionLedger }; } diff --git a/app/components/shared/SendTransaction/Form.jsx b/app/components/shared/SendTransaction/Form.jsx index 00e7b4332a..05cba321dd 100644 --- a/app/components/shared/SendTransaction/Form.jsx +++ b/app/components/shared/SendTransaction/Form.jsx @@ -22,6 +22,7 @@ const Form = ({ unsignedRawTx, isWatchingOnly, isTrezor, + isLedger, insuficientFunds, styles, hideDetails, @@ -71,7 +72,9 @@ const Form = ({ )} - {((isTrezor && isWatchingOnly) || !isWatchingOnly) && + {((isTrezor && isWatchingOnly) || + (isLedger && isWatchingOnly) || + !isWatchingOnly) && (getRunningIndicator ? ( - {unsignedRawTx && isWatchingOnly && !isTrezor && ( + {unsignedRawTx && isWatchingOnly && !isTrezor && !isLedger && ( } tx={unsignedRawTx} diff --git a/app/components/shared/SendTransaction/SendTransaction.jsx b/app/components/shared/SendTransaction/SendTransaction.jsx index 8eadf1e11f..faf7a834af 100644 --- a/app/components/shared/SendTransaction/SendTransaction.jsx +++ b/app/components/shared/SendTransaction/SendTransaction.jsx @@ -32,6 +32,7 @@ const SendTransaction = ({ totalSpent, notMixedAccounts, isTrezor, + isLedger, isWatchingOnly, isConstructingTransaction, attemptConstructTransaction, @@ -394,6 +395,7 @@ const SendTransaction = ({ willEnter, isWatchingOnly, isTrezor, + isLedger, insuficientFunds, styles, hideDetails, diff --git a/app/components/shared/SendTransaction/hooks.js b/app/components/shared/SendTransaction/hooks.js index 4a8d45eb3c..faf3c996de 100644 --- a/app/components/shared/SendTransaction/hooks.js +++ b/app/components/shared/SendTransaction/hooks.js @@ -24,6 +24,7 @@ export function useSendTransaction() { shallowEqual ); const isTrezor = useSelector(sel.isTrezor); + const isLedger = useSelector(sel.isLedger); const isWatchingOnly = useSelector(sel.isWatchingOnly); const isConstructingTransaction = useSelector(sel.isConstructingTransaction); const constructTxRequestAttempt = useSelector(sel.constructTxRequestAttempt); @@ -61,6 +62,7 @@ export function useSendTransaction() { totalSpent, notMixedAccounts, isTrezor, + isLedger, isWatchingOnly, isConstructingTransaction, attemptConstructTransaction, diff --git a/app/components/views/GetStartedPage/CreateWalletPage/CreateWalletPage.jsx b/app/components/views/GetStartedPage/CreateWalletPage/CreateWalletPage.jsx index 51f67f6245..5c61fc8b4c 100644 --- a/app/components/views/GetStartedPage/CreateWalletPage/CreateWalletPage.jsx +++ b/app/components/views/GetStartedPage/CreateWalletPage/CreateWalletPage.jsx @@ -103,18 +103,16 @@ const CreateWalletPage = ({ createWalletRef, onSendBack }) => { cancelCreateWallet ]); - const onCreateWatchOnly = useCallback(() => { - createWatchOnlyWalletRequest(walletMasterPubKey) - .then(() => sendEvent({ type: "WALLET_CREATED" })) - .catch((error) => sendEvent({ type: "ERROR", error })); - // we send a continue so we go to loading state - sendContinue(); - }, [ - createWatchOnlyWalletRequest, - sendEvent, - sendContinue, - walletMasterPubKey - ]); + const onCreateWatchOnly = useCallback( + (isLedger, isTrezor) => { + createWatchOnlyWalletRequest(walletMasterPubKey, isLedger, isTrezor) + .then(() => sendEvent({ type: "WALLET_CREATED" })) + .catch((error) => sendEvent({ type: "ERROR", error })); + // we send a continue so we go to loading state + sendContinue(); + }, + [createWatchOnlyWalletRequest, sendEvent, sendContinue, walletMasterPubKey] + ); const getStateComponent = useCallback(() => { const { mnemonic, error } = current.context; @@ -188,7 +186,8 @@ const CreateWalletPage = ({ createWalletRef, onSendBack }) => { ]); useEffect(() => { - const { isNew, walletMasterPubKey, mnemonic } = current.context; + const { isNew, walletMasterPubKey, mnemonic, isLedger, isTrezor } = + current.context; switch (current.value) { case "createWalletInit": setIsNew(isNew); @@ -222,7 +221,7 @@ const CreateWalletPage = ({ createWalletRef, onSendBack }) => { checkIsValid(); break; case "restoreWatchingOnly": - onCreateWatchOnly(); + onCreateWatchOnly(isLedger, isTrezor); break; case "finished": break; diff --git a/app/components/views/GetStartedPage/CreateWalletPage/hooks.js b/app/components/views/GetStartedPage/CreateWalletPage/hooks.js index cdcb279050..65ff6a21a7 100644 --- a/app/components/views/GetStartedPage/CreateWalletPage/hooks.js +++ b/app/components/views/GetStartedPage/CreateWalletPage/hooks.js @@ -21,8 +21,15 @@ export const useCreateWallet = () => { ); // TODO implement pubpass const createWatchOnlyWalletRequest = useCallback( - (extendedPubKey, pubPass = "") => - dispatch(wla.createWatchOnlyWalletRequest(extendedPubKey, pubPass)), + (extendedPubKey, isLedger, isTrezor, pubPass = "") => + dispatch( + wla.createWatchOnlyWalletRequest( + extendedPubKey, + isLedger, + isTrezor, + pubPass + ) + ), [dispatch] ); const createWalletRequest = useCallback( diff --git a/app/components/views/GetStartedPage/PreCreateWallet/CreateLedgerWalletForm/CreateLedgerWalletForm.jsx b/app/components/views/GetStartedPage/PreCreateWallet/CreateLedgerWalletForm/CreateLedgerWalletForm.jsx new file mode 100644 index 0000000000..69ecddc9f0 --- /dev/null +++ b/app/components/views/GetStartedPage/PreCreateWallet/CreateLedgerWalletForm/CreateLedgerWalletForm.jsx @@ -0,0 +1,75 @@ +import { FormattedMessage as T, defineMessages } from "react-intl"; +import { TextInput } from "inputs"; +import { KeyBlueButton, InvisibleButton } from "buttons"; +import styles from "./CreateLedgerWalletForm.module.css"; +import { Documentation } from "shared"; + +const messages = defineMessages({ + walletNameInputPlaceholder: { + id: "createLedgerWallet.walletNameInput.placeholder", + defaultMessage: "Choose a name for your Ledger Wallet" + }, + walletNameInputLabel: { + id: "createLedgerWallet.walletNameInput.label", + defaultMessage: "Wallet Name" + }, + messageWalletDupeNameError: { + id: "createLedgerWallet.dupeWalletName.error", + defaultMessage: "Please choose an unused wallet name" + } +}); + +const CreateLedgerWalletForm = ({ + createWallet, + hideCreateWalletForm, + newWalletName, + walletNameError, + onChangeCreateWalletName, + hasFailedAttemptName, + intl +}) => ( +
+
+ +
+
+ + + +
+ +
+ onChangeCreateWalletName(e.target.value)} + label={intl.formatMessage(messages.walletNameInputLabel)} + placeholder={intl.formatMessage(messages.walletNameInputPlaceholder)} + showErrors={hasFailedAttemptName} + className={styles.walletNameInput} + /> + +
+ + + + + + +
+
+
+); + +export default CreateLedgerWalletForm; diff --git a/app/components/views/GetStartedPage/PreCreateWallet/CreateLedgerWalletForm/CreateLedgerWalletForm.module.css b/app/components/views/GetStartedPage/PreCreateWallet/CreateLedgerWalletForm/CreateLedgerWalletForm.module.css new file mode 100644 index 0000000000..10040539b1 --- /dev/null +++ b/app/components/views/GetStartedPage/PreCreateWallet/CreateLedgerWalletForm/CreateLedgerWalletForm.module.css @@ -0,0 +1,59 @@ +.container { + width: 764px; +} + +.grid { + display: grid; + grid-template-columns: repeat(2, 30rem); + grid-column-gap: 76px; + font-size: 13px; + line-height: 17px; + color: var(--grey-7); + margin: 38px 0 32px 0; +} + +.title { + font-size: 27px; + line-height: 34px; + color: var(--grey-7); + width: 32rem; +} + +.textToggle { + font-size: 13px; + line-height: 16px; + height: 29px; + grid-column: 1; + margin-top: 20px; +} + +.textToggleChild { + padding: 6px 31px; +} + +.walletNameInput, +.walletNameInput input { + width: 279px !important; +} + +.buttonContrainer { + width: 279px; + display: flex; + justify-content: flex-end; + margin-top: 20px; +} + +.cancelBt, +.createWalletBt { + height: 44px; +} + +@media screen and (max-width: 768px) { + .container { + width: 355px; + } + + .grid { + grid-template-columns: 30rem; + } +} diff --git a/app/components/views/GetStartedPage/PreCreateWallet/CreateLedgerWalletForm/index.js b/app/components/views/GetStartedPage/PreCreateWallet/CreateLedgerWalletForm/index.js new file mode 100644 index 0000000000..b2674514b5 --- /dev/null +++ b/app/components/views/GetStartedPage/PreCreateWallet/CreateLedgerWalletForm/index.js @@ -0,0 +1 @@ +export { default } from "./CreateLedgerWalletForm"; diff --git a/app/components/views/GetStartedPage/PreCreateWallet/LedgerLoaderBarContainer/LedgerLoaderBarContainer.jsx b/app/components/views/GetStartedPage/PreCreateWallet/LedgerLoaderBarContainer/LedgerLoaderBarContainer.jsx new file mode 100644 index 0000000000..71b2aece0a --- /dev/null +++ b/app/components/views/GetStartedPage/PreCreateWallet/LedgerLoaderBarContainer/LedgerLoaderBarContainer.jsx @@ -0,0 +1,23 @@ +import { FormattedMessage as T } from "react-intl"; +import styles from "./LedgerLoaderBarContainer.module.css"; +import { useLedgerLoaderBarContainer } from "./hooks"; + +const LedgerLoaderBarContainer = ({ loaderBar }) => { + const { ledgerDevice } = useLedgerLoaderBarContainer(); + return ledgerDevice ? ( +
+
+ + + + | + + + +
+
{loaderBar}
+
+ ) : null; +}; + +export default LedgerLoaderBarContainer; diff --git a/app/components/views/GetStartedPage/PreCreateWallet/LedgerLoaderBarContainer/LedgerLoaderBarContainer.module.css b/app/components/views/GetStartedPage/PreCreateWallet/LedgerLoaderBarContainer/LedgerLoaderBarContainer.module.css new file mode 100644 index 0000000000..db06fe3a11 --- /dev/null +++ b/app/components/views/GetStartedPage/PreCreateWallet/LedgerLoaderBarContainer/LedgerLoaderBarContainer.module.css @@ -0,0 +1,63 @@ +.loaderBarContainer { + position: fixed; + bottom: 0; + left: 0; + right: 0; + box-shadow: 0px -4px 8px rgba(0, 0, 0, 0.1); + display: grid; + grid-template-columns: 1fr 1fr; + align-items: center; + background-size: 14px; + background-repeat: no-repeat; + background-position: 25px center; + background-image: var(--tz-create-bt); +} + +.loaderBar div { + border-radius: 0; +} + +.loaderBar, +.loaderBar > div { + height: 60px; + background: linear-gradient( + 270deg, + var(--display-wallet-gradient-selected-right), + var(--input-color) + ); + color: var(--white-button-text); + padding-left: 1rem; + font-size: 1.1rem; +} + +.loaderBar > div > div:nth-of-type(1) { + grid-column: 2; + grid-row: 1; + align-items: center; + display: flex; + padding-left: 1rem; +} +.loaderBar > div > div:nth-of-type(2n) { + grid-column: 1; + grid-row: 1; +} + +.loaderBar > div > div:nth-of-type(2n) > div { + background-size: 2rem; + background-position: center; +} + +.deviceStatus { + font-size: 13px; + line-height: 16px; + padding-left: 55px; +} + +.deviceLabel { + font-weight: 600; + color: var(--main-dark-blue); +} + +.connected { + color: var(--accent-blue); +} diff --git a/app/components/views/GetStartedPage/PreCreateWallet/LedgerLoaderBarContainer/hooks.js b/app/components/views/GetStartedPage/PreCreateWallet/LedgerLoaderBarContainer/hooks.js new file mode 100644 index 0000000000..e6b7f3a29a --- /dev/null +++ b/app/components/views/GetStartedPage/PreCreateWallet/LedgerLoaderBarContainer/hooks.js @@ -0,0 +1,7 @@ +import { useDaemonStartup } from "hooks"; + +export const useLedgerLoaderBarContainer = () => { + const { ledgerDevice } = useDaemonStartup(); + + return { ledgerDevice }; +}; diff --git a/app/components/views/GetStartedPage/PreCreateWallet/LedgerLoaderBarContainer/index.js b/app/components/views/GetStartedPage/PreCreateWallet/LedgerLoaderBarContainer/index.js new file mode 100644 index 0000000000..0f8e9e04c6 --- /dev/null +++ b/app/components/views/GetStartedPage/PreCreateWallet/LedgerLoaderBarContainer/index.js @@ -0,0 +1 @@ +export { default } from "./LedgerLoaderBarContainer"; diff --git a/app/components/views/GetStartedPage/PreCreateWallet/PreCreateWallet.jsx b/app/components/views/GetStartedPage/PreCreateWallet/PreCreateWallet.jsx index 545a208529..70261cc88d 100644 --- a/app/components/views/GetStartedPage/PreCreateWallet/PreCreateWallet.jsx +++ b/app/components/views/GetStartedPage/PreCreateWallet/PreCreateWallet.jsx @@ -1,9 +1,11 @@ import { FormattedMessage as T } from "react-intl"; import CreateWalletForm from "./CreateWalletForm"; import CreateTrezorWalletForm from "./CreateTrezorWalletForm"; +import CreateLedgerWalletForm from "./CreateLedgerWalletForm"; import { usePreCreateWallet } from "./hooks"; import { injectIntl } from "react-intl"; import NoDevicePage from "views/TrezorPage/NoDevicePage"; +import { default as NoDevicePageLedger } from "views/LedgerPage/NoDevicePage"; import styles from "./PreCreateWallet.module.css"; import { InvisibleButton } from "buttons"; @@ -15,7 +17,8 @@ const PreCreateWallet = ({ isCreateNewWallet, onShowCreateWallet, isTrezor, - creatingWallet + creatingWallet, + isLedger }) => { const { availableWallets, @@ -29,6 +32,7 @@ const PreCreateWallet = ({ walletNameError, isSPV, trezorDevice, + ledgerDevice, hideCreateWalletForm, onChangeCreateWalletName, createWallet, @@ -38,14 +42,16 @@ const PreCreateWallet = ({ toggleDisableCoinTypeUpgrades, gapLimit, setGapLimit, - connectTrezor + connectTrezor, + connectLedger } = usePreCreateWallet({ onSendContinue, onSendBack, onSendError, isCreateNewWallet, onShowCreateWallet, - isTrezor + isTrezor, + isLedger }); return isTrezor && !trezorDevice ? ( @@ -81,7 +87,45 @@ const PreCreateWallet = ({ disableCoinTypeUpgrades, toggleDisableCoinTypeUpgrades, gapLimit, - setGapLimit + setGapLimit, + isLedger + }} + /> + ) : isLedger && !ledgerDevice ? ( +
+ + + + +
+ ) : isLedger ? ( + ) : ( @@ -110,7 +154,8 @@ const PreCreateWallet = ({ disableCoinTypeUpgrades, toggleDisableCoinTypeUpgrades, gapLimit, - setGapLimit + setGapLimit, + isLedger }} /> ); diff --git a/app/components/views/GetStartedPage/PreCreateWallet/hooks.js b/app/components/views/GetStartedPage/PreCreateWallet/hooks.js index 41824abf41..30cd8fa93b 100644 --- a/app/components/views/GetStartedPage/PreCreateWallet/hooks.js +++ b/app/components/views/GetStartedPage/PreCreateWallet/hooks.js @@ -2,6 +2,7 @@ import { useState, useCallback, useEffect } from "react"; import { useDispatch } from "react-redux"; import { useDaemonStartup } from "hooks"; import * as trza from "actions/TrezorActions"; +import * as ldgr from "actions/LedgerActions"; export const usePreCreateWallet = ({ onSendContinue, @@ -9,7 +10,8 @@ export const usePreCreateWallet = ({ onSendError, isCreateNewWallet, onShowCreateWallet, - isTrezor + isTrezor, + isLedger }) => { const { isSPV, @@ -22,7 +24,12 @@ export const usePreCreateWallet = ({ trezorGetWalletCreationMasterPubKey, onCreateWallet, trezorEnable, - validateMasterPubKey + validateMasterPubKey, + ledgerDevice, + ledgerEnable, + ledgerDisable, + ledgerAlertNoConnectedDevice, + ledgerGetWalletCreationMasterPubKey } = useDaemonStartup(); const [newWalletName, setNewWalletName] = useState(""); const [isWatchingOnly, setIsWatchingOnly] = useState(false); @@ -38,8 +45,11 @@ export const usePreCreateWallet = ({ if (isTrezor) { trezorDisable(); } + if (isLedger) { + ledgerDisable(); + } onSendBack(); - }, [isTrezor, trezorDisable, onSendBack]); + }, [isTrezor, trezorDisable, onSendBack, isLedger, ledgerDisable]); const onChangeCreateWalletName = useCallback( (newWalletName) => { @@ -83,7 +93,18 @@ export const usePreCreateWallet = ({ } else { trezorDisable(); } - }, [isTrezor, trezorEnable, trezorDisable]); + if (isLedger) { + return ledgerEnable(); + } + return ledgerDisable(); + }, [ + isTrezor, + trezorEnable, + trezorDisable, + isLedger, + ledgerEnable, + ledgerDisable + ]); const toggleDisableCoinTypeUpgrades = () => setDisableCoinTypeUpgrades((value) => !value); @@ -96,6 +117,7 @@ export const usePreCreateWallet = ({ wallet: newWalletName, isWatchingOnly, isTrezor: !!isTrezor, + isLedger: !!isLedger, isNew, network: isTestNet ? "testnet" : "mainnet", gapLimit, @@ -112,8 +134,10 @@ export const usePreCreateWallet = ({ return; } if (isTrezor && !trezorDevice) { - trezorAlertNoConnectedDevice(); - return; + return trezorAlertNoConnectedDevice(); + } + if (isLedger && !ledgerDevice) { + return ledgerAlertNoConnectedDevice(); } // onSendContinue action so getStartedStateMachine can go to // creatingWallet state. @@ -130,6 +154,18 @@ export const usePreCreateWallet = ({ ) ); } + if (isLedger) { + walletSelected.isWatchingOnly = true; + return ledgerGetWalletCreationMasterPubKey().then((walletMasterPubKey) => + onCreateWallet(walletSelected).then(() => + onShowCreateWallet({ + isNew, + walletMasterPubKey, + isLedger: true + }) + ) + ); + } return onCreateWallet(walletSelected) .then(() => onShowCreateWallet({ isNew, walletMasterPubKey })) @@ -138,6 +174,7 @@ export const usePreCreateWallet = ({ isCreateNewWallet, isTestNet, isTrezor, + isLedger, isWatchingOnly, masterPubKeyError, newWalletName, @@ -147,6 +184,9 @@ export const usePreCreateWallet = ({ trezorAlertNoConnectedDevice, trezorDevice, trezorGetWalletCreationMasterPubKey, + ledgerAlertNoConnectedDevice, + ledgerDevice, + ledgerGetWalletCreationMasterPubKey, walletMasterPubKey, walletNameError, gapLimit, @@ -172,6 +212,7 @@ export const usePreCreateWallet = ({ const dispatch = useDispatch(); const connectTrezor = useCallback(() => dispatch(trza.connect()), [dispatch]); + const connectLedger = useCallback(() => dispatch(ldgr.connect()), [dispatch]); return { availableWallets, @@ -194,6 +235,8 @@ export const usePreCreateWallet = ({ toggleDisableCoinTypeUpgrades, gapLimit, setGapLimit, - connectTrezor + ledgerDevice, + connectTrezor, + connectLedger }; }; diff --git a/app/components/views/GetStartedPage/SetupWallet/hooks.js b/app/components/views/GetStartedPage/SetupWallet/hooks.js index d5b77e04a9..e346850c78 100644 --- a/app/components/views/GetStartedPage/SetupWallet/hooks.js +++ b/app/components/views/GetStartedPage/SetupWallet/hooks.js @@ -87,7 +87,7 @@ export const useWalletSetup = (settingUpWalletRef) => { isCreateNewWallet, isRestoreNewWallet } = ctx; - const { isWatchingOnly, isTrezor } = selectedWallet.value; + const { isWatchingOnly, isTrezor, isLedger } = selectedWallet.value; let component, hasSoloTickets; @@ -115,7 +115,7 @@ export const useWalletSetup = (settingUpWalletRef) => { switch (current.value) { case "settingAccountsPass": { // step not needed with trezor or watching only wallets. - if (isWatchingOnly || isTrezor) { + if (isWatchingOnly || isTrezor || isLedger) { sendContinue(); return; } diff --git a/app/components/views/GetStartedPage/WalletSelection/Form/Form.jsx b/app/components/views/GetStartedPage/WalletSelection/Form/Form.jsx index b8610894f6..b3bc54cf92 100644 --- a/app/components/views/GetStartedPage/WalletSelection/Form/Form.jsx +++ b/app/components/views/GetStartedPage/WalletSelection/Form/Form.jsx @@ -26,6 +26,7 @@ const WalletSelectionForm = ({ availableWallets, showCreateWalletForm, showCreateTrezorBackedWalletForm, + showCreateLedgerBackedWalletForm, onRemoveWallet, onToggleEditWallet, editWallets, @@ -39,6 +40,7 @@ const WalletSelectionForm = ({ const green = getThemeProperty(theme, "green-2"); const lightGreen = getThemeProperty(theme, "light-green"); const closeButtonColor = getThemeProperty(theme, "background-back-color"); + const ledgerIconColor = getThemeProperty(theme, "input-color-default"); const CreateButton = (props) => ( ); + const LedgerButton = (props) => ( + showCreateLedgerBackedWalletForm()} + text={intl.formatMessage(messages.ledgerTabMsg)} + iconColor={ledgerIconColor} + {...props} + /> + ); + return (
@@ -100,6 +112,7 @@ const WalletSelectionForm = ({ +
)} + {wallet.value.isLedger && ( +
+ + }> + + +
+ )} {wallet.isWatchingOnly && (
+ )}
diff --git a/app/components/views/GetStartedPage/WalletSelection/WalletSelection.jsx b/app/components/views/GetStartedPage/WalletSelection/WalletSelection.jsx index e4861af67a..a1a2751109 100644 --- a/app/components/views/GetStartedPage/WalletSelection/WalletSelection.jsx +++ b/app/components/views/GetStartedPage/WalletSelection/WalletSelection.jsx @@ -30,6 +30,10 @@ const WalletSelectionBody = ({ onSendCreateWallet(false, true); }, [onSendCreateWallet]); + const showCreateLedgerBackedWalletForm = useCallback(() => { + onSendCreateWallet(false, false, true); + }, [onSendCreateWallet]); + return ( diff --git a/app/components/views/GetStartedPage/hooks.js b/app/components/views/GetStartedPage/hooks.js index 6d6f6bfbc5..00bcd4dbc6 100644 --- a/app/components/views/GetStartedPage/hooks.js +++ b/app/components/views/GetStartedPage/hooks.js @@ -26,6 +26,7 @@ import styles from "./GetStarted.module.css"; import { isObject } from "lodash"; import { wallet } from "wallet-preload-shim"; import TrezorLoaderBarContainer from "views/GetStartedPage/PreCreateWallet/TrezorLoaderBarContainer"; +import LedgerLoaderBarContainer from "views/GetStartedPage/PreCreateWallet/LedgerLoaderBarContainer"; import { LoaderBarContainer } from "./helpers"; export const useGetStarted = () => { @@ -164,7 +165,7 @@ export const useGetStarted = () => { isAtStartWallet: (context) => { const { selectedWallet } = context; const { passPhrase } = context; - const { isWatchingOnly, isTrezor } = selectedWallet.value; + const { isWatchingOnly, isTrezor, isLedger } = selectedWallet.value; const hasPassPhrase = !!passPhrase; onStartWallet(selectedWallet, hasPassPhrase) .then((discoverAccountsComplete) => { @@ -174,7 +175,8 @@ export const useGetStarted = () => { !discoverAccountsComplete && !passPhrase && !isWatchingOnly && - !isTrezor + !isTrezor && + !isLedger ) { // Need to discover accounts and the passphrase isn't stored in // context, so ask for the private passphrase before continuing. @@ -373,7 +375,8 @@ export const useGetStarted = () => { ); const onSendCreateWallet = useCallback( - (isNew, isTrezor) => send({ type: "CREATE_WALLET", isNew, isTrezor }), + (isNew, isTrezor, isLedger) => + send({ type: "CREATE_WALLET", isNew, isTrezor, isLedger }), [send] ); @@ -409,12 +412,13 @@ export const useGetStarted = () => { ); const onShowCreateWallet = useCallback( - ({ isNew, walletMasterPubKey, isTrezor }) => + ({ isNew, walletMasterPubKey, isTrezor, isLedger }) => send({ type: "SHOW_CREATE_WALLET", isNew, walletMasterPubKey, - isTrezor + isTrezor, + isLedger }), [send] ); @@ -489,7 +493,8 @@ export const useGetStarted = () => { isTrezor, isSPV, createWalletRef, - settingUpWalletRef + settingUpWalletRef, + isLedger } = state.context; let component, text, animationType, PageComponent; @@ -560,9 +565,19 @@ export const useGetStarted = () => { m="Create a trezor wallet..." /> ); - hideHeader = isTrezor; - showLoaderBar = isTrezor; - loaderBarContainer = isTrezor ? TrezorLoaderBarContainer : null; + text = isLedger && ( + + ); + hideHeader = isTrezor || isLedger; + showLoaderBar = isTrezor || isLedger; + loaderBarContainer = isTrezor + ? TrezorLoaderBarContainer + : isLedger + ? LedgerLoaderBarContainer + : null; component = h(PreCreateWalletForm, { onShowCreateWallet, onSendContinue, @@ -570,6 +585,7 @@ export const useGetStarted = () => { onSendError, isCreateNewWallet, isTrezor, + isLedger, error }); break; diff --git a/app/components/views/GetStartedPage/messages/messages.jsx b/app/components/views/GetStartedPage/messages/messages.jsx index df9a250106..1fe4f2a005 100644 --- a/app/components/views/GetStartedPage/messages/messages.jsx +++ b/app/components/views/GetStartedPage/messages/messages.jsx @@ -81,6 +81,10 @@ export const messages = defineMessages({ id: "getStarted.trezor", defaultMessage: "Setup a Trezor Wallet" }, + ledgerTabMsg: { + id: "getStarted.ledger", + defaultMessage: "Setup a Ledger Wallet" + }, closeEditWallets: { id: "getStarted.closeEditWallets", defaultMessage: "Close" @@ -108,6 +112,11 @@ export const messages = defineMessages({ defaultMessage: "Trezor is a hardware wallet. For more information, visit {link}" }, + messageWalletLedgerDescription: { + id: "createwallet.ledger.description", + defaultMessage: + "Ledger is a hardware wallet. For more information, visit {link}" + }, messageWalletMasterPubKey: { id: "createwallet.walletpubkey.placeholder", defaultMessage: "Master Pub Key" diff --git a/app/components/views/HomePage/HomePage.jsx b/app/components/views/HomePage/HomePage.jsx index 0406db5189..2bfb2ad465 100644 --- a/app/components/views/HomePage/HomePage.jsx +++ b/app/components/views/HomePage/HomePage.jsx @@ -56,8 +56,9 @@ export default () => { // TODO: Enable ticket purchacing for Trezor. const isTrezor = useSelector(sel.isTrezor); + const isLedger = useSelector(sel.isLedger); let recentTickets, tabs; - if (isTrezor) { + if (isTrezor || isLedger) { tabs = [balanceTab, transactionsTab]; } else { recentTickets = ( diff --git a/app/components/views/LedgerPage/NoDevicePage/NoDevicePage.jsx b/app/components/views/LedgerPage/NoDevicePage/NoDevicePage.jsx new file mode 100644 index 0000000000..194ce9a8f4 --- /dev/null +++ b/app/components/views/LedgerPage/NoDevicePage/NoDevicePage.jsx @@ -0,0 +1,19 @@ +import styles from "./NoDevicePage.module.css"; +import { FormattedMessage as T } from "react-intl"; +import { KeyBlueButton } from "buttons"; + +const NoDevicePage = ({ onConnect }) => ( +
+
+ +
+ + + +
+); + +export default NoDevicePage; diff --git a/app/components/views/LedgerPage/NoDevicePage/NoDevicePage.module.css b/app/components/views/LedgerPage/NoDevicePage/NoDevicePage.module.css new file mode 100644 index 0000000000..ad8d45215e --- /dev/null +++ b/app/components/views/LedgerPage/NoDevicePage/NoDevicePage.module.css @@ -0,0 +1,29 @@ +.container { + display: flex; + flex-direction: column; + align-items: center; +} + +.desc { + font-size: 27px; + line-height: 34px; + color: var(--grey-7); + width: 522px; + text-align: center; + margin-bottom: 40px; +} + +.button { + height: 29px; + font-size: 13px; + line-height: 16.34px; + padding: 6px 10px; +} + +@media screen and (max-width: 768px) { + .desc { + width: 80%; + font-size: 22px; + line-height: 23px; + } +} diff --git a/app/components/views/LedgerPage/NoDevicePage/index.js b/app/components/views/LedgerPage/NoDevicePage/index.js new file mode 100644 index 0000000000..ef97d62181 --- /dev/null +++ b/app/components/views/LedgerPage/NoDevicePage/index.js @@ -0,0 +1 @@ +export { default } from "./NoDevicePage"; diff --git a/app/components/views/PrivacyPage/PrivacyPage.jsx b/app/components/views/PrivacyPage/PrivacyPage.jsx index 78739f7463..bd66a5ad1b 100644 --- a/app/components/views/PrivacyPage/PrivacyPage.jsx +++ b/app/components/views/PrivacyPage/PrivacyPage.jsx @@ -1,10 +1,12 @@ import { TabbedPage, TitleHeader, DescriptionHeader } from "layout"; import { FormattedMessage as T } from "react-intl"; +import { useSelector } from "react-redux"; import SecurityTab from "./SecurityTab"; import PrivacyTab from "./PrivacyTab"; import { usePrivacyPage } from "./hooks"; import styles from "./PrivacyPage.module.css"; import { SECURITY_ICON } from "constants"; +import * as sel from "selectors"; export const PrivacyTabHeader = () => { const { mixedAccountName, changeAccountName } = usePrivacyPage(); @@ -46,21 +48,27 @@ const PrivacyPageHeader = () => ( const PrivacyPage = () => { const { privacyEnabled } = usePrivacyPage(); - const tabs = [ - { - path: "/privacy/mixing", - content: PrivacyTab, - header: PrivacyTabHeader, - label: , - disabled: !privacyEnabled - }, - { - path: "/privacy/security", - content: SecurityTab, - header: PrivacyTabHeader, - label: - } - ]; + const isTrezor = useSelector(sel.isTrezor); + const mixingTab = { + path: "/privacy/mixing", + content: PrivacyTab, + header: PrivacyTabHeader, + label: , + disabled: !privacyEnabled + }; + const securityTab = { + path: "/privacy/security", + content: SecurityTab, + header: PrivacyTabHeader, + label: + }; + let tabs; + // Cannot currently mix with trezor and ledger hides this tab altogether. + if (isTrezor) { + tabs = [securityTab]; + } else { + tabs = [mixingTab, securityTab]; + } return } tabs={tabs} />; }; diff --git a/app/components/views/TransactionsPage/ReceiveTab/ReceivePage/ReceivePage.jsx b/app/components/views/TransactionsPage/ReceiveTab/ReceivePage/ReceivePage.jsx index 48d28f37d5..045fe9e2f7 100644 --- a/app/components/views/TransactionsPage/ReceiveTab/ReceivePage/ReceivePage.jsx +++ b/app/components/views/TransactionsPage/ReceiveTab/ReceivePage/ReceivePage.jsx @@ -54,8 +54,9 @@ const ReceivePage = ({ // TODO: Enable ticket purchacing for Trezor. const isTrezor = useSelector(sel.isTrezor); + const isLedger = useSelector(sel.isLedger); let hardwareWalletWarning; - if (isTrezor) { + if (isTrezor || isLedger) { const warningStr = "Caution! Hardware wallets cannot spend from special/staking inputs. " + "Only use this address for receiving funds from normal transacitons. Do not " + diff --git a/app/constants/config.js b/app/constants/config.js index d700935986..4f1df9200c 100644 --- a/app/constants/config.js +++ b/app/constants/config.js @@ -59,6 +59,7 @@ export const IS_WATCH_ONLY = "iswatchonly"; export const POLITEIA_LAST_ACCESS_TIME = "politeia_last_access_time"; export const POLITEIA_LAST_ACCESS_BLOCK = "politeia_last_access_block"; export const TREZOR = "trezor"; +export const LEDGER = "ledger"; export const ENABLE_PRIVACY = "enableprivacy"; export const LN_ACCOUNT = "ln_account"; export const LN_ADDRESS = "ln_address"; @@ -98,6 +99,7 @@ export const WALLET_INITIAL_VALUE = { [POLITEIA_LAST_ACCESS_TIME]: 0, [POLITEIA_LAST_ACCESS_BLOCK]: 0, [TREZOR]: false, + [LEDGER]: false, // enable_privacy only shows the privacy menu on the wallet [ENABLE_PRIVACY]: true, [LN_ACCOUNT]: null, diff --git a/app/helpers/ledger.js b/app/helpers/ledger.js index 3948b27e89..c04452f17a 100644 --- a/app/helpers/ledger.js +++ b/app/helpers/ledger.js @@ -14,7 +14,16 @@ export function addressPath(branch, index) { // fixPubKeyChecksum replaces the sha256 checksum, or last four bytes, of a // pubkey with a blake256 checksum. -export function fixPubKeyChecksum(pubKey) { +export function fixPubKeyChecksum(pubKey, isTestnet) { + const mainnetPubPrefix = "dpub"; + const testnetPubPrefix = "tpub"; + if (isTestnet) { + if (pubKey.slice(0, 4) !== testnetPubPrefix) { + throw "pubkey is not for testnet"; + } + } else if (pubKey.slice(0, 4) !== mainnetPubPrefix) { + throw "pubkey is not for mainnet"; + } const buff = bs58.decode(pubKey).slice(0, -4); const firstPass = blake("blake256").update(Buffer.from(buff)).digest(); const secondPass = blake("blake256").update(firstPass).digest(); diff --git a/app/hooks/useDaemonStartup.js b/app/hooks/useDaemonStartup.js index 676aea1f0f..20f2588321 100644 --- a/app/hooks/useDaemonStartup.js +++ b/app/hooks/useDaemonStartup.js @@ -6,6 +6,7 @@ import * as da from "actions/DaemonActions"; import * as ca from "actions/ClientActions"; import * as ctrla from "actions/ControlActions"; import * as trza from "actions/TrezorActions"; +import * as ldgr from "actions/LedgerActions"; import * as ama from "actions/AccountMixerActions"; import * as va from "actions/VSPActions"; @@ -38,7 +39,9 @@ const useDaemonStartup = () => { const getDaemonStarted = useSelector(sel.getDaemonStarted); const getEstimatedTimeLeft = useSelector(sel.getEstimatedTimeLeft); const trezorDevice = useSelector(sel.trezorDevice); + const ledgerDevice = useSelector(sel.ledgerDevice); const isTrezor = useSelector(sel.isTrezor); + const isLedger = useSelector(sel.isLedger); const syncAttemptRequest = useSelector(sel.getSyncAttemptRequest); const daemonWarning = useSelector(sel.daemonWarning); const isSettingAccountsPassphrase = useSelector( @@ -230,6 +233,22 @@ const useDaemonStartup = () => { () => dispatch(trza.getWalletCreationMasterPubKey()), [dispatch] ); + const ledgerEnable = useCallback( + () => dispatch(ldgr.enableLedger()), + [dispatch] + ); + const ledgerDisable = useCallback( + () => dispatch(ldgr.disableLedger()), + [dispatch] + ); + const ledgerAlertNoConnectedDevice = useCallback( + () => dispatch(ldgr.alertNoConnectedDevice()), + [dispatch] + ); + const ledgerGetWalletCreationMasterPubKey = useCallback( + () => dispatch(ldgr.getWalletCreationMasterPubKey()), + [dispatch] + ); const validateMasterPubKey = useCallback( (masterPubKey) => dispatch(ctrla.validateMasterPubKey(masterPubKey)), [dispatch] @@ -279,6 +298,11 @@ const useDaemonStartup = () => { trezorDisable, trezorEnable, trezorLoadDeviceList, + ledgerGetWalletCreationMasterPubKey, + ledgerAlertNoConnectedDevice, + ledgerDisable, + ledgerEnable, + ledgerDevice, getDcrwalletLogs, onCreateWallet, goToErrorPage, @@ -325,6 +349,7 @@ const useDaemonStartup = () => { getEstimatedTimeLeft, trezorDevice, isTrezor, + isLedger, peerCount, synced, syncFetchMissingCfiltersAttempt, diff --git a/app/hooks/useLedger.js b/app/hooks/useLedger.js new file mode 100644 index 0000000000..0f5abeb950 --- /dev/null +++ b/app/hooks/useLedger.js @@ -0,0 +1,30 @@ +import { useSelector, useDispatch } from "react-redux"; +import { useCallback } from "react"; +import * as sel from "selectors"; +import * as ldgr from "actions/LedgerActions"; + +const useLedger = () => { + const isLedger = useSelector(sel.isLedger); + const device = useSelector(sel.ledgerDevice); + const walletCreationMasterPubkeyAttempt = useSelector( + sel.ledgerWalletCreationMasterPubkeyAttempt + ); + + const dispatch = useDispatch(); + + const onConnect = useCallback(() => dispatch(ldgr.connect()), [dispatch]); + const onEnableLedger = useCallback( + () => dispatch(ldgr.enableLedger()), + [dispatch] + ); + + return { + isLedger, + device, + walletCreationMasterPubkeyAttempt, + onConnect, + onEnableLedger + }; +}; + +export default useLedger; diff --git a/app/hooks/useSettings.js b/app/hooks/useSettings.js index 7a09c2b75e..4167becfc1 100644 --- a/app/hooks/useSettings.js +++ b/app/hooks/useSettings.js @@ -24,6 +24,7 @@ const useSettings = () => { const walletName = useSelector(sel.getWalletName); const walletReady = useSelector(sel.getWalletReady); const isTrezor = useSelector(sel.isTrezor); + const isLedger = useSelector(sel.isLedger); const onAttemptChangePassphrase = useCallback( (oldPass, args) => { @@ -124,6 +125,7 @@ const useSettings = () => { walletName, walletReady, isTrezor, + isLedger, onAttemptChangePassphrase, onChangeTempSettings, onSaveSettings, diff --git a/app/i18n/docs/en/Ledger/PreCreateLedgerWallet1.md b/app/i18n/docs/en/Ledger/PreCreateLedgerWallet1.md new file mode 100644 index 0000000000..e2719b812c --- /dev/null +++ b/app/i18n/docs/en/Ledger/PreCreateLedgerWallet1.md @@ -0,0 +1,3 @@ +You must use an already setup Ledger “Device Wallet”, a device already initialized with a seed and the decred app installed. New devices should be initialized via [ledger live](https://www.ledger.com/ledger-live/) and the decred app downloaded and installed there. + +A Ledger-backed wallet can be used with multiple cryptocurrencies without conflict. In other words, if you already use your device wallet with other coins, you can keep using it and use the same seed for your Decrediton wallet. diff --git a/app/i18n/docs/en/Ledger/PreCreateLedgerWallet2.md b/app/i18n/docs/en/Ledger/PreCreateLedgerWallet2.md new file mode 100644 index 0000000000..a65e8d55df --- /dev/null +++ b/app/i18n/docs/en/Ledger/PreCreateLedgerWallet2.md @@ -0,0 +1 @@ +Ledger wallets used within Decrediton do not currently support staking operations (purchasing, voting and revoking tickets) or signing messages. diff --git a/app/i18n/docs/en/index.js b/app/i18n/docs/en/index.js index d203c295dd..1fc827f2da 100644 --- a/app/i18n/docs/en/index.js +++ b/app/i18n/docs/en/index.js @@ -94,3 +94,6 @@ export { default as TrezorRecoverDevice } from "./Trezor/RecoverDevice.md"; export { default as TrezorWipeDevice } from "./Trezor/WipeDevice.md"; export { default as PreCreateTrezorWallet1 } from "./Trezor/PreCreateTrezorWallet1.md"; export { default as PreCreateTrezorWallet2 } from "./Trezor/PreCreateTrezorWallet2.md"; + +export { default as PreCreateLedgerWallet1 } from "./Ledger/PreCreateLedgerWallet1.md"; +export { default as PreCreateLedgerWallet2 } from "./Ledger/PreCreateLedgerWallet2.md"; diff --git a/app/index.js b/app/index.js index 48abab17fc..20493999a4 100644 --- a/app/index.js +++ b/app/index.js @@ -406,6 +406,11 @@ const initialState = { performingTogglePassphraseOnDeviceProtection: false, deviceLabel: undefined }, + ledger: { + enabled: false, + device: false, + walletCreationMasterPubkeyAttempt: false + }, ln: { enabled: globalCfg.get(cfgConstants.LN_ENABLED), active: false, diff --git a/app/main_dev/externalRequests.js b/app/main_dev/externalRequests.js index 0eef75a5b9..a87463a7d5 100644 --- a/app/main_dev/externalRequests.js +++ b/app/main_dev/externalRequests.js @@ -78,7 +78,7 @@ export const installSessionHandlers = (mainLogger) => { ); callback({ cancel: true, requestHeaders: details.requestHeaders }); } else { - //logger.log("verbose", details.method + " " + details.url); + // logger.log("verbose", details.method + " " + details.url); if ( allowedExternalRequests[EXTERNALREQUEST_TREZOR_BRIDGE] && /^http:\/\/127.0.0.1:21325\//.test(details.url) diff --git a/app/main_dev/ipc.js b/app/main_dev/ipc.js index 204968eb5e..7f0c5f1b5b 100644 --- a/app/main_dev/ipc.js +++ b/app/main_dev/ipc.js @@ -56,6 +56,7 @@ export const getAvailableWallets = (network) => { const lastAccess = cfg.get(cfgConstants.LAST_ACCESS); const isWatchingOnly = cfg.get(cfgConstants.IS_WATCH_ONLY); const isTrezor = cfg.get(cfgConstants.TREZOR); + const isLedger = cfg.get(cfgConstants.LEDGER); const isPrivacy = cfg.get(cfgConstants.MIXED_ACCOUNT_CFG); const walletDbFilePath = getWalletDb(isTestNet, wallet); const finished = fs.existsSync(walletDbFilePath); @@ -68,6 +69,7 @@ export const getAvailableWallets = (network) => { lastAccess, isWatchingOnly, isTrezor, + isLedger, isPrivacy, isLN, displayWalletGradient diff --git a/app/reducers/index.js b/app/reducers/index.js index e0f50f4f35..51c57de385 100644 --- a/app/reducers/index.js +++ b/app/reducers/index.js @@ -12,6 +12,7 @@ import snackbar from "./snackbar"; import statistics from "./statistics"; import governance from "./governance"; import trezor from "./trezor"; +import ledger from "./ledger"; import ln from "./ln"; import vsp from "./vsp"; import dex from "./dex"; @@ -30,6 +31,7 @@ export default { statistics, governance, trezor, + ledger, ln, vsp, dex diff --git a/app/reducers/ledger.js b/app/reducers/ledger.js new file mode 100644 index 0000000000..db47290a5b --- /dev/null +++ b/app/reducers/ledger.js @@ -0,0 +1,61 @@ +import { + LDG_WALLET_CLOSED, + LDG_LEDGER_ENABLED, + LDG_LEDGER_DISABLED, + LDG_CONNECT_ATTEMPT, + LDG_CONNECT_FAILED, + LDG_CONNECT_SUCCESS, + LDG_NOCONNECTEDDEVICE, + LDG_GETWALLETCREATIONMASTERPUBKEY_ATTEMPT, + LDG_GETWALLETCREATIONMASTERPUBKEY_FAILED, + LDG_GETWALLETCREATIONMASTERPUBKEY_SUCCESS +} from "actions/LedgerActions"; +import { CLOSEWALLET_SUCCESS } from "actions/WalletLoaderActions"; + +export default function ledger(state = {}, action) { + switch (action.type) { + case LDG_LEDGER_ENABLED: + return { ...state, enabled: true }; + case LDG_LEDGER_DISABLED: + return { ...state, enabled: false }; + case LDG_CONNECT_ATTEMPT: + return { + ...state, + connectAttempt: true + }; + case LDG_CONNECT_SUCCESS: + return { + ...state, + // Ledger does not keep a constant connection. Device is set to true on + // the first successful attempt and left true until the wallet is closed. + device: true, + connectAttempt: false + }; + case LDG_CONNECT_FAILED: + return { + ...state, + connectError: action.error, + connectAttempt: false + }; + case LDG_NOCONNECTEDDEVICE: + // We don't currently listen for reconnect so not deleting the device. + return { + ...state + }; + case LDG_WALLET_CLOSED: + return { + ...state, + device: false, + connected: false + }; + case LDG_GETWALLETCREATIONMASTERPUBKEY_ATTEMPT: + return { ...state, walletCreationMasterPubkeyAttempt: true }; + case LDG_GETWALLETCREATIONMASTERPUBKEY_SUCCESS: + case LDG_GETWALLETCREATIONMASTERPUBKEY_FAILED: + return { ...state, walletCreationMasterPubkeyAttempt: false }; + case CLOSEWALLET_SUCCESS: + return { ...state, enabled: false }; + default: + return state; + } +} diff --git a/app/reducers/snackbar.js b/app/reducers/snackbar.js index fb0f74682e..287a318786 100644 --- a/app/reducers/snackbar.js +++ b/app/reducers/snackbar.js @@ -105,6 +105,10 @@ import { TRZ_GETWALLETCREATIONMASTERPUBKEY_FAILED, TRZ_NOTBACKEDUP } from "actions/TrezorActions"; +import { + LDG_NOCONNECTEDDEVICE, + LDG_GETWALLETCREATIONMASTERPUBKEY_FAILED +} from "actions/LedgerActions"; import { NEW_TRANSACTIONS_RECEIVED, TRANSACTION_TYPES, @@ -362,6 +366,16 @@ const messages = defineMessages({ defaultMessage: "Trezor must be backed up in order to perform this operation." }, + LDG_NOCONNECTEDDEVICE: { + id: "ledger.noConnectedDevice", + defaultMessage: + "No Ledger device connected. Check the device connection and Ledger bridge." + }, + LDG_GETWALLETCREATIONMASTERPUBKEY_FAILED: { + id: "ledger.getWalletCreationMasterPubKey.failed", + defaultMessage: + "Failed to obtain master extended pubkey from Ledger device: {originalError}" + }, ERROR_IS_OBJECT: { id: "snackbar.errorObject", defaultMessage: "The following error happened: {error}" @@ -839,6 +853,8 @@ export default function snackbar(state = {}, action) { case TRZ_NOTBACKEDUP: case TRZ_BACKUPDEVICE_FAILED: case TRZ_GETWALLETCREATIONMASTERPUBKEY_FAILED: + case LDG_NOCONNECTEDDEVICE: + case LDG_GETWALLETCREATIONMASTERPUBKEY_FAILED: case LNWALLET_CREATEACCOUNT_FAILED: case LNWALLET_STARTDCRLND_FAILED: case LNWALLET_CONNECT_FAILED: diff --git a/app/reducers/walletLoader.js b/app/reducers/walletLoader.js index b566505ee6..304f3fee1c 100644 --- a/app/reducers/walletLoader.js +++ b/app/reducers/walletLoader.js @@ -71,7 +71,8 @@ export default function walletLoader(state = {}, action) { return { ...state, isWatchingOnly: action.isWatchingOnly, - isTrezor: action.isTrezor + isTrezor: action.isTrezor, + isLedger: action.isLedger }; case WALLET_SELECTED: return { ...state, selectedWallet: action.selectedWallet }; diff --git a/app/selectors.js b/app/selectors.js index 8a287b5591..6c1b1e0aca 100644 --- a/app/selectors.js +++ b/app/selectors.js @@ -1104,9 +1104,13 @@ export const confirmationDialogModalVisible = bool( export const isTrezor = get(["trezor", "enabled"]); export const isPerformingTrezorUpdate = get(["trezor", "performingUpdate"]); +export const isLedger = get(["ledger", "enabled"]); + export const isSignMessageDisabled = and(isWatchingOnly, not(isTrezor)); export const isChangePassPhraseDisabled = isWatchingOnly; -export const isTransactionsSendTabDisabled = not(isTrezor); +export const isTransactionsSendTabDisabled = bool( + and(not(isTrezor), not(isLedger)) +); export const politeiaURL = createSelector([isTestNet], (isTestNet) => isTestNet ? POLITEIA_URL_TESTNET : POLITEIA_URL_MAINNET @@ -1313,6 +1317,12 @@ export const trezorWalletCreationMasterPubkeyAttempt = get([ "walletCreationMasterPubkeyAttempt" ]); +export const ledgerDevice = get(["ledger", "device"]); +export const ledgerWalletCreationMasterPubkeyAttempt = get([ + "ledger", + "walletCreationMasterPubkeyAttempt" +]); + // selectors for checking if decrediton can be closed. // getRunningIndicator is a indicator for indicate something is runnning on // decrediton, like the ticket auto buyer or the mixer. @@ -1329,7 +1339,9 @@ export const loggedInDex = bool(get(["dex", "loggedIn"])); // ln selectors -export const lnEnabled = bool(and(not(isWatchingOnly), not(isTrezor))); +export const lnEnabled = bool( + and(not(isWatchingOnly), not(isTrezor), not(isLedger)) +); export const lnActive = bool(get(["ln", "active"])); export const lnStartupStage = get(["ln", "startupStage"]); export const lnStartAttempt = bool(get(["ln", "startAttempt"])); diff --git a/app/stateMachines/CreateWalletStateMachine.js b/app/stateMachines/CreateWalletStateMachine.js index 380b2423f9..cd73379106 100644 --- a/app/stateMachines/CreateWalletStateMachine.js +++ b/app/stateMachines/CreateWalletStateMachine.js @@ -13,7 +13,9 @@ export const CreateWalletMachine = Machine({ mnemonic: "", seed: "", passPhrase: "", - walletMasterPubKey: "" + walletMasterPubKey: "", + isTrezor: false, + isLedger: false }, states: { createWalletInit: { @@ -26,10 +28,6 @@ export const CreateWalletMachine = Machine({ target: "restoreWatchingOnly", cond: (c, event) => event.isWatchingOnly }, - RESTORE_TREZOR_WALLET: { - target: "restoreTrezor", - cond: (c, event) => event.isTrezor - }, RESTORE_WALLET: { target: "writeSeed", cond: (c, event) => event.isRestore @@ -124,11 +122,6 @@ export const CreateWalletMachine = Machine({ CONTINUE: "loading" } }, - restoreTrezor: { - on: { - CONTINUE: "loading" - } - }, loading: { on: { ERROR: { diff --git a/app/stateMachines/GetStartedStateMachine.js b/app/stateMachines/GetStartedStateMachine.js index 075c7c268e..0c100aae19 100644 --- a/app/stateMachines/GetStartedStateMachine.js +++ b/app/stateMachines/GetStartedStateMachine.js @@ -26,6 +26,7 @@ export const getStartedMachine = Machine({ initial: "preStart", on: { SHOW_TREZOR_CONFIG: "trezorConfig", + SHOW_LEDGER_CONFIG: "ledgerConfig", SHOW_CREATE_WALLET: "creatingWallet", SHOW_SETTING_UP_WALLET: "settingUpWallet" }, @@ -213,7 +214,8 @@ export const getStartedMachine = Machine({ !isUndefined(event.isNew) ? !event.isNew : context.isRestoreNewWallet, - isTrezor: (context, event) => event.isTrezor + isTrezor: (context, event) => event.isTrezor, + isLedger: (context, event) => event.isLedger }) }, ERROR: { @@ -319,7 +321,8 @@ export const getStartedMachine = Machine({ CreateWalletMachine.withContext({ isNew: e.isNew, walletMasterPubKey: e.walletMasterPubKey, - isTrezor: e.isTrezor + isTrezor: e.isTrezor, + isLedger: e.isLedger }) ); } catch (e) { @@ -373,6 +376,7 @@ export const getStartedMachine = Machine({ isCreateNewWallet: ctx.isCreateNewWallet, isWatchingOnly: ctx.selectedWallet.isWatchingOnly, isTrezor: ctx.selectedWallet.isTrezor, + isLedger: ctx.selectedWallet.isLedger, passPhrase: ctx.passPhrase }) ); @@ -394,6 +398,16 @@ export const getStartedMachine = Machine({ BACK: "startMachine.hist", SHOW_TREZOR_CONFIG: "trezorConfig" } + }, + ledgerConfig: { + initial: "ledgerConfig", + states: { + ledgerConfig: {} + }, + on: { + BACK: "startMachine.hist", + SHOW_LEDGER_CONFIG: "ledgerConfig" + } } } }); diff --git a/package.json b/package.json index 7a92245444..2a0b26da71 100644 --- a/package.json +++ b/package.json @@ -274,7 +274,7 @@ "@grpc/grpc-js": "1.7.3", "@hot-loader/react-dom": "16.14.0", "@ledgerhq/hw-app-btc": "^10.0.5", - "@ledgerhq/hw-transport-webusb": "^6.27.16", + "@ledgerhq/hw-transport-webusb": "6.27.1", "@peculiar/webcrypto": "1.4.3", "@xstate/react": "^0.8.1", "blake-hash": "^2.0.0", @@ -297,7 +297,7 @@ "minimist": "1.2.8", "mv": "^2.1.1", "node-polyfill-webpack-plugin": "1.1.4", - "pi-ui": "https://github.com/decred/pi-ui/", + "pi-ui": "https://github.com/decred/pi-ui#4daf214b901aa9f50547cf5645594cb76e7c9e51", "postcss": "8.4.24", "postcss-loader": "^7.0.1", "postcss-preset-env": "7.8.3", @@ -343,5 +343,8 @@ "engines": { "node": ">=14.16", "yarn": "^1.22" + }, + "resolutions": { + "@ledgerhq/hw-transport": "6.27.1" } } diff --git a/test/unit/actions/DaemonActions.spec.js b/test/unit/actions/DaemonActions.spec.js index 971a8ca48f..5b416fb893 100644 --- a/test/unit/actions/DaemonActions.spec.js +++ b/test/unit/actions/DaemonActions.spec.js @@ -883,7 +883,7 @@ test("test createWallet", async () => { selectedWallet.value.isWatchingOnly ); expect(store.getState().walletLoader.isTrezor).toBe( - selectedWallet.value.istrezor + selectedWallet.value.isTrezor ); expect(mockWalletCfgSet).toHaveBeenCalledWith( diff --git a/test/unit/components/views/GetStaredPage/CreateWallet.spec.js b/test/unit/components/views/GetStaredPage/CreateWallet.spec.js index ba88768eda..0135854aba 100644 --- a/test/unit/components/views/GetStaredPage/CreateWallet.spec.js +++ b/test/unit/components/views/GetStaredPage/CreateWallet.spec.js @@ -24,6 +24,7 @@ const testSelectedWallet = { value: { isNew: true, isTrezor: false, + isLedger: false, isWatchingOnly: false, network: "mainnet", wallet: testWalletName, diff --git a/test/unit/components/views/GetStaredPage/PreCreateWallet.spec.js b/test/unit/components/views/GetStaredPage/PreCreateWallet.spec.js index 47df248fac..3a68a3cb69 100644 --- a/test/unit/components/views/GetStaredPage/PreCreateWallet.spec.js +++ b/test/unit/components/views/GetStaredPage/PreCreateWallet.spec.js @@ -19,6 +19,7 @@ const testSelectedWallet = { value: { isNew: true, isTrezor: false, + isLedger: false, isWatchingOnly: false, network: "mainnet", wallet: testWalletName, @@ -258,6 +259,8 @@ test("test watch only control on restore wallet", async () => { await wait(() => expect(mockCreateWatchOnlyWalletRequest).toHaveBeenCalledWith( testValidMasterPubKey, + undefined, + undefined, "" ) ); @@ -286,7 +289,7 @@ test("test create trezor-backed wallet page (trezor device is connected)", async }); const testRestoreSelectedWallet = { ...testSelectedWallet, - value: { ...testSelectedWallet.value, isNew: false, isTrezor: true }, + value: { ...testSelectedWallet.value, isNew: false, isTrezor: true, isLedger: false }, isWatchingOnly: true }; @@ -338,6 +341,8 @@ test("test create trezor-backed wallet page (trezor device is connected)", async await wait(() => expect(mockCreateWatchOnlyWalletRequest).toHaveBeenCalledWith( testWalletCreationMasterPubKey, + undefined, + true, "" ) ); diff --git a/test/unit/helpers/ledger.spec.js b/test/unit/helpers/ledger.spec.js index 4a9d2ff655..0b49b6312f 100644 --- a/test/unit/helpers/ledger.spec.js +++ b/test/unit/helpers/ledger.spec.js @@ -6,9 +6,9 @@ test("test ledger address path", () => { test("test ledger pubkey to hd key", () => { // testnet - expect(fixPubKeyChecksum("tpubVpeRVBDM14ydoJ7jCzTayDHiP6CU4NcbKEW6bWQGSghmi14BfNx3omc6GjV3AxbtkTYcPAedw48XywVgSyY8H9ef73zWWtoZ6MCMLLJiUyq")).toStrictEqual("tpubVpeRVBDM14ydoJ7jCzTayDHiP6CU4NcbKEW6bWQGSghmi14BfNx3omc6GjV3AxbtkTYcPAedw48XywVgSyY8H9ef73zWWtoZ6MCMLGM6GV3"); + expect(fixPubKeyChecksum("tpubVpeRVBDM14ydoJ7jCzTayDHiP6CU4NcbKEW6bWQGSghmi14BfNx3omc6GjV3AxbtkTYcPAedw48XywVgSyY8H9ef73zWWtoZ6MCMLLJiUyq", true)).toStrictEqual("tpubVpeRVBDM14ydoJ7jCzTayDHiP6CU4NcbKEW6bWQGSghmi14BfNx3omc6GjV3AxbtkTYcPAedw48XywVgSyY8H9ef73zWWtoZ6MCMLGM6GV3"); // mainet - expect(fixPubKeyChecksum("dpubZFs9f5ex4qxoiqCHVEnotAaYpZhCgwDAndMyLicfcXJysBmNxMyx7X3QHW2NiUN14KnekWcMQt4XwyF5wAqDPzbLcv59mQTRhL6foyvvqLK")).toStrictEqual("dpubZFs9f5ex4qxoiqCHVEnotAaYpZhCgwDAndMyLicfcXJysBmNxMyx7X3QHW2NiUN14KnekWcMQt4XwyF5wAqDPzbLcv59mQTRhL6fp1csmnS"); + expect(fixPubKeyChecksum("dpubZFs9f5ex4qxoiqCHVEnotAaYpZhCgwDAndMyLicfcXJysBmNxMyx7X3QHW2NiUN14KnekWcMQt4XwyF5wAqDPzbLcv59mQTRhL6foyvvqLK", false)).toStrictEqual("dpubZFs9f5ex4qxoiqCHVEnotAaYpZhCgwDAndMyLicfcXJysBmNxMyx7X3QHW2NiUN14KnekWcMQt4XwyF5wAqDPzbLcv59mQTRhL6fp1csmnS"); }); function dispatchFn (inputs) { diff --git a/yarn.lock b/yarn.lock index d6edb6d758..dfd57ecc84 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1795,17 +1795,17 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@ledgerhq/devices@^8.0.4": - version "8.0.4" - resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-8.0.4.tgz#ebc7779adbbec2d046424603a481623eb3fbe306" - integrity sha512-dxOiWZmtEv1tgw70+rW8gviCRZUeGDUnxY6HUPiRqTAc0Ts2AXxiJChgAsPvIywWTGW+S67Nxq1oTZdpRbdt+A== +"@ledgerhq/devices@^6.27.1": + version "6.27.1" + resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-6.27.1.tgz#3b13ab1d1ba8201e9e74a08f390560483978c962" + integrity sha512-jX++oy89jtv7Dp2X6gwt3MMkoajel80JFWcdc0HCouwDsV1mVJ3SQdwl/bQU0zd8HI6KebvUP95QTwbQLLK/RQ== dependencies: - "@ledgerhq/errors" "^6.12.7" - "@ledgerhq/logs" "^6.10.1" + "@ledgerhq/errors" "^6.10.0" + "@ledgerhq/logs" "^6.10.0" rxjs "6" semver "^7.3.5" -"@ledgerhq/errors@^6.12.7": +"@ledgerhq/errors@^6.10.0": version "6.12.7" resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-6.12.7.tgz#c7b630488d5713bc7b1e1682d6ab5d08918c69f1" integrity sha512-1BpjzFErPK7qPFx0oItcX0mNLJMplVAm2Dpl5urZlubewnTyyw5sahIBjU+8LLCWJ2eGEh/0wyvh0jMtR0n2Mg== @@ -1828,26 +1828,26 @@ tiny-secp256k1 "1.1.6" varuint-bitcoin "1.1.2" -"@ledgerhq/hw-transport-webusb@^6.27.16": - version "6.27.16" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-webusb/-/hw-transport-webusb-6.27.16.tgz#b8e20e772f78c312fc7f2ce3a469c99ecf59dc67" - integrity sha512-A3S2p5Rh9Ot402pWNZw8v5EpO3wOHP8ch/Dcz0AjInmwNouQ9nIYd1+eLSL7QiyG9X7+tuHxFF1IjrEgvAzQuQ== +"@ledgerhq/hw-transport-webusb@6.27.1": + version "6.27.1" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-webusb/-/hw-transport-webusb-6.27.1.tgz#62be3b2e5f0d54ae06d066571e4408a807d0f01f" + integrity sha512-n0ygJSeRpznrUfwtbDCLQOM5mA23YT/ngYY8HU46dzsVJHrHQ4jwBNJU48iKB+a9GhHyPAUpPNlWGTogvoVUxg== dependencies: - "@ledgerhq/devices" "^8.0.4" - "@ledgerhq/errors" "^6.12.7" - "@ledgerhq/hw-transport" "^6.28.5" - "@ledgerhq/logs" "^6.10.1" + "@ledgerhq/devices" "^6.27.1" + "@ledgerhq/errors" "^6.10.0" + "@ledgerhq/hw-transport" "^6.27.1" + "@ledgerhq/logs" "^6.10.0" -"@ledgerhq/hw-transport@^6.28.5": - version "6.28.5" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-6.28.5.tgz#675193be2f695a596068145351da598316c25831" - integrity sha512-xmw5RhYbqExBBqTvOnOjN/RYNIGMBxFJ+zcYNfkfw/E+uEY3L7xq8Z7sC/n7URTT6xtEctElqduBJnBQE4OQtw== +"@ledgerhq/hw-transport@6.27.1", "@ledgerhq/hw-transport@^6.27.1", "@ledgerhq/hw-transport@^6.28.5": + version "6.27.1" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-6.27.1.tgz#88072278f69c279cb6569352acd4ae2fec33ace3" + integrity sha512-hnE4/Fq1YzQI4PA1W0H8tCkI99R3UWDb3pJeZd6/Xs4Qw/q1uiQO+vNLC6KIPPhK0IajUfuI/P2jk0qWcMsuAQ== dependencies: - "@ledgerhq/devices" "^8.0.4" - "@ledgerhq/errors" "^6.12.7" + "@ledgerhq/devices" "^6.27.1" + "@ledgerhq/errors" "^6.10.0" events "^3.3.0" -"@ledgerhq/logs@^6.10.1": +"@ledgerhq/logs@^6.10.0", "@ledgerhq/logs@^6.10.1": version "6.10.1" resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-6.10.1.tgz#5bd16082261d7364eabb511c788f00937dac588d" integrity sha512-z+ILK8Q3y+nfUl43ctCPuR4Y2bIxk/ooCQFwZxhtci1EhAtMDzMAx2W25qx8G1PPL9UUOdnUax19+F0OjXoj4w== @@ -10405,9 +10405,9 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== -"pi-ui@https://github.com/decred/pi-ui/": +"pi-ui@https://github.com/decred/pi-ui#4daf214b901aa9f50547cf5645594cb76e7c9e51": version "1.1.0" - resolved "https://github.com/decred/pi-ui/#d7ca43e8bc65a45bbebd93b0101e58f3a6a170ab" + resolved "https://github.com/decred/pi-ui#4daf214b901aa9f50547cf5645594cb76e7c9e51" dependencies: clamp-js-main "^0.11.5" lodash "^4.17.15"