Electron Ledger + Trezor Support (#1836)
* Initial scaffold of enclave * Cleanup types * Add comments * Do not truncate errors, pretty output * Introduce helpers for sagas * Update yarn lock * Convert enclave into its own lib. Implement client and server. * Check in progress * Initial types * Remove unused lib * Finish types * cleanup * Switch over to using electron protocol, remove code thats no longer necessary * Refactor Ledger and Trezor wallets to provide all functionality via libs. Run chain code generation thru Enclave. * Check in trezor work * Transaction signing * Message signing * Display address * Fix deallocation of trezor * Adjust API * Remove unused getAddresses * Fix imports and filenames to cooperate with internal typings * Fix type uncertainty * Add persistent message to Ledger unlock. * Update ledger help link to kb * Convert ledger over to updated libs * Fix jest config * Enclave README * Unnecessary assertion * Adjust tip * Type ledger errors * Reduce enclave client code. * No default exports * l18n user facing enclave errors * Reduce repeated enclave code by splitting it into its own wallet lib. Fix some types * tslint * Reduce repeated enclave code by splitting it into its own wallet lib. Fix some types and error messages. * Electron TREZOR Support (#1946) * Type trezor connect. * Check in trezor code * Implement TREZOR wallet * Convert TREZOR to use enclave class like Ledger. * Switch to mycrypto fork of trezor lib. Remove unused dependencies. * remove unnecessary window attachment * tslint
This commit is contained in:
parent
41f8ab8966
commit
cb92f59e57
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import { toChecksumAddress } from 'ethereumjs-util';
|
||||
import { UnitDisplay, NewTabLink } from 'components/ui';
|
||||
import { IWallet, TrezorWallet, LedgerWallet, Balance } from 'libs/wallet';
|
||||
import { IWallet, HardwareWallet, Balance } from 'libs/wallet';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import Spinner from 'components/ui/Spinner';
|
||||
import { getNetworkConfig, getOffline } from 'selectors/config';
|
||||
|
@ -73,7 +73,7 @@ class AccountInfo extends React.Component<Props, State> {
|
|||
};
|
||||
|
||||
public render() {
|
||||
const { network, isOffline, balance } = this.props;
|
||||
const { network, isOffline, balance, wallet } = this.props;
|
||||
const { address, showLongBalance, confirmAddr } = this.state;
|
||||
|
||||
let blockExplorer;
|
||||
|
@ -84,12 +84,11 @@ class AccountInfo extends React.Component<Props, State> {
|
|||
tokenExplorer = network.tokenExplorer;
|
||||
}
|
||||
|
||||
const wallet = this.props.wallet as LedgerWallet | TrezorWallet;
|
||||
return (
|
||||
<div>
|
||||
<AccountAddress address={toChecksumAddress(address)} />
|
||||
|
||||
{typeof wallet.displayAddress === 'function' && (
|
||||
{isHardwareWallet(wallet) && (
|
||||
<div className="AccountInfo-section">
|
||||
<a
|
||||
className="AccountInfo-address-hw-addr"
|
||||
|
@ -98,9 +97,9 @@ class AccountInfo extends React.Component<Props, State> {
|
|||
wallet
|
||||
.displayAddress()
|
||||
.then(() => this.toggleConfirmAddr())
|
||||
.catch(e => {
|
||||
.catch((e: Error | string) => {
|
||||
console.error('Display address failed', e);
|
||||
this.toggleConfirmAddr();
|
||||
throw new Error(e);
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
@ -192,6 +191,10 @@ class AccountInfo extends React.Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
function isHardwareWallet(wallet: IWallet): wallet is HardwareWallet {
|
||||
return typeof (wallet as any).displayAddress === 'function';
|
||||
}
|
||||
|
||||
function mapStateToProps(state: AppState): StateProps {
|
||||
return {
|
||||
balance: state.wallet.balance,
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
@import 'common/sass/variables';
|
||||
|
||||
.LedgerDecrypt {
|
||||
text-align: center;
|
||||
|
||||
&-tip {
|
||||
font-size: $font-size-xs-bump;
|
||||
opacity: 0.7;
|
||||
margin-bottom: $space;
|
||||
}
|
||||
|
||||
&-help {
|
||||
margin-top: 10px;
|
||||
font-size: 13px;
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import ledger from 'ledgerco';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import DeterministicWalletsModal from './DeterministicWalletsModal';
|
||||
import UnsupportedNetwork from './UnsupportedNetwork';
|
||||
import { LedgerWallet } from 'libs/wallet';
|
||||
import { Spinner, NewTabLink } from 'components/ui';
|
||||
import { Spinner, NewTabLink, HelpLink } from 'components/ui';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
import { SecureWalletName, ledgerReferralURL } from 'config';
|
||||
import { SecureWalletName, ledgerReferralURL, HELP_ARTICLE } from 'config';
|
||||
import { getPaths, getSingleDPath } from 'selectors/config/wallet';
|
||||
import { getNetworkConfig } from 'selectors/config';
|
||||
import { NetworkConfig } from 'types/network';
|
||||
|
@ -29,7 +28,6 @@ interface State {
|
|||
dPath: DPath;
|
||||
error: string | null;
|
||||
isLoading: boolean;
|
||||
showTip: boolean;
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps;
|
||||
|
@ -40,14 +38,7 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
|
|||
chainCode: '',
|
||||
dPath: this.props.dPath || this.props.dPaths[0],
|
||||
error: null,
|
||||
isLoading: false,
|
||||
showTip: false
|
||||
};
|
||||
|
||||
public showTip = () => {
|
||||
this.setState({
|
||||
showTip: true
|
||||
});
|
||||
isLoading: false
|
||||
};
|
||||
|
||||
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
||||
|
@ -58,14 +49,14 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
|
|||
|
||||
public render() {
|
||||
const { network } = this.props;
|
||||
const { dPath, publicKey, chainCode, error, isLoading, showTip } = this.state;
|
||||
const { dPath, publicKey, chainCode, error, isLoading } = this.state;
|
||||
const showErr = error ? 'is-showing' : '';
|
||||
|
||||
if (!dPath) {
|
||||
return <UnsupportedNetwork walletType={translateRaw('x_Ledger')} />;
|
||||
}
|
||||
|
||||
if (window.location.protocol !== 'https:') {
|
||||
if (!process.env.BUILD_ELECTRON && window.location.protocol !== 'https:') {
|
||||
return (
|
||||
<div className="LedgerDecrypt">
|
||||
<div className="alert alert-danger">
|
||||
|
@ -78,6 +69,15 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
|
|||
|
||||
return (
|
||||
<div className="LedgerDecrypt">
|
||||
<div className="LedgerDecrypt-tip">
|
||||
{translate('LEDGER_TIP', {
|
||||
$network: network.unit,
|
||||
$browserSupportState: process.env.BUILD_ELECTRON
|
||||
? translateRaw('DISABLED')
|
||||
: translateRaw('ENABLED')
|
||||
})}
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="LedgerDecrypt-decrypt btn btn-primary btn-lg btn-block"
|
||||
onClick={this.handleNullConnect}
|
||||
|
@ -99,14 +99,10 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
|
|||
|
||||
<div className={`LedgerDecrypt-error alert alert-danger ${showErr}`}>{error || '-'}</div>
|
||||
|
||||
{showTip && (
|
||||
<p className="LedgerDecrypt-tip">{translate('LEDGER_TIP', { $network: network.unit })}</p>
|
||||
)}
|
||||
|
||||
<div className="LedgerDecrypt-help">
|
||||
<NewTabLink href="https://support.ledgerwallet.com/hc/en-us/articles/115005200009">
|
||||
<HelpLink article={HELP_ARTICLE.HOW_TO_USE_LEDGER}>
|
||||
{translate('HELP_ARTICLE_1')}
|
||||
</NewTabLink>
|
||||
</HelpLink>
|
||||
</div>
|
||||
|
||||
<DeterministicWalletsModal
|
||||
|
@ -133,51 +129,23 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
|
|||
private handleConnect = (dPath: DPath) => {
|
||||
this.setState({
|
||||
isLoading: true,
|
||||
error: null,
|
||||
showTip: false
|
||||
error: null
|
||||
});
|
||||
|
||||
ledger.comm_u2f.create_async().then((comm: any) => {
|
||||
new ledger.eth(comm)
|
||||
.getAddress_async(dPath.value, false, true)
|
||||
.then(res => {
|
||||
this.setState({
|
||||
publicKey: res.publicKey,
|
||||
chainCode: res.chainCode,
|
||||
isLoading: false
|
||||
});
|
||||
})
|
||||
.catch((err: any) => {
|
||||
let showTip;
|
||||
let errMsg;
|
||||
// Timeout
|
||||
if (err && err.metaData && err.metaData.code === 5) {
|
||||
showTip = true;
|
||||
errMsg = translateRaw('LEDGER_TIMEOUT');
|
||||
}
|
||||
// Wrong app logged into
|
||||
if (err && err.includes && err.includes('6804')) {
|
||||
showTip = true;
|
||||
errMsg = translateRaw('LEDGER_WRONG_APP');
|
||||
}
|
||||
// Ledger locked
|
||||
if (err && err.includes && err.includes('6801')) {
|
||||
errMsg = translateRaw('LEDGER_LOCKED');
|
||||
}
|
||||
// Other
|
||||
if (!errMsg) {
|
||||
errMsg = err && err.metaData ? err.metaData.type : err.toString();
|
||||
}
|
||||
|
||||
this.setState({
|
||||
error: errMsg,
|
||||
isLoading: false
|
||||
});
|
||||
if (showTip) {
|
||||
this.showTip();
|
||||
}
|
||||
LedgerWallet.getChainCode(dPath.value)
|
||||
.then(res => {
|
||||
this.setState({
|
||||
publicKey: res.publicKey,
|
||||
chainCode: res.chainCode,
|
||||
isLoading: false
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({
|
||||
error: translateRaw(err.message),
|
||||
isLoading: false
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
private handleCancel = () => {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { TrezorWallet, TREZOR_MINIMUM_FIRMWARE } from 'libs/wallet';
|
||||
import { TrezorWallet } from 'libs/wallet';
|
||||
import React, { PureComponent } from 'react';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import TrezorConnect from 'vendor/trezor-connect';
|
||||
import DeterministicWalletsModal from './DeterministicWalletsModal';
|
||||
import UnsupportedNetwork from './UnsupportedNetwork';
|
||||
import { Spinner, NewTabLink } from 'components/ui';
|
||||
|
@ -109,25 +108,21 @@ class TrezorDecryptClass extends PureComponent<Props, State> {
|
|||
error: null
|
||||
});
|
||||
|
||||
(TrezorConnect as any).getXPubKey(
|
||||
dPath.value,
|
||||
(res: any) => {
|
||||
if (res.success) {
|
||||
this.setState({
|
||||
dPath,
|
||||
publicKey: res.publicKey,
|
||||
chainCode: res.chainCode,
|
||||
isLoading: false
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
error: res.error,
|
||||
isLoading: false
|
||||
});
|
||||
}
|
||||
},
|
||||
TREZOR_MINIMUM_FIRMWARE
|
||||
);
|
||||
TrezorWallet.getChainCode(dPath.value)
|
||||
.then(res => {
|
||||
this.setState({
|
||||
dPath,
|
||||
publicKey: res.publicKey,
|
||||
chainCode: res.chainCode,
|
||||
isLoading: false
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({
|
||||
error: err.message,
|
||||
isLoading: false
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
private handleCancel = () => {
|
||||
|
|
|
@ -9,6 +9,7 @@ export enum HELP_ARTICLE {
|
|||
ADDING_NEW_TOKENS = 'tokens/adding-new-token-and-sending-custom-tokens.html',
|
||||
HARDWARE_WALLET_RECOMMENDATIONS = 'hardware-wallets/hardware-wallet-recommendations.html',
|
||||
SENDING_TO_TREZOR = 'hardware-wallets/trezor-sending-to-trezor-device.html',
|
||||
HOW_TO_USE_LEDGER = 'accessing-your-wallet/how-to-use-your-ledger-with-mycrypto.html',
|
||||
SECURING_YOUR_ETH = 'security/securing-your-ethereum.html',
|
||||
PROTECT_YOUR_FUNDS = 'getting-started/protecting-yourself-and-your-funds.html',
|
||||
WHAT_IS_NONCE = 'transactions/what-is-nonce.html',
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
import EthTx from 'ethereumjs-tx';
|
||||
import EnclaveAPI, { WalletTypes } from 'shared/enclave/client';
|
||||
import { getTransactionFields } from 'libs/transaction';
|
||||
import { HardwareWallet, ChainCodeResponse } from './hardware';
|
||||
import { IFullWallet } from '../IWallet';
|
||||
import { translateRaw } from 'translations';
|
||||
|
||||
const walletTypeNames = {
|
||||
[WalletTypes.LEDGER]: 'X_LEDGER',
|
||||
[WalletTypes.TREZOR]: 'X_TREZOR',
|
||||
[WalletTypes.KEEPKEY]: 'X_KEEPKEY'
|
||||
};
|
||||
|
||||
export function makeEnclaveWallet(walletType: WalletTypes) {
|
||||
class EnclaveWallet extends HardwareWallet implements IFullWallet {
|
||||
public static async getChainCode(dpath: string): Promise<ChainCodeResponse> {
|
||||
return EnclaveAPI.getChainCode({
|
||||
walletType,
|
||||
dpath
|
||||
});
|
||||
}
|
||||
|
||||
constructor(address: string, dPath: string, index: number) {
|
||||
super(address, dPath, index);
|
||||
}
|
||||
|
||||
public async signRawTransaction(t: EthTx): Promise<Buffer> {
|
||||
const txFields = getTransactionFields(t);
|
||||
const res = await EnclaveAPI.signTransaction({
|
||||
walletType,
|
||||
transaction: txFields,
|
||||
path: this.getPath()
|
||||
});
|
||||
return new EthTx(res.signedTransaction).serialize();
|
||||
}
|
||||
|
||||
public async signMessage(msg: string): Promise<string> {
|
||||
if (!msg) {
|
||||
throw Error('No message to sign');
|
||||
}
|
||||
|
||||
const res = await EnclaveAPI.signMessage({
|
||||
walletType,
|
||||
message: msg,
|
||||
path: this.getPath()
|
||||
});
|
||||
return res.signedMessage;
|
||||
}
|
||||
|
||||
public async displayAddress() {
|
||||
const path = `${this.dPath}/${this.index}`;
|
||||
return EnclaveAPI.displayAddress({
|
||||
walletType,
|
||||
path
|
||||
})
|
||||
.then(res => res.success)
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
public getWalletType(): string {
|
||||
return translateRaw(walletTypeNames[walletType]);
|
||||
}
|
||||
}
|
||||
|
||||
return EnclaveWallet;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import EthTx from 'ethereumjs-tx';
|
||||
import { DeterministicWallet } from './deterministic';
|
||||
import { IFullWallet } from '../IWallet';
|
||||
|
||||
export interface ChainCodeResponse {
|
||||
chainCode: string;
|
||||
publicKey: string;
|
||||
}
|
||||
|
||||
export abstract class HardwareWallet extends DeterministicWallet implements IFullWallet {
|
||||
// Static functions can't be abstract, so implement an errorous one
|
||||
// @ts-ignore
|
||||
public static getChainCode(dpath: string): Promise<ChainCodeResponse> {
|
||||
throw new Error(`getChainCode is not implemented in ${this.constructor.name}`);
|
||||
}
|
||||
|
||||
public abstract signRawTransaction(t: EthTx): Promise<Buffer>;
|
||||
public abstract signMessage(msg: string): Promise<string>;
|
||||
public abstract displayAddress(): Promise<boolean>;
|
||||
public abstract getWalletType(): string;
|
||||
}
|
|
@ -1,3 +1,13 @@
|
|||
export * from './ledger';
|
||||
import { makeEnclaveWallet } from './enclave';
|
||||
import { WalletTypes } from 'shared/enclave/client';
|
||||
import { LedgerWallet as LedgerWalletWeb } from './ledger';
|
||||
import { TrezorWallet as TrezorWalletWeb } from './trezor';
|
||||
|
||||
function enclaveOrWallet<T>(type: WalletTypes, lib: T) {
|
||||
return process.env.BUILD_ELECTRON ? makeEnclaveWallet(type) : lib;
|
||||
}
|
||||
|
||||
export * from './mnemonic';
|
||||
export * from './trezor';
|
||||
export * from './hardware';
|
||||
export const LedgerWallet = enclaveOrWallet(WalletTypes.LEDGER, LedgerWalletWeb);
|
||||
export const TrezorWallet = enclaveOrWallet(WalletTypes.TREZOR, TrezorWalletWeb);
|
||||
|
|
|
@ -1,80 +1,128 @@
|
|||
import ledger from 'ledgerco';
|
||||
import TransportU2F from '@ledgerhq/hw-transport-u2f';
|
||||
import LedgerEth from '@ledgerhq/hw-app-eth';
|
||||
import EthTx, { TxObj } from 'ethereumjs-tx';
|
||||
import { addHexPrefix, toBuffer } from 'ethereumjs-util';
|
||||
import { DeterministicWallet } from './deterministic';
|
||||
import { HardwareWallet, ChainCodeResponse } from './hardware';
|
||||
import { getTransactionFields } from 'libs/transaction';
|
||||
import { IFullWallet } from '../IWallet';
|
||||
import { translateRaw } from 'translations';
|
||||
|
||||
export class LedgerWallet extends DeterministicWallet implements IFullWallet {
|
||||
private ethApp: ledger.eth;
|
||||
// Ledger throws a few types of errors
|
||||
interface U2FError {
|
||||
metaData: {
|
||||
type: string;
|
||||
code: number;
|
||||
};
|
||||
}
|
||||
|
||||
type LedgerError = U2FError | Error | string;
|
||||
|
||||
export class LedgerWallet extends HardwareWallet {
|
||||
public static async getChainCode(dpath: string): Promise<ChainCodeResponse> {
|
||||
return makeApp()
|
||||
.then(app => app.getAddress(dpath, false, true))
|
||||
.then(res => {
|
||||
return {
|
||||
publicKey: res.publicKey,
|
||||
chainCode: res.chainCode
|
||||
};
|
||||
})
|
||||
.catch((err: LedgerError) => {
|
||||
throw new Error(ledgerErrToMessage(err));
|
||||
});
|
||||
}
|
||||
|
||||
constructor(address: string, dPath: string, index: number) {
|
||||
super(address, dPath, index);
|
||||
ledger.comm_u2f.create_async().then((comm: any) => {
|
||||
this.ethApp = new ledger.eth(comm);
|
||||
});
|
||||
}
|
||||
|
||||
// modeled after
|
||||
// https://github.com/kvhnuke/etherwallet/blob/3f7ff809e5d02d7ea47db559adaca1c930025e24/app/scripts/uiFuncs.js#L58
|
||||
public signRawTransaction(t: EthTx): Promise<Buffer> {
|
||||
public async signRawTransaction(t: EthTx): Promise<Buffer> {
|
||||
const txFields = getTransactionFields(t);
|
||||
t.v = Buffer.from([t._chainId]);
|
||||
t.r = toBuffer(0);
|
||||
t.s = toBuffer(0);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.ethApp
|
||||
.signTransaction_async(this.getPath(), t.serialize().toString('hex'))
|
||||
.then(result => {
|
||||
const strTx = getTransactionFields(t);
|
||||
const txToSerialize: TxObj = {
|
||||
...strTx,
|
||||
v: addHexPrefix(result.v),
|
||||
r: addHexPrefix(result.r),
|
||||
s: addHexPrefix(result.s)
|
||||
};
|
||||
try {
|
||||
const ethApp = await makeApp();
|
||||
const result = await ethApp.signTransaction(this.getPath(), t.serialize().toString('hex'));
|
||||
|
||||
const serializedTx = new EthTx(txToSerialize).serialize();
|
||||
resolve(serializedTx);
|
||||
})
|
||||
.catch(err => {
|
||||
return reject(Error(err + '. Check to make sure contract data is on'));
|
||||
});
|
||||
});
|
||||
const txToSerialize: TxObj = {
|
||||
...txFields,
|
||||
v: addHexPrefix(result.v),
|
||||
r: addHexPrefix(result.r),
|
||||
s: addHexPrefix(result.s)
|
||||
};
|
||||
|
||||
return new EthTx(txToSerialize).serialize();
|
||||
} catch (err) {
|
||||
throw Error(err + '. Check to make sure contract data is on');
|
||||
}
|
||||
}
|
||||
|
||||
// modeled after
|
||||
// https://github.com/kvhnuke/etherwallet/blob/3f7ff809e5d02d7ea47db559adaca1c930025e24/app/scripts/controllers/signMsgCtrl.js#L53
|
||||
public async signMessage(msg: string): Promise<string> {
|
||||
if (!msg) {
|
||||
throw Error('No message to sign');
|
||||
}
|
||||
const msgHex = Buffer.from(msg).toString('hex');
|
||||
|
||||
const signed = await this.ethApp.signPersonalMessage_async(this.getPath(), msgHex);
|
||||
const combined = addHexPrefix(signed.r + signed.s + signed.v.toString(16));
|
||||
return combined;
|
||||
try {
|
||||
const msgHex = Buffer.from(msg).toString('hex');
|
||||
const ethApp = await makeApp();
|
||||
const signed = await ethApp.signPersonalMessage(this.getPath(), msgHex);
|
||||
const combined = addHexPrefix(signed.r + signed.s + signed.v.toString(16));
|
||||
return combined;
|
||||
} catch (err) {
|
||||
throw new Error(ledgerErrToMessage(err));
|
||||
}
|
||||
}
|
||||
|
||||
public displayAddress = (
|
||||
dPath?: string,
|
||||
index?: number
|
||||
): Promise<{
|
||||
publicKey: string;
|
||||
address: string;
|
||||
chainCode?: string;
|
||||
}> => {
|
||||
if (!dPath) {
|
||||
dPath = this.dPath;
|
||||
public async displayAddress() {
|
||||
const path = `${this.dPath}/${this.index}`;
|
||||
|
||||
try {
|
||||
const ethApp = await makeApp();
|
||||
await ethApp.getAddress(path, true, false);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error('Failed to display Ledger address:', err);
|
||||
return false;
|
||||
}
|
||||
if (!index) {
|
||||
index = this.index;
|
||||
}
|
||||
return this.ethApp.getAddress_async(dPath + '/' + index, true, false);
|
||||
};
|
||||
}
|
||||
|
||||
public getWalletType(): string {
|
||||
return translateRaw('X_LEDGER');
|
||||
}
|
||||
}
|
||||
|
||||
async function makeApp() {
|
||||
const transport = await TransportU2F.create();
|
||||
return new LedgerEth(transport);
|
||||
}
|
||||
|
||||
const isU2FError = (err: LedgerError): err is U2FError => !!err && !!(err as U2FError).metaData;
|
||||
const isStringError = (err: LedgerError): err is string => typeof err === 'string';
|
||||
function ledgerErrToMessage(err: LedgerError) {
|
||||
// https://developers.yubico.com/U2F/Libraries/Client_error_codes.html
|
||||
if (isU2FError(err)) {
|
||||
// Timeout
|
||||
if (err.metaData.code === 5) {
|
||||
return translateRaw('LEDGER_TIMEOUT');
|
||||
}
|
||||
|
||||
return err.metaData.type;
|
||||
}
|
||||
|
||||
if (isStringError(err)) {
|
||||
// Wrong app logged into
|
||||
if (err.includes('6804')) {
|
||||
return translateRaw('LEDGER_WRONG_APP');
|
||||
}
|
||||
// Ledger locked
|
||||
if (err.includes('6801')) {
|
||||
return translateRaw('LEDGER_LOCKED');
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
// Other
|
||||
return err.toString();
|
||||
}
|
||||
|
|
|
@ -3,23 +3,40 @@ import EthTx, { TxObj } from 'ethereumjs-tx';
|
|||
import { addHexPrefix } from 'ethereumjs-util';
|
||||
import { stripHexPrefixAndLower, padLeftEven } from 'libs/values';
|
||||
import TrezorConnect from 'vendor/trezor-connect';
|
||||
import { DeterministicWallet } from './deterministic';
|
||||
import { HardwareWallet, ChainCodeResponse } from './hardware';
|
||||
import { getTransactionFields } from 'libs/transaction';
|
||||
import mapValues from 'lodash/mapValues';
|
||||
|
||||
import { IFullWallet } from '../IWallet';
|
||||
import { translateRaw } from 'translations';
|
||||
|
||||
export const TREZOR_MINIMUM_FIRMWARE = '1.5.2';
|
||||
|
||||
export class TrezorWallet extends DeterministicWallet implements IFullWallet {
|
||||
export class TrezorWallet extends HardwareWallet {
|
||||
public static getChainCode(dpath: string): Promise<ChainCodeResponse> {
|
||||
return new Promise(resolve => {
|
||||
TrezorConnect.getXPubKey(
|
||||
dpath,
|
||||
res => {
|
||||
if (res.success) {
|
||||
resolve({
|
||||
publicKey: res.publicKey,
|
||||
chainCode: res.chainCode
|
||||
});
|
||||
} else {
|
||||
throw new Error(res.error);
|
||||
}
|
||||
},
|
||||
TREZOR_MINIMUM_FIRMWARE
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public signRawTransaction(tx: EthTx): Promise<Buffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { chainId, ...strTx } = getTransactionFields(tx);
|
||||
// stripHexPrefixAndLower identical to ethFuncs.getNakedAddress
|
||||
const cleanedTx = mapValues(mapValues(strTx, stripHexPrefixAndLower), padLeftEven);
|
||||
|
||||
(TrezorConnect as any).ethereumSignTx(
|
||||
TrezorConnect.ethereumSignTx(
|
||||
// Args
|
||||
this.getPath(),
|
||||
cleanedTx.nonce,
|
||||
|
@ -30,7 +47,7 @@ export class TrezorWallet extends DeterministicWallet implements IFullWallet {
|
|||
cleanedTx.data,
|
||||
chainId,
|
||||
// Callback
|
||||
(result: any) => {
|
||||
result => {
|
||||
if (!result.success) {
|
||||
return reject(Error(result.error));
|
||||
}
|
||||
|
@ -40,7 +57,7 @@ export class TrezorWallet extends DeterministicWallet implements IFullWallet {
|
|||
const txToSerialize: TxObj = {
|
||||
...strTx,
|
||||
v: addHexPrefix(new BN(result.v).toString(16)),
|
||||
r: addHexPrefix(result.r),
|
||||
r: addHexPrefix(result.r.toString()),
|
||||
s: addHexPrefix(result.s)
|
||||
};
|
||||
const eTx = new EthTx(txToSerialize);
|
||||
|
@ -51,30 +68,25 @@ export class TrezorWallet extends DeterministicWallet implements IFullWallet {
|
|||
});
|
||||
}
|
||||
|
||||
public signMessage = () => Promise.reject(new Error('Signing via Trezor not yet supported.'));
|
||||
public signMessage() {
|
||||
return Promise.reject(new Error('Signing via Trezor not yet supported.'));
|
||||
}
|
||||
|
||||
public displayAddress = (dPath?: string, index?: number): Promise<any> => {
|
||||
if (!dPath) {
|
||||
dPath = this.dPath;
|
||||
}
|
||||
if (!index) {
|
||||
index = this.index;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
(TrezorConnect as any).ethereumGetAddress(
|
||||
dPath + '/' + index,
|
||||
(res: any) => {
|
||||
public displayAddress(): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
TrezorConnect.ethereumGetAddress(
|
||||
`${this.dPath}/${this.index}`,
|
||||
res => {
|
||||
if (res.error) {
|
||||
reject(res.error);
|
||||
resolve(false);
|
||||
} else {
|
||||
resolve(res);
|
||||
resolve(true);
|
||||
}
|
||||
},
|
||||
TREZOR_MINIMUM_FIRMWARE
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
public getWalletType(): string {
|
||||
return translateRaw('X_TREZOR');
|
||||
|
|
|
@ -187,9 +187,6 @@ export function getDisabledWallets(state: AppState): DisabledWallets {
|
|||
}
|
||||
|
||||
// Some wallets are disabled on certain platforms
|
||||
if (process.env.BUILD_DOWNLOADABLE) {
|
||||
addReason([SecureWalletName.LEDGER_NANO_S], 'This wallet is only supported at MyCrypto.com');
|
||||
}
|
||||
if (process.env.BUILD_ELECTRON) {
|
||||
addReason([SecureWalletName.WEB3], 'This wallet is not supported in the MyCrypto app');
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
"X_MIST": "Mist",
|
||||
"X_WEB3": "Web3",
|
||||
"X_MNEMONIC": "Mnemonic Phrase ",
|
||||
"X_HARDWARE_WALLET": "hardware wallet ",
|
||||
"X_PRINT": "Print Paper Wallet ",
|
||||
"X_PRINTDESC": "ProTip: Click print and save this as a PDF, even if you do not own a printer! ",
|
||||
"X_PRIVKEY": "Private Key (unencrypted) ",
|
||||
|
@ -76,6 +77,7 @@
|
|||
"X_TREZOR": "TREZOR ",
|
||||
"X_HARDWARE_WALLET": "Hardware Wallet",
|
||||
"ADD_TREZOR_SCAN": "Connect to TREZOR ",
|
||||
"X_KEEPKEY": "KeepKey",
|
||||
"X_PARITYSIGNER": "Parity Signer ",
|
||||
"ADD_PARITY_DESC": "Connect & sign via your Parity Signer mobile app ",
|
||||
"ADD_PARITY_1": "Transaction canceled ",
|
||||
|
@ -447,7 +449,9 @@
|
|||
"OLD_MYCRYPTO": "MyCrypto (Legacy Site)",
|
||||
"LEDGER_REFERRAL_1": "Buy a Ledger Wallet",
|
||||
"LEDGER_REFERRAL_2": "Don’t have a Ledger? Order one now!",
|
||||
"LEDGER_TIP": "**Tip:** Make sure you're logged into the $network app on your hardware wallet, and have enabled browser support in the settings",
|
||||
"LEDGER_TIP": "Make sure your Ledger is unlocked, you've selected the **$network** app on the device, and browser support is **$browserSupportState**.",
|
||||
"ENABLED": "enabled",
|
||||
"DISABLED": "disabled",
|
||||
"LEDGER_TIMEOUT": "The request timed out",
|
||||
"LEDGER_WRONG_APP": "Wrong application selected on your device",
|
||||
"LEDGER_LOCKED": "Your Ledger device is locked",
|
||||
|
@ -636,6 +640,8 @@
|
|||
"COINBASE_PROMO_SMALL": "It’s now easier to get more ETH",
|
||||
"COINBASE_PROMO": "Buy ETH with USD",
|
||||
"SIMPLEX_PROMO": "Buy ETH with EUR",
|
||||
"TESTNET": "Testnet"
|
||||
"TESTNET": "Testnet",
|
||||
"ENCLAVE_LEDGER_FAIL": "Failed to connect to Ledger",
|
||||
"ENCLAVE_LEDGER_IN_USE": "Your Ledger is currently in use with another application. Please wait, or close other wallet applications before trying again."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,11 +13,15 @@ declare module '@ledgerhq/hw-app-eth' {
|
|||
* @returns {Promise<{ publicKey: string; address: string; chainCode?: string }>}
|
||||
* @memberof Eth
|
||||
*/
|
||||
public getAddress(
|
||||
public getAddress<BoolChaincode extends boolean>(
|
||||
path: string,
|
||||
boolDisplay?: boolean,
|
||||
boolChaincode?: boolean
|
||||
): Promise<{ publicKey: string; address: string; chainCode?: string }>;
|
||||
boolChaincode?: BoolChaincode
|
||||
): Promise<{
|
||||
publicKey: string;
|
||||
address: string;
|
||||
chainCode: BoolChaincode extends true ? string : undefined;
|
||||
}>;
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
declare module 'ledgerco' {
|
||||
// TODO: fill the library typings out
|
||||
export const comm_u2f: any;
|
||||
export class eth {
|
||||
constructor(transport: any);
|
||||
getAddress_async(
|
||||
path: string,
|
||||
boolDisplay?: boolean,
|
||||
boolChaincode?: boolean
|
||||
): Promise<{
|
||||
publicKey: string;
|
||||
address: string;
|
||||
chainCode: string;
|
||||
}>;
|
||||
|
||||
signTransaction_async(
|
||||
path: string,
|
||||
rawTxHex: string
|
||||
): Promise<{
|
||||
s: string;
|
||||
v: string;
|
||||
r: string;
|
||||
}>;
|
||||
|
||||
getAppConfiguration_async(): Promise<{
|
||||
arbitraryDataEnabled: number;
|
||||
version: string;
|
||||
}>;
|
||||
|
||||
signPersonalMessage_async(
|
||||
path: string,
|
||||
messageHex: string
|
||||
): Promise<{
|
||||
v: number;
|
||||
s: string;
|
||||
r: string;
|
||||
}>;
|
||||
|
||||
signPersonalMessage_async(
|
||||
path: string,
|
||||
messageHex: string,
|
||||
cb: (signed: any, error: any) => any
|
||||
): void;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
declare module 'vendor/trezor-connect' {
|
||||
type Path = number[] | string;
|
||||
|
||||
interface TxSignature {
|
||||
r: number;
|
||||
s: string;
|
||||
v: string;
|
||||
}
|
||||
|
||||
interface MessageSignature {
|
||||
signature: string;
|
||||
address: string;
|
||||
}
|
||||
|
||||
interface PublicKey {
|
||||
xpubkey: string;
|
||||
path: string;
|
||||
serializedPath: string;
|
||||
chainCode: string;
|
||||
publicKey: string;
|
||||
}
|
||||
|
||||
interface ErrorResponse {
|
||||
success: false;
|
||||
error: string;
|
||||
}
|
||||
type SuccessResponse<T> = {
|
||||
success: true;
|
||||
error: undefined;
|
||||
} & T;
|
||||
type Response<T> = ErrorResponse | SuccessResponse<T>;
|
||||
|
||||
namespace TrezorConnect {
|
||||
export function getXPubKey(
|
||||
path: Path,
|
||||
cb: (res: Response<PublicKey>) => void,
|
||||
minFirmware?: string
|
||||
): void;
|
||||
|
||||
export function ethereumSignTx(
|
||||
path: Path,
|
||||
nonce: string,
|
||||
gasPrice: string,
|
||||
gasLimit: string,
|
||||
to: string,
|
||||
value: string,
|
||||
data: string | null,
|
||||
chainId: number | null,
|
||||
cb: (signature: Response<TxSignature>) => void,
|
||||
minFirmware?: string
|
||||
): void;
|
||||
|
||||
export function signMessage(
|
||||
path: Path,
|
||||
message: string,
|
||||
cb: (res: Response<MessageSignature>) => void,
|
||||
coin?: string,
|
||||
minFirmware?: string
|
||||
): void;
|
||||
|
||||
export function ethereumGetAddress(
|
||||
path: Path,
|
||||
cb: (res: Response<{ address: string }>) => void,
|
||||
minFirmware?: string
|
||||
): void;
|
||||
}
|
||||
|
||||
export default TrezorConnect;
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
/* tslint:disable max-classes-per-file */
|
||||
// Types are only based off of what's mentioned in the API
|
||||
// https://github.com/trezor/trezor.js/blob/master/API.md
|
||||
|
||||
declare module 'mycrypto-trezor.js' {
|
||||
import { EventEmitter } from 'events';
|
||||
import { Transport, TrezorDeviceInfoWithSession as DeviceDescriptor } from 'trezor-link';
|
||||
|
||||
/***************/
|
||||
/* Device List */
|
||||
/***************/
|
||||
|
||||
export interface DeviceListOptions {
|
||||
debug?: boolean;
|
||||
debugInfo?: boolean;
|
||||
transport?: Transport;
|
||||
nodeTransport?: Transport;
|
||||
configUrl?: string;
|
||||
config?: string;
|
||||
bridgeVersionUrl?: string;
|
||||
clearSession?: boolean;
|
||||
clearSessionTime?: number;
|
||||
rememberDevicePasshprase?: boolean;
|
||||
// Unsure of these options or our need for them
|
||||
// getPassphraseHash?(device: Device): number[] | undefined;
|
||||
// xpubDerive?: (xpub: string, network: bitcoin.Network, index: number) => Promise<string>;
|
||||
}
|
||||
|
||||
export class DeviceList extends EventEmitter {
|
||||
public transport: Transport | undefined;
|
||||
public devices: { [k: string]: Device };
|
||||
public unacquiredDevices: { [k: string]: UnacquiredDevice };
|
||||
constructor(opts?: DeviceListOptions);
|
||||
public acquireFirstDevice(
|
||||
rejectOnEmpty?: boolean
|
||||
): Promise<{ device: Device; session: Session }>;
|
||||
}
|
||||
|
||||
/**********/
|
||||
/* Device */
|
||||
/**********/
|
||||
|
||||
export interface CoinType {
|
||||
coin_name: string;
|
||||
coin_shortcut: string;
|
||||
address_type: number;
|
||||
maxfee_kb: number;
|
||||
address_type_p2sh: number;
|
||||
}
|
||||
|
||||
export interface Features {
|
||||
vendor: string;
|
||||
major_version: number;
|
||||
minor_version: number;
|
||||
patch_version: number;
|
||||
bootloader_mode: boolean;
|
||||
device_id: string;
|
||||
pin_protection: boolean;
|
||||
passphrase_protection: boolean;
|
||||
language: string;
|
||||
label: string;
|
||||
coins: CoinType[];
|
||||
initialized: boolean;
|
||||
revision: string;
|
||||
bootloader_hash: string;
|
||||
imported: boolean;
|
||||
pin_cached: boolean;
|
||||
passphrase_cached: boolean;
|
||||
needs_backup?: boolean;
|
||||
firmware_present?: boolean;
|
||||
flags?: number;
|
||||
model?: string;
|
||||
unfinished_backup?: boolean;
|
||||
}
|
||||
|
||||
export interface RunOptions {
|
||||
aggressive?: boolean;
|
||||
skipFinalReload?: boolean;
|
||||
waiting?: boolean;
|
||||
onlyOneActivity?: boolean;
|
||||
}
|
||||
|
||||
export class Device extends EventEmitter {
|
||||
public path: string;
|
||||
public features: Features;
|
||||
|
||||
constructor(
|
||||
transport: Transport,
|
||||
descriptor: DeviceDescriptor,
|
||||
features: Features,
|
||||
deviceList: DeviceList
|
||||
);
|
||||
|
||||
public isBootloader(): boolean;
|
||||
public isInitialized(): boolean;
|
||||
public getVersion(): string;
|
||||
public atLeast(v: string): boolean;
|
||||
public isUsed(): boolean;
|
||||
public isUsedHere(): boolean;
|
||||
public isUsedElsewhere(): boolean;
|
||||
|
||||
public run<T>(fn: (session: Session) => Promise<T> | T, options?: RunOptions): Promise<T>;
|
||||
public waitForSessionAndRun<T>(
|
||||
fn: (session: Session) => Promise<T> | T,
|
||||
options?: RunOptions
|
||||
): Promise<T>;
|
||||
public steal(): Promise<boolean>;
|
||||
}
|
||||
|
||||
/*********************/
|
||||
/* Unacquired Device */
|
||||
/*********************/
|
||||
|
||||
export class UnacquiredDevice extends EventEmitter {
|
||||
public path: string;
|
||||
constructor(transport: Transport, descriptor: DeviceDescriptor, deviceList: DeviceList);
|
||||
public steal(): Promise<boolean>;
|
||||
}
|
||||
|
||||
/***********/
|
||||
/* Session */
|
||||
/***********/
|
||||
|
||||
export interface MessageResponse<T> {
|
||||
type: string;
|
||||
message: T;
|
||||
}
|
||||
|
||||
export interface EthereumSignature {
|
||||
v: number;
|
||||
r: string;
|
||||
s: string;
|
||||
}
|
||||
|
||||
export interface HDPubNode {
|
||||
depth: number;
|
||||
fingerprint: number;
|
||||
child_num: number;
|
||||
chain_code: string;
|
||||
public_key: string;
|
||||
}
|
||||
|
||||
export interface PublicKey {
|
||||
node: HDPubNode;
|
||||
xpub: string;
|
||||
}
|
||||
|
||||
export type DefaultMessageResponse = MessageResponse<object>;
|
||||
|
||||
export class Session extends EventEmitter {
|
||||
public typedCall<T>(type: string, resType: string, message: T): Promise<T>;
|
||||
public getEntropy(size: number): Promise<MessageResponse<{ bytes: string }>>;
|
||||
public ethereumGetAddress(
|
||||
path: number[],
|
||||
display?: boolean
|
||||
): Promise<MessageResponse<{ address: string; path: number[] }>>;
|
||||
public clearSession(): Promise<boolean>;
|
||||
public signEthMessage(
|
||||
path: number[],
|
||||
message: string
|
||||
): Promise<MessageResponse<{ address: string; signature: string }>>;
|
||||
public verifyEthMessage(address: string, signature: string, message: string): Promise<boolean>;
|
||||
public signEthTx(
|
||||
path: number[],
|
||||
nonce: string,
|
||||
gasPrice: string,
|
||||
gasLimit: string,
|
||||
to: string,
|
||||
value: string,
|
||||
data?: string,
|
||||
chainId?: number
|
||||
): Promise<EthereumSignature>;
|
||||
public getPublicKey(
|
||||
path: number[],
|
||||
coin?: string | CoinType
|
||||
): Promise<MessageResponse<PublicKey>>;
|
||||
|
||||
/* Unused functions, either Bitcoin-centric or just things we wouldn't want to touch */
|
||||
// public getAddress(path: number[], coin: string | CoinType, display: boolean): Promise<MessageResponse<{ address: string }>>;
|
||||
// public verifyAddress(path: number[], refAddress: string, coin: string | CoinType): Promise<boolean>;
|
||||
// public getHDNode(path: number[], coin: string | CoinType): Promise<HDNode>;
|
||||
// public wipeDevice(): Promise<boolean>;
|
||||
// public resetDevice(...): Promise<boolean>;
|
||||
// public loadDevice(...): Promise<boolean>;
|
||||
// public recoverDevice(...): Promise<boolean>;
|
||||
// public updateFirmware(payload: string): Promise<boolean>;
|
||||
// public signMessage(path: number[], message: string, coin: string | CoinType, segwit: boolean): Promise<object>;
|
||||
// public verifyMessage(...): Promise<boolean>;
|
||||
// public signIdentity(...): any;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
declare module 'trezor-link' {
|
||||
export interface TrezorDeviceInfo {
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface TrezorDeviceInfoWithSession extends TrezorDeviceInfo {
|
||||
session?: string;
|
||||
}
|
||||
|
||||
export interface AcquireInput {
|
||||
path: string;
|
||||
previous?: string;
|
||||
checkPrevious: boolean;
|
||||
}
|
||||
|
||||
export interface MessageFromTrezor {
|
||||
type: string;
|
||||
message: any;
|
||||
}
|
||||
|
||||
export interface Transport {
|
||||
configured: boolean;
|
||||
version: string;
|
||||
name: string;
|
||||
requestNeeded: boolean;
|
||||
isOutdated: boolean;
|
||||
|
||||
enumerate(): Promise<TrezorDeviceInfoWithSession[]>;
|
||||
listen(old?: TrezorDeviceInfoWithSession[]): Promise<TrezorDeviceInfoWithSession[]>;
|
||||
acquire(input: AcquireInput): Promise<string>;
|
||||
release(session: string, onclose: boolean): Promise<void>;
|
||||
configure(signedData: string): Promise<void>;
|
||||
call(session: string, name: string, data: any): Promise<MessageFromTrezor>;
|
||||
init(debug?: boolean): Promise<void>;
|
||||
stop(): void;
|
||||
requestDevice(): Promise<void>;
|
||||
setBridgeLatestUrl(url: string): void;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
import 'babel-polyfill';
|
||||
import { app } from 'electron';
|
||||
import { registerServer } from 'shared/enclave/server';
|
||||
import getWindow from './window';
|
||||
|
||||
// Quit application when all windows are closed
|
||||
|
@ -20,3 +22,6 @@ app.on('activate', () => {
|
|||
app.on('ready', () => {
|
||||
getWindow();
|
||||
});
|
||||
|
||||
// Register enclave protocol
|
||||
registerServer(app);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { BrowserWindow, Menu, shell } from 'electron';
|
||||
import { URL } from 'url';
|
||||
import path from 'path';
|
||||
import MENU from './menu';
|
||||
import popupContextMenu from './contextMenu';
|
||||
import { APP_TITLE } from '../constants';
|
||||
|
@ -25,7 +26,8 @@ export default function getWindow() {
|
|||
webPreferences: {
|
||||
devTools: true,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true
|
||||
contextIsolation: true,
|
||||
preload: path.join(__dirname, 'preload.js')
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
import { registerProtocol } from 'shared/enclave/preload';
|
||||
registerProtocol();
|
|
@ -7,6 +7,7 @@
|
|||
"moduleDirectories": ["node_modules", "common"],
|
||||
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "worker.ts"],
|
||||
"moduleNameMapper": {
|
||||
"shared/(.*)": "<rootDir>/shared/$1",
|
||||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
|
||||
"<rootDir>/jest_config/__mocks__/fileMock.ts",
|
||||
"\\.(css|scss)$": "<rootDir>/jest_config/__mocks__/styleMock.ts",
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"moduleDirectories": ["node_modules", "common"],
|
||||
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json"],
|
||||
"moduleNameMapper": {
|
||||
"shared/(.*)": "<rootDir>/shared/$1",
|
||||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
|
||||
"<rootDir>/jest_config/__mocks__/fileMock.ts",
|
||||
"\\.(css|scss)$": "<rootDir>/jest_config/__mocks__/styleMock.ts"
|
||||
|
|
12
package.json
12
package.json
|
@ -11,6 +11,9 @@
|
|||
"npm": ">= 5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ledgerhq/hw-app-eth": "4.7.3",
|
||||
"@ledgerhq/hw-transport-node-hid": "4.7.6",
|
||||
"@ledgerhq/hw-transport-u2f": "4.12.0",
|
||||
"@parity/qr-signer": "0.2.1",
|
||||
"babel-polyfill": "6.26.0",
|
||||
"bip39": "2.5.0",
|
||||
|
@ -18,7 +21,6 @@
|
|||
"bootstrap-sass": "3.3.7",
|
||||
"classnames": "2.2.5",
|
||||
"electron-updater": "2.21.10",
|
||||
"mycrypto-eth-exists": "1.0.0",
|
||||
"ethereum-blockies-base64": "1.0.1",
|
||||
"ethereumjs-abi": "git://github.com/ethereumjs/ethereumjs-abi.git#09c3c48fd3bed143df7fa8f36f6f164205e23796",
|
||||
"ethereumjs-tx": "1.3.4",
|
||||
|
@ -28,11 +30,12 @@
|
|||
"hdkey": "0.8.0",
|
||||
"idna-uts46": "1.1.0",
|
||||
"jsonschema": "1.2.4",
|
||||
"ledgerco": "1.2.1",
|
||||
"lodash": "4.17.5",
|
||||
"moment": "2.22.1",
|
||||
"moment-timezone": "0.5.14",
|
||||
"mycrypto-eth-exists": "1.0.0",
|
||||
"mycrypto-shepherd": "1.4.0",
|
||||
"mycrypto-trezor.js": "6.17.5",
|
||||
"normalizr": "3.2.4",
|
||||
"qrcode": "1.2.0",
|
||||
"qrcode.react": "0.8.0",
|
||||
|
@ -95,6 +98,7 @@
|
|||
"css-loader": "0.28.11",
|
||||
"electron": "2.0.1",
|
||||
"electron-builder": "20.13.4",
|
||||
"electron-rebuild": "1.7.3",
|
||||
"empty": "0.10.1",
|
||||
"enzyme": "3.3.0",
|
||||
"enzyme-adapter-react-16": "1.1.1",
|
||||
|
@ -112,6 +116,7 @@
|
|||
"lint-staged": "7.0.4",
|
||||
"mini-css-extract-plugin": "0.4.0",
|
||||
"minimist": "1.2.0",
|
||||
"node-hid": "0.7.2",
|
||||
"mycrypto-nano-result": "0.0.1",
|
||||
"node-sass": "4.8.3",
|
||||
"nodemon": "1.17.3",
|
||||
|
@ -185,7 +190,8 @@
|
|||
"formatAll": "find ./common/ -name '*.ts*' | xargs prettier --write --config ./.prettierrc --config-precedence file-override",
|
||||
"prettier:diff": "prettier --write --config ./.prettierrc --list-different \"common/**/*.ts\" \"common/**/*.tsx\"",
|
||||
"prepush": "npm run tslint && npm run tscheck",
|
||||
"update:tokens": "ts-node scripts/update-tokens"
|
||||
"update:tokens": "ts-node scripts/update-tokens",
|
||||
"postinstall": "electron-rebuild --force"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx}": [
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
# ETH Enclave
|
||||
|
||||
Enclave is the communication layer between hardware wallets and the Electron
|
||||
web view. This layer is necessary if you've disabled node integration, and
|
||||
enabled context isolation on your webview ([Which is something you should do.](https://github.com/electron/electron/blob/master/docs/tutorial/security.md))
|
||||
|
||||
Enclave uses Electron's Protocol API to open up an HTTP-like communication layer
|
||||
between Electron and the web view. You can read [more about this approach here](https://gist.github.com/wbobeirne/ec3e52b3db1359278c19f29e1bbfd5f1).
|
||||
|
||||
## Setup
|
||||
|
||||
```js
|
||||
// Electron main js
|
||||
import { registerServer } from 'enclave/server';
|
||||
registerServer(app);
|
||||
```
|
||||
|
||||
```js
|
||||
// Electron preload js
|
||||
import { registerProtocol } from 'enclave/preload';
|
||||
registerProtocol();
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import EnclaveAPI, { WalletTypes } from 'enclave/client';
|
||||
EnclaveAPI.getChainCode({
|
||||
walletType: WalletTypes.LEDGER,
|
||||
dpath: "m/44'/60'/0'/0"
|
||||
}).then(({ publicKey, chainCode }) => {
|
||||
// ...
|
||||
});
|
||||
```
|
|
@ -0,0 +1,35 @@
|
|||
import { makeRequest } from './requests';
|
||||
import {
|
||||
EnclaveMethods,
|
||||
EnclaveMethodParams,
|
||||
GetChainCodeParams,
|
||||
GetChainCodeResponse,
|
||||
SignTransactionParams,
|
||||
SignTransactionResponse,
|
||||
SignMessageParams,
|
||||
SignMessageResponse,
|
||||
DisplayAddressParams,
|
||||
DisplayAddressResponse
|
||||
} from 'shared/enclave/types';
|
||||
|
||||
function makeMethod<ParamsType extends EnclaveMethodParams, ResponseType>(method: EnclaveMethods) {
|
||||
return (params: ParamsType) => makeRequest<ResponseType>(method, params);
|
||||
}
|
||||
|
||||
export class EnclaveAPIClass {
|
||||
public getChainCode = makeMethod<GetChainCodeParams, GetChainCodeResponse>(
|
||||
EnclaveMethods.GET_CHAIN_CODE
|
||||
);
|
||||
public signTransaction = makeMethod<SignTransactionParams, SignTransactionResponse>(
|
||||
EnclaveMethods.SIGN_TRANSACTION
|
||||
);
|
||||
public signMessage = makeMethod<SignMessageParams, SignMessageResponse>(
|
||||
EnclaveMethods.SIGN_MESSAGE
|
||||
);
|
||||
public displayAddress = makeMethod<DisplayAddressParams, DisplayAddressResponse>(
|
||||
EnclaveMethods.DISPLAY_ADDRESS
|
||||
);
|
||||
}
|
||||
|
||||
export default new EnclaveAPIClass();
|
||||
export * from 'shared/enclave/types';
|
|
@ -0,0 +1,17 @@
|
|||
import { EnclaveMethods, EnclaveMethodParams, EnclaveResponse } from 'shared/enclave/types';
|
||||
import { PROTOCOL_NAME } from 'shared/enclave/utils';
|
||||
|
||||
export function makeRequest<T>(type: EnclaveMethods, params: EnclaveMethodParams): Promise<T> {
|
||||
return fetch(`${PROTOCOL_NAME}://${type}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then((res: EnclaveResponse<T>) => {
|
||||
const { error, data } = res;
|
||||
if (data) {
|
||||
return data;
|
||||
}
|
||||
throw new Error(error!.message || 'Unknown response from server');
|
||||
});
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { webFrame } from 'electron';
|
||||
import { PROTOCOL_NAME } from 'shared/enclave/utils';
|
||||
|
||||
export function registerProtocol() {
|
||||
// Whitelist custom protocol
|
||||
webFrame.registerURLSchemeAsPrivileged(PROTOCOL_NAME);
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { getWalletLib } from 'shared/enclave/server/wallets';
|
||||
import { DisplayAddressParams, DisplayAddressResponse } from 'shared/enclave/types';
|
||||
|
||||
export function displayAddress(params: DisplayAddressParams): Promise<DisplayAddressResponse> {
|
||||
const wallet = getWalletLib(params.walletType);
|
||||
return wallet.displayAddress(params.path);
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { getWalletLib } from 'shared/enclave/server/wallets';
|
||||
import { GetChainCodeParams, GetChainCodeResponse } from 'shared/enclave/types';
|
||||
|
||||
export function getChainCode(params: GetChainCodeParams): Promise<GetChainCodeResponse> {
|
||||
const wallet = getWalletLib(params.walletType);
|
||||
return wallet.getChainCode(params.dpath);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { getChainCode } from './getChainCode';
|
||||
import { signTransaction } from './signTransaction';
|
||||
import { signMessage } from './signMessage';
|
||||
import { displayAddress } from './displayAddress';
|
||||
import { EnclaveMethods, EnclaveMethodParams, EnclaveMethodResponse } from 'shared/enclave/types';
|
||||
|
||||
type EnclaveHandlers = {
|
||||
[key in EnclaveMethods]: (
|
||||
params: EnclaveMethodParams
|
||||
) => EnclaveMethodResponse | Promise<EnclaveMethodResponse>
|
||||
};
|
||||
|
||||
const handlers: EnclaveHandlers = {
|
||||
[EnclaveMethods.GET_CHAIN_CODE]: getChainCode,
|
||||
[EnclaveMethods.SIGN_TRANSACTION]: signTransaction,
|
||||
[EnclaveMethods.SIGN_MESSAGE]: signMessage,
|
||||
[EnclaveMethods.DISPLAY_ADDRESS]: displayAddress
|
||||
};
|
||||
|
||||
export default handlers;
|
|
@ -0,0 +1,7 @@
|
|||
import { getWalletLib } from 'shared/enclave/server/wallets';
|
||||
import { SignMessageParams, SignMessageResponse } from 'shared/enclave/types';
|
||||
|
||||
export function signMessage(params: SignMessageParams): Promise<SignMessageResponse> {
|
||||
const wallet = getWalletLib(params.walletType);
|
||||
return wallet.signMessage(params.message, params.path);
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { getWalletLib } from 'shared/enclave/server/wallets';
|
||||
import { SignTransactionParams, SignTransactionResponse } from 'shared/enclave/types';
|
||||
|
||||
export function signTransaction(params: SignTransactionParams): Promise<SignTransactionResponse> {
|
||||
const wallet = getWalletLib(params.walletType);
|
||||
return wallet.signTransaction(params.transaction, params.path);
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
import { protocol, App } from 'electron';
|
||||
import handlers from './handlers';
|
||||
import { PROTOCOL_NAME, isValidEventType } from 'shared/enclave/utils';
|
||||
import { EnclaveMethods, EnclaveMethodParams, EnclaveResponse } from 'shared/enclave/types';
|
||||
|
||||
export function registerServer(app: App) {
|
||||
// Register protocol scheme
|
||||
protocol.registerStandardSchemes([PROTOCOL_NAME]);
|
||||
|
||||
app.on('ready', () => {
|
||||
// Register custom protocol behavior
|
||||
protocol.registerStringProtocol(PROTOCOL_NAME, async (req, cb) => {
|
||||
let res: EnclaveResponse;
|
||||
|
||||
try {
|
||||
const method = getMethod(req);
|
||||
const params = getParams(method, req);
|
||||
const data = await handlers[method](params);
|
||||
res = { data };
|
||||
} catch (err) {
|
||||
console.error(`Request to '${req.url}' failed with error:`, err);
|
||||
res = {
|
||||
error: {
|
||||
code: 500,
|
||||
type: err.name,
|
||||
message: err.message
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
cb(JSON.stringify(res));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getMethod(req: Electron.RegisterStringProtocolRequest): EnclaveMethods {
|
||||
const urlSplit = req.url.split(`${PROTOCOL_NAME}://`);
|
||||
|
||||
if (!urlSplit[1]) {
|
||||
throw new Error('No method provided');
|
||||
}
|
||||
|
||||
const method = urlSplit[1].replace('/', '');
|
||||
if (!isValidEventType(method)) {
|
||||
throw new Error(`Invalid or unknown method '${method}'`);
|
||||
}
|
||||
|
||||
return method;
|
||||
}
|
||||
|
||||
function getParams(
|
||||
method: EnclaveMethods,
|
||||
req: Electron.RegisterStringProtocolRequest
|
||||
): EnclaveMethodParams {
|
||||
const data = req.uploadData.find(d => !!d.bytes);
|
||||
|
||||
if (!data) {
|
||||
throw new Error(`No data provided for '${method}'`);
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: Validate params based on provided method
|
||||
const params = JSON.parse(data.bytes.toString());
|
||||
return params as EnclaveMethodParams;
|
||||
} catch (err) {
|
||||
throw new Error(`Invalid JSON blob provided for '${method}': ${err.message}`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import Ledger from './ledger';
|
||||
import Trezor from './trezor';
|
||||
import KeepKey from './keepkey';
|
||||
import { WalletTypes, WalletLib } from 'shared/enclave/types';
|
||||
|
||||
export const wallets: { [key in WalletTypes]: WalletLib } = {
|
||||
[WalletTypes.LEDGER]: Ledger,
|
||||
[WalletTypes.TREZOR]: Trezor,
|
||||
[WalletTypes.KEEPKEY]: KeepKey
|
||||
};
|
||||
|
||||
export function getWalletLib(type: WalletTypes): WalletLib {
|
||||
return wallets[type];
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import { WalletLib } from 'shared/enclave/types';
|
||||
|
||||
const KeepKey: WalletLib = {
|
||||
async getChainCode() {
|
||||
throw new Error('Not yet implemented');
|
||||
},
|
||||
|
||||
async signTransaction() {
|
||||
throw new Error('Not yet implemented');
|
||||
},
|
||||
|
||||
async signMessage() {
|
||||
throw new Error('Not yet implemented');
|
||||
},
|
||||
|
||||
async displayAddress() {
|
||||
throw new Error('Not yet implemented');
|
||||
}
|
||||
};
|
||||
|
||||
export default KeepKey;
|
|
@ -0,0 +1,84 @@
|
|||
import EthTx from 'ethereumjs-tx';
|
||||
import { addHexPrefix, toBuffer } from 'ethereumjs-util';
|
||||
import { WalletLib } from 'shared/enclave/types';
|
||||
import LedgerTransport from '@ledgerhq/hw-transport';
|
||||
import TransportNodeHid from '@ledgerhq/hw-transport-node-hid';
|
||||
import LedgerEth from '@ledgerhq/hw-app-eth';
|
||||
let transport: LedgerTransport<string> | null;
|
||||
|
||||
async function getEthApp() {
|
||||
try {
|
||||
if (!transport) {
|
||||
transport = await TransportNodeHid.create();
|
||||
transport.on('disconnect', () => (transport = null));
|
||||
}
|
||||
return new LedgerEth(transport);
|
||||
} catch (err) {
|
||||
if (err && err.name === 'TransportError') {
|
||||
throw new Error('ENCLAVE_LEDGER_IN_USE');
|
||||
}
|
||||
if (err && err.message && err.message.includes('cannot open device with path')) {
|
||||
throw new Error('ENCLAVE_LEDGER_IN_USE');
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const Ledger: WalletLib = {
|
||||
async getChainCode(dpath) {
|
||||
const app = await getEthApp();
|
||||
try {
|
||||
const res = await app.getAddress(dpath, false, true);
|
||||
return {
|
||||
publicKey: res.publicKey,
|
||||
chainCode: res.chainCode
|
||||
};
|
||||
} catch (err) {
|
||||
console.error('Failed to get chain code from ledger:', err);
|
||||
throw new Error('ENCLAVE_LEDGER_FAIL');
|
||||
}
|
||||
},
|
||||
|
||||
async signTransaction(tx, path) {
|
||||
const app = await getEthApp();
|
||||
const ethTx = new EthTx({
|
||||
...tx,
|
||||
v: Buffer.from([tx.chainId]),
|
||||
r: toBuffer(0),
|
||||
s: toBuffer(0)
|
||||
});
|
||||
|
||||
const rsv = await app.signTransaction(path, ethTx.serialize().toString('hex'));
|
||||
const signedTx = new EthTx({
|
||||
...tx,
|
||||
r: addHexPrefix(rsv.r),
|
||||
s: addHexPrefix(rsv.s),
|
||||
v: addHexPrefix(rsv.v)
|
||||
});
|
||||
return {
|
||||
signedTransaction: signedTx.serialize().toString('hex')
|
||||
};
|
||||
},
|
||||
|
||||
async signMessage(msg, path) {
|
||||
const app = await getEthApp();
|
||||
const msgHex = Buffer.from(msg).toString('hex');
|
||||
const signed = await app.signPersonalMessage(path, msgHex);
|
||||
const combined = addHexPrefix(signed.r + signed.s + signed.v.toString(16));
|
||||
return {
|
||||
signedMessage: combined
|
||||
};
|
||||
},
|
||||
|
||||
async displayAddress(path) {
|
||||
try {
|
||||
const app = await getEthApp();
|
||||
await app.getAddress(path, true, false);
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
return { success: false };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default Ledger;
|
|
@ -0,0 +1,108 @@
|
|||
import { DeviceList, Session } from 'mycrypto-trezor.js';
|
||||
import BN from 'bn.js';
|
||||
import mapValues from 'lodash/mapValues';
|
||||
import { addHexPrefix } from 'ethereumjs-util';
|
||||
import EthTx from 'ethereumjs-tx';
|
||||
import { stripHexPrefixAndLower, padLeftEven } from 'libs/values';
|
||||
import { WalletLib } from 'shared/enclave/types';
|
||||
const deviceList = new DeviceList({ debug: false });
|
||||
|
||||
// Keep session in memory so that we're not constantly re-acquiring
|
||||
// Null it out if session is grabbed somewhere else first
|
||||
let currentSession: Session | null;
|
||||
async function getSession() {
|
||||
if (currentSession) {
|
||||
return currentSession;
|
||||
}
|
||||
|
||||
const { device, session } = await deviceList.acquireFirstDevice(true);
|
||||
device.on('disconnect', () => (currentSession = null));
|
||||
device.on('changedSessions', (_, isUsedHere) => {
|
||||
if (isUsedHere) {
|
||||
currentSession = null;
|
||||
}
|
||||
});
|
||||
|
||||
currentSession = session;
|
||||
return currentSession;
|
||||
}
|
||||
|
||||
const Trezor: WalletLib = {
|
||||
async getChainCode(dpath) {
|
||||
const session = await getSession();
|
||||
const { message } = await session.getPublicKey(parseHDPath(dpath));
|
||||
|
||||
return {
|
||||
chainCode: message.node.chain_code,
|
||||
publicKey: message.node.public_key
|
||||
};
|
||||
},
|
||||
|
||||
async signTransaction(tx, dpath) {
|
||||
const { chainId, ...strTx } = tx;
|
||||
const cleanedTx = mapValues(mapValues(strTx, stripHexPrefixAndLower), padLeftEven);
|
||||
|
||||
const session = await getSession();
|
||||
const res = await session.signEthTx(
|
||||
parseHDPath(dpath),
|
||||
cleanedTx.nonce,
|
||||
cleanedTx.gasPrice,
|
||||
cleanedTx.gasLimit,
|
||||
cleanedTx.to,
|
||||
cleanedTx.value,
|
||||
cleanedTx.data,
|
||||
chainId
|
||||
);
|
||||
|
||||
const signedTx = new EthTx({
|
||||
...strTx,
|
||||
v: addHexPrefix(new BN(res.v).toString(16)),
|
||||
r: addHexPrefix(res.r.toString()),
|
||||
s: addHexPrefix(res.s)
|
||||
});
|
||||
return {
|
||||
signedTransaction: signedTx.serialize().toString('hex')
|
||||
};
|
||||
},
|
||||
|
||||
async signMessage() {
|
||||
throw new Error('Signing is not supported on TREZOR devices');
|
||||
},
|
||||
|
||||
async displayAddress(path) {
|
||||
const session = await getSession();
|
||||
try {
|
||||
await session.ethereumGetAddress(parseHDPath(path), true);
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
return { success: false };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Lifted from https://github.com/trezor/connect/blob/7919d47ca9d483cf303d77907505ccc7d389c68c/popup/src/utils/path.js#L110
|
||||
// tslint:disable no-bitwise
|
||||
function parseHDPath(path: string) {
|
||||
return path
|
||||
.toLowerCase()
|
||||
.split('/')
|
||||
.filter(p => p !== 'm')
|
||||
.map(p => {
|
||||
let hardened = false;
|
||||
let n = parseInt(p, 10);
|
||||
if (p[p.length - 1] === "'") {
|
||||
hardened = true;
|
||||
p = p.substr(0, p.length - 1);
|
||||
}
|
||||
if (isNaN(n)) {
|
||||
throw new Error('Invalid path specified');
|
||||
}
|
||||
if (hardened) {
|
||||
// hardened index
|
||||
n = (n | 0x80000000) >>> 0;
|
||||
}
|
||||
return n;
|
||||
});
|
||||
}
|
||||
|
||||
export default Trezor;
|
|
@ -0,0 +1,105 @@
|
|||
// Enclave enums
|
||||
export enum EnclaveMethods {
|
||||
GET_CHAIN_CODE = 'get-chain-code',
|
||||
SIGN_TRANSACTION = 'sign-transaction',
|
||||
SIGN_MESSAGE = 'sign-message',
|
||||
DISPLAY_ADDRESS = 'display-address'
|
||||
}
|
||||
|
||||
export enum WalletTypes {
|
||||
LEDGER = 'ledger',
|
||||
TREZOR = 'trezor',
|
||||
KEEPKEY = 'keepkey'
|
||||
}
|
||||
|
||||
export interface RawTransaction {
|
||||
chainId: number;
|
||||
gasLimit: string;
|
||||
gasPrice: string;
|
||||
to: string;
|
||||
nonce: string;
|
||||
data: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
// Get chain code request
|
||||
export interface GetChainCodeParams {
|
||||
walletType: WalletTypes;
|
||||
dpath: string;
|
||||
}
|
||||
|
||||
export interface GetChainCodeResponse {
|
||||
publicKey: string;
|
||||
chainCode: string;
|
||||
}
|
||||
|
||||
// Sign Transaction Request
|
||||
export interface SignTransactionParams {
|
||||
walletType: WalletTypes;
|
||||
transaction: RawTransaction;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface SignTransactionResponse {
|
||||
signedTransaction: string;
|
||||
}
|
||||
|
||||
// Sign Message Request
|
||||
export interface SignMessageParams {
|
||||
walletType: WalletTypes;
|
||||
message: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface SignMessageResponse {
|
||||
signedMessage: string;
|
||||
}
|
||||
|
||||
// Display Address Request
|
||||
export interface DisplayAddressParams {
|
||||
walletType: WalletTypes;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface DisplayAddressResponse {
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
// All Requests & Responses
|
||||
export type EnclaveMethodParams =
|
||||
| GetChainCodeParams
|
||||
| SignTransactionParams
|
||||
| SignMessageParams
|
||||
| DisplayAddressParams;
|
||||
export type EnclaveMethodResponse =
|
||||
| GetChainCodeResponse
|
||||
| SignTransactionResponse
|
||||
| SignMessageResponse
|
||||
| DisplayAddressResponse;
|
||||
|
||||
// RPC requests, responses & failures
|
||||
export interface EnclaveSuccessResponse<T = EnclaveMethodResponse> {
|
||||
data: T;
|
||||
error?: undefined;
|
||||
}
|
||||
|
||||
export interface EnclaveErrorResponse {
|
||||
data?: undefined;
|
||||
error: {
|
||||
code: number;
|
||||
type: string;
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type EnclaveResponse<T = EnclaveMethodResponse> =
|
||||
| EnclaveSuccessResponse<T>
|
||||
| EnclaveErrorResponse;
|
||||
|
||||
// Wallet lib
|
||||
export interface WalletLib {
|
||||
getChainCode(dpath: string): Promise<GetChainCodeResponse>;
|
||||
signTransaction(transaction: RawTransaction, path: string): Promise<SignTransactionResponse>;
|
||||
signMessage(msg: string, path: string): Promise<SignMessageResponse>;
|
||||
displayAddress(path: string): Promise<DisplayAddressResponse>;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { EnclaveMethods } from './types';
|
||||
|
||||
export const PROTOCOL_NAME = 'eth-enclave';
|
||||
|
||||
const eventTypes = Object.values(EnclaveMethods);
|
||||
export const isValidEventType = (e: string): e is EnclaveMethods => eventTypes.includes(e);
|
|
@ -3,19 +3,20 @@ const webpack = require('webpack');
|
|||
const path = require('path');
|
||||
const ClearDistPlugin = require('./plugins/clearDist');
|
||||
const config = require('./config');
|
||||
const makeConfig = require('./makeConfig');
|
||||
|
||||
const electronConfig = {
|
||||
target: 'electron-main',
|
||||
mode: 'development',
|
||||
entry: {
|
||||
main: path.join(config.path.electron, 'main/index.ts')
|
||||
main: path.join(config.path.electron, 'main/index.ts'),
|
||||
preload: path.join(config.path.electron, 'preload/index.ts')
|
||||
},
|
||||
module: {
|
||||
rules: [config.typescriptRule]
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.js', '.json']
|
||||
extensions: ['.ts', '.js', '.json'],
|
||||
modules: config.resolve.modules
|
||||
},
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
|
@ -27,10 +28,14 @@ const electronConfig = {
|
|||
'process.env.NODE_ENV': JSON.stringify('development')
|
||||
}),
|
||||
],
|
||||
externals: {
|
||||
'node-hid': 'commonjs node-hid'
|
||||
},
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false
|
||||
}
|
||||
},
|
||||
devtool: 'eval'
|
||||
};
|
||||
|
||||
module.exports = electronConfig;
|
||||
|
|
|
@ -23,4 +23,6 @@ electronConfig.plugins = [
|
|||
new DelayPlugin(500)
|
||||
];
|
||||
|
||||
electronConfig.devtool = undefined;
|
||||
|
||||
module.exports = [electronConfig, jsConfig];
|
||||
|
|
306
yarn.lock
306
yarn.lock
|
@ -47,6 +47,32 @@
|
|||
core-js "^2.5.3"
|
||||
regenerator-runtime "^0.11.1"
|
||||
|
||||
"@ledgerhq/hw-app-eth@4.7.3":
|
||||
version "4.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-eth/-/hw-app-eth-4.7.3.tgz#d352e19658ae296532e522c53c8ec2a1a77b64e5"
|
||||
dependencies:
|
||||
"@ledgerhq/hw-transport" "^4.7.3"
|
||||
|
||||
"@ledgerhq/hw-transport-node-hid@4.7.6":
|
||||
version "4.7.6"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-4.7.6.tgz#f2bd7c714e359af84377d07dd6431f2aa582e71e"
|
||||
dependencies:
|
||||
"@ledgerhq/hw-transport" "^4.7.3"
|
||||
node-hid "^0.7.2"
|
||||
|
||||
"@ledgerhq/hw-transport-u2f@4.12.0":
|
||||
version "4.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-u2f/-/hw-transport-u2f-4.12.0.tgz#151c7ccaf14627bd26b95a9f4863e4eb76d09865"
|
||||
dependencies:
|
||||
"@ledgerhq/hw-transport" "^4.12.0"
|
||||
u2f-api "0.2.7"
|
||||
|
||||
"@ledgerhq/hw-transport@^4.12.0", "@ledgerhq/hw-transport@^4.7.3":
|
||||
version "4.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-4.13.0.tgz#463043200c96c91379318052ad4dbe8c80d95cf5"
|
||||
dependencies:
|
||||
events "^2.0.0"
|
||||
|
||||
"@parity/qr-signer@0.2.1":
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@parity/qr-signer/-/qr-signer-0.2.1.tgz#f8b0e0ff5d8ee90b1788951c8524cdf8b1aadf27"
|
||||
|
@ -542,6 +568,13 @@ asap@~2.0.3:
|
|||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
|
||||
|
||||
ascli@~0.3:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ascli/-/ascli-0.3.0.tgz#5e66230e5219fe3e8952a4efb4f20fae596a813a"
|
||||
dependencies:
|
||||
colour latest
|
||||
optjs latest
|
||||
|
||||
asn1.js@^4.0.0:
|
||||
version "4.10.1"
|
||||
resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0"
|
||||
|
@ -1317,7 +1350,7 @@ base-x@^1.1.0:
|
|||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/base-x/-/base-x-1.1.0.tgz#42d3d717474f9ea02207f6d1aa1f426913eeb7ac"
|
||||
|
||||
base-x@^3.0.4:
|
||||
base-x@^3.0.2, base-x@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.4.tgz#94c1788736da065edb1d68808869e357c977fa77"
|
||||
dependencies:
|
||||
|
@ -1351,20 +1384,39 @@ base@^0.11.1:
|
|||
mixin-deep "^1.2.0"
|
||||
pascalcase "^0.1.1"
|
||||
|
||||
bchaddrjs@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/bchaddrjs/-/bchaddrjs-0.2.1.tgz#8c2f1582e24e5ca11687ae19e2463543cfa0dd24"
|
||||
dependencies:
|
||||
bs58check "^2.1.1"
|
||||
cashaddrjs "^0.2.7"
|
||||
|
||||
bcrypt-pbkdf@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
|
||||
dependencies:
|
||||
tweetnacl "^0.14.3"
|
||||
|
||||
bech32@^1.1.2:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.3.tgz#bd47a8986bbb3eec34a56a097a84b8d3e9a2dfcd"
|
||||
|
||||
beeper@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809"
|
||||
|
||||
big-integer@^1.6.26:
|
||||
version "1.6.31"
|
||||
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.31.tgz#6d7852486e67c642502dcc03f7225a245c9fc7fa"
|
||||
|
||||
big.js@^3.1.3:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
|
||||
|
||||
bigi@^1.1.0, bigi@^1.4.0, bigi@^1.4.1:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/bigi/-/bigi-1.4.2.tgz#9c665a95f88b8b08fc05cfd731f561859d725825"
|
||||
|
||||
bignumber.js@^2.1.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-2.4.0.tgz#838a992da9f9d737e0f4b2db0be62bb09dd0c5e8"
|
||||
|
@ -1445,12 +1497,36 @@ bip39@2.5.0:
|
|||
safe-buffer "^5.0.1"
|
||||
unorm "^1.3.3"
|
||||
|
||||
bip66@^1.1.3:
|
||||
bip66@^1.1.0, bip66@^1.1.3:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/bip66/-/bip66-1.1.5.tgz#01fa8748785ca70955d5011217d1b3139969ca22"
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
bitcoin-ops@^1.3.0:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz#e45de620398e22fd4ca6023de43974ff42240278"
|
||||
|
||||
bitcoinjs-lib-zcash@^3.0.0, bitcoinjs-lib-zcash@^3.3.2:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/bitcoinjs-lib-zcash/-/bitcoinjs-lib-zcash-3.4.1.tgz#ea440857188ed3168f66ae8f6ecba603c53e3f22"
|
||||
dependencies:
|
||||
bech32 "^1.1.2"
|
||||
bigi "^1.4.0"
|
||||
bip66 "^1.1.0"
|
||||
bitcoin-ops "^1.3.0"
|
||||
bs58check "^2.0.0"
|
||||
create-hash "^1.1.0"
|
||||
create-hmac "^1.1.3"
|
||||
ecurve "^1.0.0"
|
||||
merkle-lib "^2.0.10"
|
||||
pushdata-bitcoin "^1.0.1"
|
||||
randombytes "^2.0.1"
|
||||
safe-buffer "^5.0.1"
|
||||
typeforce "^1.11.3"
|
||||
varuint-bitcoin "^1.0.4"
|
||||
wif "^2.0.1"
|
||||
|
||||
bl@^1.0.0:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
|
||||
|
@ -1658,6 +1734,19 @@ bs58@^3.1.0:
|
|||
dependencies:
|
||||
base-x "^1.1.0"
|
||||
|
||||
bs58@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a"
|
||||
dependencies:
|
||||
base-x "^3.0.2"
|
||||
|
||||
bs58check@<3.0.0, bs58check@^2.0.0, bs58check@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.1.tgz#8a5d0e587af97b784bf9cbf1b29f454d82bc0222"
|
||||
dependencies:
|
||||
bs58 "^4.0.0"
|
||||
create-hash "^1.1.0"
|
||||
|
||||
bs58check@^1.0.8:
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-1.3.4.tgz#c52540073749117714fa042c3047eb8f9151cbf8"
|
||||
|
@ -1735,6 +1824,10 @@ buffer@^4.3.0:
|
|||
ieee754 "^1.1.4"
|
||||
isarray "^1.0.0"
|
||||
|
||||
bufferview@~1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/bufferview/-/bufferview-1.0.1.tgz#7afd74a45f937fa422a1d338c08bbfdc76cd725d"
|
||||
|
||||
builder-util-runtime@4.2.1, builder-util-runtime@^4.2.1, builder-util-runtime@~4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-4.2.1.tgz#0caa358f1331d70680010141ca591952b69b35bc"
|
||||
|
@ -1790,6 +1883,13 @@ builtin-status-codes@^3.0.0:
|
|||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
|
||||
|
||||
bytebuffer-old-fixed-webpack@3.5.6:
|
||||
version "3.5.6"
|
||||
resolved "https://registry.yarnpkg.com/bytebuffer-old-fixed-webpack/-/bytebuffer-old-fixed-webpack-3.5.6.tgz#5adc419c6a9b4692f217206703ec7431c759aa3f"
|
||||
dependencies:
|
||||
bufferview "~1"
|
||||
long "~2 >=2.2.3"
|
||||
|
||||
bytes@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
|
||||
|
@ -1916,6 +2016,12 @@ caseless@~0.12.0:
|
|||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||
|
||||
cashaddrjs@^0.2.7:
|
||||
version "0.2.8"
|
||||
resolved "https://registry.yarnpkg.com/cashaddrjs/-/cashaddrjs-0.2.8.tgz#74003dd4beb55c6328c1a13117d4093f7becb580"
|
||||
dependencies:
|
||||
big-integer "^1.6.26"
|
||||
|
||||
caw@^1.0.1:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/caw/-/caw-1.2.0.tgz#ffb226fe7efc547288dc62ee3e97073c212d1034"
|
||||
|
@ -2135,6 +2241,10 @@ cli-spinners@^0.1.2:
|
|||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c"
|
||||
|
||||
cli-spinners@^1.0.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.3.1.tgz#002c1990912d0d59580c93bd36c056de99e4259a"
|
||||
|
||||
cli-table@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23"
|
||||
|
@ -2325,6 +2435,10 @@ colors@~1.1.2:
|
|||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
|
||||
|
||||
colour@latest:
|
||||
version "0.7.1"
|
||||
resolved "https://registry.yarnpkg.com/colour/-/colour-0.7.1.tgz#9cb169917ec5d12c0736d3e8685746df1cadf778"
|
||||
|
||||
combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818"
|
||||
|
@ -2587,7 +2701,7 @@ create-hash@^1.1.0, create-hash@^1.1.1, create-hash@^1.1.2:
|
|||
ripemd160 "^2.0.1"
|
||||
sha.js "^2.4.0"
|
||||
|
||||
create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
|
||||
create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.3, create-hmac@^1.1.4:
|
||||
version "1.1.7"
|
||||
resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff"
|
||||
dependencies:
|
||||
|
@ -2878,7 +2992,7 @@ dateformat@^3.0.2:
|
|||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
|
||||
|
||||
debug@2.6.9, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8:
|
||||
debug@2.6.9, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3, debug@^2.5.1, debug@^2.6.3, debug@^2.6.8:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
dependencies:
|
||||
|
@ -3358,6 +3472,13 @@ ecc-jsbn@~0.1.1:
|
|||
dependencies:
|
||||
jsbn "~0.1.0"
|
||||
|
||||
ecurve@^1.0.0, ecurve@^1.0.2, ecurve@^1.0.3:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/ecurve/-/ecurve-1.0.6.tgz#dfdabbb7149f8d8b78816be5a7d5b83fcf6de797"
|
||||
dependencies:
|
||||
bigi "^1.1.0"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
editions@^1.3.3:
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b"
|
||||
|
@ -3506,6 +3627,21 @@ electron-publish@20.13.2:
|
|||
lazy-val "^1.0.3"
|
||||
mime "^2.3.1"
|
||||
|
||||
electron-rebuild@1.7.3:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/electron-rebuild/-/electron-rebuild-1.7.3.tgz#24ae06ad9dd61cb7e4d688961f49118c40a110eb"
|
||||
dependencies:
|
||||
colors "^1.1.2"
|
||||
debug "^2.6.3"
|
||||
detect-libc "^1.0.3"
|
||||
fs-extra "^3.0.1"
|
||||
node-abi "^2.0.0"
|
||||
node-gyp "^3.6.0"
|
||||
ora "^1.2.0"
|
||||
rimraf "^2.6.1"
|
||||
spawn-rx "^2.0.10"
|
||||
yargs "^7.0.2"
|
||||
|
||||
electron-to-chromium@^1.2.7:
|
||||
version "1.3.42"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.42.tgz#95c33bf01d0cc405556aec899fe61fd4d76ea0f9"
|
||||
|
@ -3844,6 +3980,10 @@ events@^1.0.0:
|
|||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
|
||||
|
||||
events@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/events/-/events-2.0.0.tgz#cbbb56bf3ab1ac18d71c43bb32c86255062769f2"
|
||||
|
||||
evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02"
|
||||
|
@ -4461,6 +4601,14 @@ fs-extra@^1.0.0:
|
|||
jsonfile "^2.1.0"
|
||||
klaw "^1.0.0"
|
||||
|
||||
fs-extra@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-3.0.1.tgz#3794f378c58b342ea7dbbb23095109c4b3b62291"
|
||||
dependencies:
|
||||
graceful-fs "^4.1.2"
|
||||
jsonfile "^3.0.0"
|
||||
universalify "^0.1.0"
|
||||
|
||||
fs-extra@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-6.0.1.tgz#8abc128f7946e310135ddc93b98bddb410e7a34b"
|
||||
|
@ -6538,6 +6686,12 @@ jsonfile@^2.1.0:
|
|||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
jsonfile@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66"
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
jsonfile@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
||||
|
@ -6679,13 +6833,6 @@ lcov-parse@^0.0.10:
|
|||
version "0.0.10"
|
||||
resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-0.0.10.tgz#1b0b8ff9ac9c7889250582b70b71315d9da6d9a3"
|
||||
|
||||
ledgerco@1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ledgerco/-/ledgerco-1.2.1.tgz#a1fdefd7aaf5a8857bf9b47887f5465bdcdf0602"
|
||||
dependencies:
|
||||
node-hid "0.6.0"
|
||||
u2f-api "0.2.7"
|
||||
|
||||
left-pad@^1.2.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e"
|
||||
|
@ -7021,6 +7168,10 @@ loglevelnext@^1.0.1:
|
|||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/loglevelnext/-/loglevelnext-1.0.4.tgz#0d991d9998180991dac8bd81e73a596a8720a645"
|
||||
|
||||
"long@~2 >=2.2.3":
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/long/-/long-2.4.0.tgz#9fa180bb1d9500cdc29c4156766a1995e1f4524f"
|
||||
|
||||
longest@^1.0.0, longest@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
|
||||
|
@ -7202,6 +7353,10 @@ merge@^1.1.3:
|
|||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da"
|
||||
|
||||
merkle-lib@^2.0.10:
|
||||
version "2.0.10"
|
||||
resolved "https://registry.yarnpkg.com/merkle-lib/-/merkle-lib-2.0.10.tgz#82b8dbae75e27a7785388b73f9d7725d0f6f3326"
|
||||
|
||||
methods@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||
|
@ -7454,6 +7609,34 @@ mycrypto-shepherd@1.4.0:
|
|||
remote-redux-devtools "^0.5.12"
|
||||
url-search-params "^0.10.0"
|
||||
|
||||
mycrypto-trezor-link@1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mycrypto-trezor-link/-/mycrypto-trezor-link-1.5.1.tgz#1d0b13bd854022dcd9960062d04f8d9e7b4c67c0"
|
||||
dependencies:
|
||||
bigi "^1.4.1"
|
||||
bitcoinjs-lib-zcash "^3.0.0"
|
||||
ecurve "^1.0.3"
|
||||
json-stable-stringify "^1.0.1"
|
||||
node-fetch "^1.6.0"
|
||||
object.values "^1.0.3"
|
||||
protobufjs-old-fixed-webpack "3.8.5"
|
||||
semver-compare "^1.0.0"
|
||||
whatwg-fetch "0.11.0"
|
||||
|
||||
mycrypto-trezor.js@6.17.5:
|
||||
version "6.17.5"
|
||||
resolved "https://registry.yarnpkg.com/mycrypto-trezor.js/-/mycrypto-trezor.js-6.17.5.tgz#f92cf11e614aa813814f8e444f459f02bf7ce906"
|
||||
dependencies:
|
||||
bchaddrjs "^0.2.1"
|
||||
bitcoinjs-lib-zcash "^3.3.2"
|
||||
ecurve "^1.0.2"
|
||||
mycrypto-trezor-link "1.5.1"
|
||||
node-fetch "^1.6.0"
|
||||
randombytes "^2.0.1"
|
||||
semver-compare "1.0.0"
|
||||
unorm "^1.3.3"
|
||||
whatwg-fetch "0.11.0"
|
||||
|
||||
nan@^2.0.5, nan@^2.0.8, nan@^2.10.0, nan@^2.2.1, nan@^2.3.0, nan@^2.6.2:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
|
||||
|
@ -7506,6 +7689,12 @@ no-case@^2.2.0:
|
|||
dependencies:
|
||||
lower-case "^1.1.1"
|
||||
|
||||
node-abi@^2.0.0:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.4.1.tgz#7628c4d4ec4e9cd3764ceb3652f36b2e7f8d4923"
|
||||
dependencies:
|
||||
semver "^5.4.1"
|
||||
|
||||
node-abi@^2.2.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.3.0.tgz#f3d554d6ac72a9ee16f0f4dc9548db7c08de4986"
|
||||
|
@ -7516,7 +7705,7 @@ node-dir@0.1.8:
|
|||
version "0.1.8"
|
||||
resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.8.tgz#55fb8deb699070707fb67f91a460f0448294c77d"
|
||||
|
||||
node-fetch@^1.0.1:
|
||||
node-fetch@^1.0.1, node-fetch@^1.6.0:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
|
||||
dependencies:
|
||||
|
@ -7541,9 +7730,26 @@ node-gyp@^3.3.1:
|
|||
tar "^2.0.0"
|
||||
which "1"
|
||||
|
||||
node-hid@0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/node-hid/-/node-hid-0.6.0.tgz#336bb29a2c4efdb67c5cb83eedbe4a4b1443ee52"
|
||||
node-gyp@^3.6.0:
|
||||
version "3.7.0"
|
||||
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.7.0.tgz#789478e8f6c45e277aa014f3e28f958f286f9203"
|
||||
dependencies:
|
||||
fstream "^1.0.0"
|
||||
glob "^7.0.3"
|
||||
graceful-fs "^4.1.2"
|
||||
mkdirp "^0.5.0"
|
||||
nopt "2 || 3"
|
||||
npmlog "0 || 1 || 2 || 3 || 4"
|
||||
osenv "0"
|
||||
request ">=2.9.0 <2.82.0"
|
||||
rimraf "2"
|
||||
semver "~5.3.0"
|
||||
tar "^2.0.0"
|
||||
which "1"
|
||||
|
||||
node-hid@0.7.2, node-hid@^0.7.2:
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/node-hid/-/node-hid-0.7.2.tgz#15025cdea2e9756aca2de7266529996d40e52c56"
|
||||
dependencies:
|
||||
bindings "^1.3.0"
|
||||
nan "^2.6.2"
|
||||
|
@ -7892,7 +8098,7 @@ object.pick@^1.3.0:
|
|||
dependencies:
|
||||
isobject "^3.0.1"
|
||||
|
||||
object.values@^1.0.4:
|
||||
object.values@^1.0.3, object.values@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.0.4.tgz#e524da09b4f66ff05df457546ec72ac99f13069a"
|
||||
dependencies:
|
||||
|
@ -7949,6 +8155,10 @@ optipng-bin@^3.0.0:
|
|||
bin-wrapper "^3.0.0"
|
||||
logalot "^2.0.0"
|
||||
|
||||
optjs@latest:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/optjs/-/optjs-3.2.2.tgz#69a6ce89c442a44403141ad2f9b370bd5bb6f4ee"
|
||||
|
||||
ora@^0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/ora/-/ora-0.2.3.tgz#37527d220adcd53c39b73571d754156d5db657a4"
|
||||
|
@ -7958,6 +8168,15 @@ ora@^0.2.3:
|
|||
cli-spinners "^0.1.2"
|
||||
object-assign "^4.0.1"
|
||||
|
||||
ora@^1.2.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/ora/-/ora-1.4.0.tgz#884458215b3a5d4097592285f93321bb7a79e2e5"
|
||||
dependencies:
|
||||
chalk "^2.1.0"
|
||||
cli-cursor "^2.1.0"
|
||||
cli-spinners "^1.0.1"
|
||||
log-symbols "^2.1.0"
|
||||
|
||||
ordered-read-streams@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz#7137e69b3298bb342247a1bbee3881c80e2fd78b"
|
||||
|
@ -8757,6 +8976,13 @@ proto-list@~1.2.1:
|
|||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
|
||||
|
||||
protobufjs-old-fixed-webpack@3.8.5:
|
||||
version "3.8.5"
|
||||
resolved "https://registry.yarnpkg.com/protobufjs-old-fixed-webpack/-/protobufjs-old-fixed-webpack-3.8.5.tgz#5813c1af9f1d136bbf39f4f9f2e6f3e43c389d06"
|
||||
dependencies:
|
||||
ascli "~0.3"
|
||||
bytebuffer-old-fixed-webpack "3.5.6"
|
||||
|
||||
proxy-addr@~2.0.2:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341"
|
||||
|
@ -8828,6 +9054,12 @@ punycode@^2.1.0:
|
|||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d"
|
||||
|
||||
pushdata-bitcoin@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz#15931d3cd967ade52206f523aa7331aef7d43af7"
|
||||
dependencies:
|
||||
bitcoin-ops "^1.3.0"
|
||||
|
||||
q@^1.1.2:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
||||
|
@ -9616,7 +9848,7 @@ request@2, request@^2.45.0, request@^2.65.0, request@^2.79.0, request@^2.81.0, r
|
|||
tunnel-agent "^0.6.0"
|
||||
uuid "^3.1.0"
|
||||
|
||||
request@2.81.0:
|
||||
request@2.81.0, "request@>=2.9.0 <2.82.0":
|
||||
version "2.81.0"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
|
||||
dependencies:
|
||||
|
@ -9824,6 +10056,12 @@ rx@2.3.24:
|
|||
version "2.3.24"
|
||||
resolved "https://registry.yarnpkg.com/rx/-/rx-2.3.24.tgz#14f950a4217d7e35daa71bbcbe58eff68ea4b2b7"
|
||||
|
||||
rxjs@^5.1.1:
|
||||
version "5.5.11"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.11.tgz#f733027ca43e3bec6b994473be4ab98ad43ced87"
|
||||
dependencies:
|
||||
symbol-observable "1.0.1"
|
||||
|
||||
rxjs@^5.4.2, rxjs@^5.5.2:
|
||||
version "5.5.10"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.10.tgz#fde02d7a614f6c8683d0d1957827f492e09db045"
|
||||
|
@ -9979,6 +10217,10 @@ seek-bzip@^1.0.3, seek-bzip@^1.0.5:
|
|||
dependencies:
|
||||
commander "~2.8.1"
|
||||
|
||||
semver-compare@1.0.0, semver-compare@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
|
||||
|
||||
semver-diff@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
|
||||
|
@ -10310,6 +10552,14 @@ spawn-command@^0.0.2-1:
|
|||
version "0.0.2-1"
|
||||
resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0"
|
||||
|
||||
spawn-rx@^2.0.10:
|
||||
version "2.0.12"
|
||||
resolved "https://registry.yarnpkg.com/spawn-rx/-/spawn-rx-2.0.12.tgz#b6285294499426089beea0c3c1ec32d7fc57a376"
|
||||
dependencies:
|
||||
debug "^2.5.1"
|
||||
lodash.assign "^4.2.0"
|
||||
rxjs "^5.1.1"
|
||||
|
||||
spdx-correct@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82"
|
||||
|
@ -11170,6 +11420,10 @@ typedarray@^0.0.6:
|
|||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
|
||||
typeforce@^1.11.3:
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.12.0.tgz#ca40899919f1466d7819e37be039406beb912a2e"
|
||||
|
||||
types-rlp@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/types-rlp/-/types-rlp-0.0.1.tgz#cb9a2fe70bbdabda3c2c491b21bc04c8bd917f9b"
|
||||
|
@ -11533,6 +11787,12 @@ value-equal@^0.4.0:
|
|||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7"
|
||||
|
||||
varuint-bitcoin@^1.0.4:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/varuint-bitcoin/-/varuint-bitcoin-1.1.0.tgz#7a343f50537607af6a3059312b9782a170894540"
|
||||
dependencies:
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
vary@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||
|
@ -11843,6 +12103,10 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3:
|
|||
dependencies:
|
||||
iconv-lite "0.4.19"
|
||||
|
||||
whatwg-fetch@0.11.0:
|
||||
version "0.11.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-0.11.0.tgz#46b1d18d0aa99955971ef1a2f5aac506add28815"
|
||||
|
||||
whatwg-fetch@2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
|
||||
|
@ -11897,6 +12161,12 @@ widest-line@^2.0.0:
|
|||
dependencies:
|
||||
string-width "^2.1.1"
|
||||
|
||||
wif@^2.0.1:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/wif/-/wif-2.0.6.tgz#08d3f52056c66679299726fade0d432ae74b4704"
|
||||
dependencies:
|
||||
bs58check "<3.0.0"
|
||||
|
||||
window-or-global@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/window-or-global/-/window-or-global-1.0.1.tgz#dbe45ba2a291aabc56d62cf66c45b7fa322946de"
|
||||
|
@ -12132,7 +12402,7 @@ yargs@^3.31.0:
|
|||
window-size "^0.1.4"
|
||||
y18n "^3.2.0"
|
||||
|
||||
yargs@^7.0.0:
|
||||
yargs@^7.0.0, yargs@^7.0.2:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8"
|
||||
dependencies:
|
||||
|
|
Loading…
Reference in New Issue