Skip to content

Commit

Permalink
Merge branch 'master' into react-v18
Browse files Browse the repository at this point in the history
  • Loading branch information
alexlyp committed Sep 26, 2023
2 parents 416bdd3 + 360ce75 commit ad00a63
Show file tree
Hide file tree
Showing 145 changed files with 2,248 additions and 203 deletions.
1 change: 0 additions & 1 deletion .yarncahce/v6/.tmp/5d8efc829b465332f7e452d93a85c4bc
Submodule 5d8efc829b465332f7e452d93a85c4bc deleted from 6a024e
2 changes: 1 addition & 1 deletion app/actions/ClientActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export const getWalletServiceAttempt = () => (dispatch, getState) => {
} = getState();
dispatch({ type: GETWALLETSERVICE_ATTEMPT });
const grpcCertAndKey = wallet.getDcrwalletGrpcKeyCert();
wallet
return wallet
.getWalletService(
sel.isTestNet(getState()),
walletName,
Expand Down
7 changes: 5 additions & 2 deletions app/actions/DaemonActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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"
// }
// }
Expand All @@ -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));
Expand Down Expand Up @@ -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;
Expand Down
212 changes: 212 additions & 0 deletions app/actions/LedgerActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import TransportWebUSB from "@ledgerhq/hw-transport-webusb";
import { createTransaction } from "@ledgerhq/hw-app-btc/lib/createTransaction";
import Btc from "@ledgerhq/hw-app-btc";
import * as ledgerHelpers from "../helpers/ledger";
import { wallet } from "wallet-preload-shim";
import { publishTransactionAttempt } from "./ControlActions";
import { hexToBytes } from "helpers";
import {
SIGNTX_ATTEMPT,
SIGNTX_FAILED,
SIGNTX_SUCCESS
} from "./ControlActions";

const coin = "decred";

// This error will happen when ledger is on the home screen, but it is called
// "UNKNOWN_ERROR" so may be something else.
const wrongScreenStatus = 25873;
// This error indicates the wrong app, like the btc app, is open and will say
// CLA_NOT_SUPPORTED
const wrongAppStatus = 28160;

import * as selectors from "selectors";

export const LDG_LEDGER_ENABLED = "LDG_LEDGER_ENABLED";
export const LDG_WALLET_CLOSED = "LDG_WALLET_CLOSED";

// This is an error's message when an app is open but we are trying to get
// device info.
// const DEVICE_ON_DASHBOARD_EXPECTED = "DeviceOnDashboardExpected";

// enableLedger only sets a value in the config. Ledger connections are made
// per action then dropped.
export const enableLedger = () => (dispatch, getState) => {
dispatch({ type: LDG_LEDGER_ENABLED });
connect()(dispatch, getState);
};

export const LDG_CONNECT_ATTEMPT = "LDG_CONNECT_ATTEMPT";
export const LDG_CONNECT_FAILED = "LDG_CONNECT_FAILED";
export const LDG_CONNECT_SUCCESS = "LDG_CONNECT_SUCCESS";

// connect only checks that a connection does not error, so a device exists and
// is plugged in.
export const connect = () => async (dispatch /*, getState*/) => {
dispatch({ type: LDG_CONNECT_ATTEMPT });
try {
await doWithTransport(async () => {});
} catch (error) {
dispatch({ type: LDG_CONNECT_FAILED });
throw error;
}
dispatch({ type: LDG_CONNECT_SUCCESS });
};

export const LDG_LEDGER_DISABLED = "LDG_LEDGER_DISABLED";

// disableLedger disables ledger integration for the current wallet. Note
// that it does **not** disable in the config, so the wallet will restart as a
// ledger wallet next time it's opened.
export const disableLedger = () => (dispatch) => {
dispatch({ type: LDG_LEDGER_DISABLED });
};

export const LDG_NOCONNECTEDDEVICE = "LDG_NOCONNECTEDDEVICE";

export const alertNoConnectedDevice = () => (dispatch) => {
dispatch({ type: LDG_NOCONNECTEDDEVICE });
};

// checkLedgerIsDcrwallet verifies whether the wallet currently running on
// dcrwallet (presumably a watch only wallet created from a ledger provided
// xpub) is the same wallet as the one of the currently connected ledger. This
// function throws an error if they are not the same.
// This is useful for making sure, prior to performing some wallet related
// function such as transaction signing, that ledger will correctly perform the
// operation.
// Note that this might trigger pin/passphrase modals, depending on the current
// ledger configuration.
// The way the check is performed is by generating the first address from the
// ledger wallet and then validating this address agains dcrwallet, ensuring
// this is an owned address at the appropriate branch/index.
// This check is only valid for a single session (ie, a single execution of
// `deviceRun`) as the physical device might change between sessions.
const checkLedgerIsDcrwallet = () => async (dispatch, getState) => {
const {
grpc: { walletService }
} = getState();

const path = ledgerHelpers.addressPath(0, 0);
const payload = await getAddress(path);
const addr = payload.bitcoinAddress;

const addrValidResp = await wallet.validateAddress(walletService, addr);
if (!addrValidResp.isValid)
throw "Ledger provided an invalid address " + addr;

if (!addrValidResp.isMine)
throw "Ledger and dcrwallet not running from the same extended public key";

if (addrValidResp.index !== 0) throw "Wallet replied with wrong index.";
};

