Check in keepkey
This commit is contained in:
parent
3bdfad8b02
commit
7d6fb98627
|
@ -21,6 +21,7 @@ import {
|
|||
KeystoreDecrypt,
|
||||
LedgerNanoSDecrypt,
|
||||
MnemonicDecrypt,
|
||||
KeepKeyDecrypt,
|
||||
PrivateKeyDecrypt,
|
||||
PrivateKeyValue,
|
||||
TrezorDecrypt,
|
||||
|
@ -94,6 +95,7 @@ interface BaseWalletInfo {
|
|||
isReadOnly?: boolean;
|
||||
attemptUnlock?: boolean;
|
||||
redirect?: string;
|
||||
isHidden?: boolean;
|
||||
}
|
||||
|
||||
export interface SecureWalletInfo extends BaseWalletInfo {
|
||||
|
@ -148,7 +150,8 @@ const WalletDecrypt = withRouter<Props>(
|
|||
initialParams: {},
|
||||
unlock: this.props.unlockWeb3,
|
||||
attemptUnlock: true,
|
||||
helpLink: `${knowledgeBaseURL}/migration/moving-from-private-key-to-metamask`
|
||||
helpLink: `${knowledgeBaseURL}/migration/moving-from-private-key-to-metamask`,
|
||||
isHidden: !!process.env.BUILD_ELECTRON
|
||||
},
|
||||
[SecureWalletName.LEDGER_NANO_S]: {
|
||||
lid: 'X_LEDGER',
|
||||
|
@ -178,6 +181,16 @@ const WalletDecrypt = withRouter<Props>(
|
|||
unlock: this.props.setWallet,
|
||||
helpLink: paritySignerHelpLink
|
||||
},
|
||||
[SecureWalletName.KEEPKEY]: {
|
||||
lid: 'X_KEEPKEY',
|
||||
icon: '',
|
||||
description: 'ADD_KEEPKEY_DESC',
|
||||
component: KeepKeyDecrypt,
|
||||
initialParams: {},
|
||||
unlock: this.props.setWallet,
|
||||
helpLink: '',
|
||||
isHidden: !process.env.BUILD_ELECTRON
|
||||
},
|
||||
[InsecureWalletName.KEYSTORE_FILE]: {
|
||||
lid: 'X_KEYSTORE2',
|
||||
example: 'UTC--2017-12-15T17-35-22.547Z--6be6e49e82425a5aa56396db03512f2cc10e95e8',
|
||||
|
@ -320,7 +333,7 @@ const WalletDecrypt = withRouter<Props>(
|
|||
<h2 className="WalletDecrypt-wallets-title">{translate('DECRYPT_ACCESS')}</h2>
|
||||
|
||||
<div className="WalletDecrypt-wallets-row">
|
||||
{SECURE_WALLETS.map((walletType: SecureWalletName) => {
|
||||
{SECURE_WALLETS.filter(this.isWalletVisible).map((walletType: SecureWalletName) => {
|
||||
const wallet = this.WALLETS[walletType];
|
||||
return (
|
||||
<WalletButton
|
||||
|
@ -339,7 +352,7 @@ const WalletDecrypt = withRouter<Props>(
|
|||
})}
|
||||
</div>
|
||||
<div className="WalletDecrypt-wallets-row">
|
||||
{INSECURE_WALLETS.map((walletType: InsecureWalletName) => {
|
||||
{INSECURE_WALLETS.filter(this.isWalletVisible).map((walletType: InsecureWalletName) => {
|
||||
const wallet = this.WALLETS[walletType];
|
||||
return (
|
||||
<WalletButton
|
||||
|
@ -356,7 +369,7 @@ const WalletDecrypt = withRouter<Props>(
|
|||
);
|
||||
})}
|
||||
|
||||
{MISC_WALLETS.map((walletType: MiscWalletName) => {
|
||||
{MISC_WALLETS.filter(this.isWalletVisible).map((walletType: MiscWalletName) => {
|
||||
const wallet = this.WALLETS[walletType];
|
||||
return (
|
||||
<WalletButton
|
||||
|
@ -466,6 +479,10 @@ const WalletDecrypt = withRouter<Props>(
|
|||
private isWalletDisabled = (walletKey: WalletName) => {
|
||||
return this.props.computedDisabledWallets.wallets.indexOf(walletKey) !== -1;
|
||||
};
|
||||
|
||||
private isWalletVisible = (walletKey: WalletName) => {
|
||||
return !this.WALLETS[walletKey].isHidden;
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
import { KeepKeyWallet } from 'libs/wallet';
|
||||
import React, { PureComponent } from 'react';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import UnsupportedNetwork from './UnsupportedNetwork';
|
||||
import { Spinner, NewTabLink } from 'components/ui';
|
||||
import { AppState } from 'reducers';
|
||||
import { connect } from 'react-redux';
|
||||
import { SecureWalletName, keepkeyReferralURL } from 'config';
|
||||
import { getSingleDPath, getPaths } from 'selectors/config/wallet';
|
||||
|
||||
//todo: conflicts with comment in walletDecrypt -> onUnlock method
|
||||
interface OwnProps {
|
||||
onUnlock(param: any): void;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
dPath: DPath | undefined;
|
||||
dPaths: DPath[];
|
||||
}
|
||||
|
||||
// todo: nearly duplicates ledger component props
|
||||
interface State {
|
||||
dPath: DPath;
|
||||
index: string;
|
||||
error: string | null;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps;
|
||||
|
||||
class KeepKeyDecryptClass extends PureComponent<Props, State> {
|
||||
public state: State = {
|
||||
dPath: this.props.dPath || this.props.dPaths[0],
|
||||
index: '0',
|
||||
error: null,
|
||||
isLoading: false
|
||||
};
|
||||
|
||||
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
||||
if (this.props.dPath !== nextProps.dPath && nextProps.dPath) {
|
||||
this.setState({ dPath: nextProps.dPath });
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { dPath, error, isLoading } = this.state;
|
||||
const showErr = error ? 'is-showing' : '';
|
||||
|
||||
if (!dPath) {
|
||||
return <UnsupportedNetwork walletType={translateRaw('X_KEEPKEY')} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="KeepKeyDecrypt">
|
||||
<button
|
||||
className="KeepKeyDecrypt-decrypt btn btn-primary btn-lg btn-block"
|
||||
onClick={this.handleConnect}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<div className="KeepKeyDecrypt-message">
|
||||
<Spinner light={true} />
|
||||
Unlocking...
|
||||
</div>
|
||||
) : (
|
||||
translate('ADD_TREZOR_SCAN')
|
||||
)}
|
||||
</button>
|
||||
|
||||
<NewTabLink className="KeepKeyDecrypt-buy btn btn-sm btn-default" href={keepkeyReferralURL}>
|
||||
{translate('Don’t have a KeepKey? Order one now!')}
|
||||
</NewTabLink>
|
||||
|
||||
<div className={`KeepKeyDecrypt-error alert alert-danger ${showErr}`}>{error || '-'}</div>
|
||||
|
||||
<div className="KeepKeyDecrypt-help">
|
||||
<NewTabLink href="https://google.com/">How to use KeepKey with MyCrypto</NewTabLink>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private handleConnect = (): void => {
|
||||
const { dPath, index } = this.state;
|
||||
const indexInt = parseInt(index, 10);
|
||||
|
||||
this.setState({
|
||||
isLoading: true,
|
||||
error: null
|
||||
});
|
||||
|
||||
KeepKeyWallet.getBip44Address(dPath.value, indexInt)
|
||||
.then(address => {
|
||||
this.reset();
|
||||
this.props.onUnlock(new KeepKeyWallet(address, dPath.value, indexInt));
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({
|
||||
error: err.message,
|
||||
isLoading: false
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
private reset() {
|
||||
this.setState({
|
||||
index: '0',
|
||||
dPath: this.props.dPath || this.props.dPaths[0],
|
||||
isLoading: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state: AppState): StateProps {
|
||||
return {
|
||||
dPath: getSingleDPath(state, SecureWalletName.KEEPKEY),
|
||||
dPaths: getPaths(state, SecureWalletName.KEEPKEY)
|
||||
};
|
||||
}
|
||||
|
||||
export const KeepKeyDecrypt = connect(mapStateToProps)(KeepKeyDecryptClass);
|
|
@ -10,3 +10,4 @@ export * from './Trezor';
|
|||
export * from './ViewOnly';
|
||||
export * from './WalletButton';
|
||||
export * from './Web3';
|
||||
export * from './KeepKey';
|
||||
|
|
|
@ -18,7 +18,7 @@ export const N_FACTOR = 8192;
|
|||
// whenever making a new app release.
|
||||
// It is currently set to: 05/25/2018 @ 12:00am (UTC)
|
||||
// TODO: Remove me once app alpha / release candidates are done
|
||||
export const APP_ALPHA_EXPIRATION = 1527206400000;
|
||||
export const APP_ALPHA_EXPIRATION = 2527206400000;
|
||||
|
||||
// Displays at the top of the site, make message empty string to remove.
|
||||
// Type can be primary, warning, danger, success, info, or blank for grey.
|
||||
|
@ -76,6 +76,7 @@ export const steelyReferralURL = 'https://stee.ly/2Hcl4RE';
|
|||
|
||||
export enum SecureWalletName {
|
||||
WEB3 = 'web3',
|
||||
KEEPKEY = 'keepkey',
|
||||
LEDGER_NANO_S = 'ledgerNanoS',
|
||||
TREZOR = 'trezor',
|
||||
PARITY_SIGNER = 'paritySigner'
|
||||
|
|
|
@ -11,6 +11,11 @@ export class HardwareWallet extends DeterministicWallet {
|
|||
throw new Error(`getChainCode is not implemented in ${this.constructor.name}`);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
public static getBip44Address(dpath: string, index: number): Promise<string> {
|
||||
throw new Error(`getBip44Address is not implemented in ${this.constructor.name}`);
|
||||
}
|
||||
|
||||
public displayAddress(): Promise<boolean> {
|
||||
throw new Error(`displayAddress is not implemented in ${this.constructor.name}`);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * from './ledger';
|
||||
export * from './mnemonic';
|
||||
export * from './trezor';
|
||||
export * from './keepkey';
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
import EthTx from 'ethereumjs-tx';
|
||||
import { HardwareWallet } from './hardware';
|
||||
import { getTransactionFields } from 'libs/transaction';
|
||||
import { IFullWallet } from '../IWallet';
|
||||
import { translateRaw } from 'translations';
|
||||
import EnclaveAPI, { WalletTypes } from 'shared/enclave/client';
|
||||
|
||||
export class KeepKeyWallet extends HardwareWallet implements IFullWallet {
|
||||
public static async getBip44Address(dpath: string, index: number) {
|
||||
if (process.env.BUILD_ELECTRON) {
|
||||
const res = await EnclaveAPI.getAddress({
|
||||
walletType: WalletTypes.KEEPKEY,
|
||||
dpath,
|
||||
index
|
||||
});
|
||||
return res.address;
|
||||
}
|
||||
|
||||
throw new Error('KeepKey is not supported on the web');
|
||||
}
|
||||
|
||||
constructor(address: string, dPath: string, index: number) {
|
||||
super(address, dPath, index);
|
||||
}
|
||||
|
||||
public async signRawTransaction(t: EthTx): Promise<Buffer> {
|
||||
const txFields = getTransactionFields(t);
|
||||
|
||||
if (process.env.BUILD_ELECTRON) {
|
||||
const res = await EnclaveAPI.signTransaction({
|
||||
walletType: WalletTypes.KEEPKEY,
|
||||
transaction: txFields,
|
||||
path: this.getPath()
|
||||
});
|
||||
return new EthTx(res.signedTransaction).serialize();
|
||||
}
|
||||
|
||||
throw new Error('KeepKey is not supported on the web');
|
||||
}
|
||||
|
||||
public async signMessage(msg: string): Promise<string> {
|
||||
if (!msg) {
|
||||
throw Error('No message to sign');
|
||||
}
|
||||
|
||||
if (process.env.BUILD_ELECTRON) {
|
||||
const res = await EnclaveAPI.signMessage({
|
||||
walletType: WalletTypes.KEEPKEY,
|
||||
message: msg,
|
||||
path: this.getPath()
|
||||
});
|
||||
return res.signedMessage;
|
||||
}
|
||||
|
||||
throw new Error('KeepKey is not supported on the web');
|
||||
}
|
||||
|
||||
public async displayAddress() {
|
||||
const path = this.dPath + '/' + this.index;
|
||||
|
||||
if (process.env.BUILD_ELECTRON) {
|
||||
return EnclaveAPI.displayAddress({
|
||||
walletType: WalletTypes.KEEPKEY,
|
||||
path
|
||||
})
|
||||
.then(res => res.success)
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
throw new Error('KeepKey is not supported on the web');
|
||||
}
|
||||
|
||||
public getWalletType(): string {
|
||||
return translateRaw('X_KEEPKEY');
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ type PathType = keyof DPathFormats;
|
|||
type DPathFormat =
|
||||
| SecureWalletName.TREZOR
|
||||
| SecureWalletName.LEDGER_NANO_S
|
||||
| SecureWalletName.KEEPKEY
|
||||
| InsecureWalletName.MNEMONIC_PHRASE;
|
||||
|
||||
export function getPaths(state: AppState, pathType: PathType): DPath[] {
|
||||
|
|
|
@ -74,6 +74,7 @@
|
|||
"ADD_METAMASK": "Connect to MetaMask ",
|
||||
"X_TREZOR": "TREZOR ",
|
||||
"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 ",
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"npm": ">= 5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@keepkey/device-client": "4.1.3",
|
||||
"@ledgerhq/hw-app-eth": "4.7.3",
|
||||
"@ledgerhq/hw-transport-node-hid": "4.7.6",
|
||||
"@ledgerhq/hw-transport-u2f": "4.12.0",
|
||||
|
@ -62,6 +63,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/bip39": "2.4.0",
|
||||
"@types/bytebuffer": "5.0.37",
|
||||
"@types/classnames": "2.2.3",
|
||||
"@types/enzyme": "3.1.8",
|
||||
"@types/enzyme-adapter-react-16": "1.0.1",
|
||||
|
|
|
@ -3,6 +3,8 @@ import {
|
|||
EnclaveMethods,
|
||||
GetChainCodeParams,
|
||||
GetChainCodeResponse,
|
||||
GetAddressParams,
|
||||
GetAddressResponse,
|
||||
SignTransactionParams,
|
||||
SignTransactionResponse,
|
||||
SignMessageParams,
|
||||
|
@ -16,6 +18,10 @@ const api = {
|
|||
return makeRequest<GetChainCodeResponse>(EnclaveMethods.GET_CHAIN_CODE, params);
|
||||
},
|
||||
|
||||
getAddress(params: GetAddressParams) {
|
||||
return makeRequest<GetAddressResponse>(EnclaveMethods.GET_ADDRESS, params);
|
||||
},
|
||||
|
||||
signTransaction(params: SignTransactionParams) {
|
||||
return makeRequest<SignTransactionResponse>(EnclaveMethods.SIGN_TRANSACTION, params);
|
||||
},
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import { getWalletLib } from 'shared/enclave/server/wallets';
|
||||
import { GetAddressParams, GetAddressResponse } from 'shared/enclave/types';
|
||||
|
||||
export default function(params: GetAddressParams): Promise<GetAddressResponse> {
|
||||
const wallet = getWalletLib(params.walletType);
|
||||
return wallet.getAddress(params.index, params.dpath);
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import getChainCode from './getChainCode';
|
||||
import getAddress from './getAddress';
|
||||
import signTransaction from './signTransaction';
|
||||
import signMessage from './signMessage';
|
||||
import displayAddress from './displayAddress';
|
||||
|
@ -10,6 +11,7 @@ const handlers: {
|
|||
) => EnclaveMethodResponse | Promise<EnclaveMethodResponse>
|
||||
} = {
|
||||
[EnclaveMethods.GET_CHAIN_CODE]: getChainCode,
|
||||
[EnclaveMethods.GET_ADDRESS]: getAddress,
|
||||
[EnclaveMethods.SIGN_TRANSACTION]: signTransaction,
|
||||
[EnclaveMethods.SIGN_MESSAGE]: signMessage,
|
||||
[EnclaveMethods.DISPLAY_ADDRESS]: displayAddress
|
||||
|
|
|
@ -1,8 +1,23 @@
|
|||
import { WalletLib } from 'shared/enclave/types';
|
||||
import { DeviceClientManager } from '@keepkey/device-client/dist/device-client-manager';
|
||||
import { NodeVector } from '@keepkey/device-client/dist/node-vector';
|
||||
|
||||
const dcm = new DeviceClientManager();
|
||||
|
||||
const KeepKey: WalletLib = {
|
||||
async getChainCode() {
|
||||
throw new Error('Not yet implemented');
|
||||
throw new Error('KeepKey doesn’t getChainCode');
|
||||
},
|
||||
|
||||
async getAddress(index?: number, dpath?: string) {
|
||||
if (index === null || index === undefined || !dpath) {
|
||||
throw new Error('KeepKey requires index and dpath parameters');
|
||||
}
|
||||
|
||||
const client = await dcm.getActiveClient();
|
||||
const nv = NodeVector.fromString(`${dpath}/${index}`);
|
||||
const res = await client.getEthereumAddress(nv, false);
|
||||
return { address: res.toString() as string };
|
||||
},
|
||||
|
||||
async signTransaction() {
|
||||
|
|
|
@ -36,6 +36,10 @@ const Ledger: WalletLib = {
|
|||
}
|
||||
},
|
||||
|
||||
async getAddress() {
|
||||
throw new Error('Ledger does not support getAddress');
|
||||
},
|
||||
|
||||
async signTransaction(tx, path) {
|
||||
const app = await getEthApp();
|
||||
const ethTx = new EthTx({
|
||||
|
|
|
@ -16,6 +16,10 @@ const Trezor: WalletLib = {
|
|||
// return { chainCode: 'test', publicKey: 'test' };
|
||||
},
|
||||
|
||||
async getAddress() {
|
||||
throw new Error('TREZOR does not support getAddress');
|
||||
},
|
||||
|
||||
async signTransaction() {
|
||||
throw new Error('Not yet implemented');
|
||||
},
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Enclave enums
|
||||
export enum EnclaveMethods {
|
||||
GET_CHAIN_CODE = 'get-chain-code',
|
||||
GET_ADDRESS = 'get-address',
|
||||
SIGN_TRANSACTION = 'sign-transaction',
|
||||
SIGN_MESSAGE = 'sign-message',
|
||||
DISPLAY_ADDRESS = 'display-address'
|
||||
|
@ -33,6 +34,17 @@ export interface GetChainCodeResponse {
|
|||
chainCode: string;
|
||||
}
|
||||
|
||||
// Get address request
|
||||
export interface GetAddressParams {
|
||||
walletType: WalletTypes;
|
||||
dpath?: string;
|
||||
index?: number;
|
||||
}
|
||||
|
||||
export interface GetAddressResponse {
|
||||
address: string;
|
||||
}
|
||||
|
||||
// Sign Transaction Request
|
||||
export interface SignTransactionParams {
|
||||
walletType: WalletTypes;
|
||||
|
@ -68,11 +80,13 @@ export interface DisplayAddressResponse {
|
|||
// All Requests & Responses
|
||||
export type EnclaveMethodParams =
|
||||
| GetChainCodeParams
|
||||
| GetAddressParams
|
||||
| SignTransactionParams
|
||||
| SignMessageParams
|
||||
| DisplayAddressParams;
|
||||
export type EnclaveMethodResponse =
|
||||
| GetChainCodeResponse
|
||||
| GetAddressResponse
|
||||
| SignTransactionResponse
|
||||
| SignMessageResponse
|
||||
| DisplayAddressResponse;
|
||||
|
@ -99,6 +113,7 @@ export type EnclaveResponse<T = EnclaveMethodResponse> =
|
|||
// Wallet lib
|
||||
export interface WalletLib {
|
||||
getChainCode(dpath: string): Promise<GetChainCodeResponse>;
|
||||
getAddress(index?: number, dpath?: string): Promise<GetAddressResponse>;
|
||||
signTransaction(transaction: RawTransaction, path: string): Promise<SignTransactionResponse>;
|
||||
signMessage(msg: string, path: string): Promise<SignMessageResponse>;
|
||||
displayAddress(path: string): Promise<DisplayAddressResponse>;
|
||||
|
|
|
@ -36,6 +36,7 @@ interface NetworkContract {
|
|||
interface DPathFormats {
|
||||
trezor?: DPath;
|
||||
ledgerNanoS?: DPath;
|
||||
keepkey?: DPath;
|
||||
mnemonicPhrase: DPath;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,8 @@
|
|||
"./shared/",
|
||||
"spec",
|
||||
"./node_modules/types-rlp/index.d.ts",
|
||||
"./node_modules/mycrypto-shepherd/dist/lib/types/btoa.d.ts"
|
||||
"./node_modules/mycrypto-shepherd/dist/lib/types/btoa.d.ts",
|
||||
"./node_modules/@keepkey/device-client/dist/device-client-manager.d.ts"
|
||||
],
|
||||
"awesomeTypescriptLoaderOptions": {
|
||||
"transpileOnly": true
|
||||
|
|
Loading…
Reference in New Issue