export const signTransactionAttemptLedger =
(rawUnsigTx) => async (dispatch, getState) => {
dispatch({ type: SIGNTX_ATTEMPT });
const {
grpc: { walletService }
} = getState();
const chainParams = selectors.chainParams(getState());

try {
const arg = await ledgerHelpers.signArg(
rawUnsigTx,
chainParams,
walletService,
dispatch
);

await dispatch(checkLedgerIsDcrwallet());
const signedRaw = await createTx(arg);
if (signedRaw.message) {
throw signedRaw.message;
}

dispatch({ type: SIGNTX_SUCCESS });
dispatch(publishTransactionAttempt(hexToBytes(signedRaw)));
} catch (error) {
dispatch({ error, type: SIGNTX_FAILED });
}
};

export const LDG_GETWALLETCREATIONMASTERPUBKEY_ATTEMPT =
"LDG_GETWALLETCREATIONMASTERPUBKEY_ATTEMPT";
export const LDG_GETWALLETCREATIONMASTERPUBKEY_FAILED =
"LDG_GETWALLETCREATIONMASTERPUBKEY_FAILED";
export const LDG_GETWALLETCREATIONMASTERPUBKEY_SUCCESS =
"LDG_GETWALLETCREATIONMASTERPUBKEY_SUCCESS";

export const getWalletCreationMasterPubKey =
() => async (dispatch, getState) => {
dispatch({ type: LDG_GETWALLETCREATIONMASTERPUBKEY_ATTEMPT });
const isTestnet = selectors.isTestNet(getState());
try {
const payload = await getPubKey(isTestnet);
const hdpk = ledgerHelpers.fixPubKeyChecksum(payload, isTestnet);
dispatch({ type: LDG_GETWALLETCREATIONMASTERPUBKEY_SUCCESS });
return hdpk;
} catch (error) {
dispatch({ error, type: LDG_GETWALLETCREATIONMASTERPUBKEY_FAILED });
throw error;
}
};

function doWithTransport(fn) {
return TransportWebUSB.create()
.then((transport) => {
return fn(transport).then((r) =>
transport
.close()
.catch((e) => {
throw e;
})
.then(() => r)
);
})
.catch((e) => {
const notDecred = "the decred ledger app is not open on the device";
if (e.statusCode && e.message) {
switch (e.statusCode) {
case wrongScreenStatus:
e.message = notDecred;
throw e;
case wrongAppStatus:
e.message = notDecred;
throw e;
}
}
throw e;
});
}

function getAddress(path) {
const fn = async (transport) => {
const btc = new Btc({ transport, currency: coin });
return await btc.getWalletPublicKey(path, {
verify: false
});
};
return doWithTransport(fn);
}

function getPubKey(isTestnet) {
const fn = async (transport) => {
const btc = new Btc({ transport, currency: coin });
let hdPublicKeyID = 0x02fda926; // dpub
if (isTestnet) {
hdPublicKeyID = 0x043587d1; // tpub
}
return await btc.getWalletXpub({
path: "44'/42'/0'",
xpubVersion: hdPublicKeyID
});
};
return doWithTransport(fn);
}

function createTx(arg) {
return doWithTransport((transport) => {
return createTransaction(transport, arg);
});
}
11 changes: 0 additions & 11 deletions app/actions/TrezorActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
12 changes: 10 additions & 2 deletions app/actions/WalletLoaderActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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;
Expand Down Expand Up @@ -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 });
Expand All @@ -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 });
Expand Down Expand Up @@ -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 });
Expand Down
2 changes: 2 additions & 0 deletions app/components/SideBar/MenuLinks/Links.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
{
Expand Down Expand Up @@ -48,6 +49,7 @@ export const linkList = [
path: "/privacy",
link: <T id="sidebar.link.privacy" m="Privacy and Security" />,
icon: "securitycntr",
key: PRIV_KEY,
ariaLabel: "Privacy"
},
{
Expand Down
17 changes: 14 additions & 3 deletions app/components/SideBar/MenuLinks/hooks.js
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -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(
Expand Down Expand Up @@ -47,19 +55,22 @@ 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(
(acc, np) => acc + (notifProps[np] || 0),
0
)
}));
}, [notifProps, isTrezor, lnEnabled]);
}, [notifProps, isTrezor, lnEnabled, isLedger]);

const [activeTabIndex, setActiveTabIndex] = useState(-1);
const history = useHistory();
Expand Down
Loading

0 comments on commit ad00a63

Please sign in to comment.