From d7373a5c0455ef81a5a1852123136e4724f13e36 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Tue, 6 Mar 2018 20:25:21 -0500 Subject: Add ledger-node package as optional dependency --- packages/subproviders/CHANGELOG.md | 4 + packages/subproviders/package.json | 3 + packages/subproviders/src/globals.d.ts | 10 +- .../test/integration/ledger_subprovider_test.ts | 375 +++++++++++---------- 4 files changed, 208 insertions(+), 184 deletions(-) diff --git a/packages/subproviders/CHANGELOG.md b/packages/subproviders/CHANGELOG.md index 7e1e006e3..b7247748e 100644 --- a/packages/subproviders/CHANGELOG.md +++ b/packages/subproviders/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## v0.7.0 - _March 6, 2018_ + + * Updated legerco packages. Removed node-hid packages as a dependency. (#437) + ## v0.6.0 - _March 4, 2018_ * Move web3 types from being a devDep to a dep since one cannot use this package without it (#429) diff --git a/packages/subproviders/package.json b/packages/subproviders/package.json index 9e18f95e1..a3e865d24 100644 --- a/packages/subproviders/package.json +++ b/packages/subproviders/package.json @@ -54,5 +54,8 @@ "types-ethereumjs-util": "0xProject/types-ethereumjs-util", "typescript": "2.7.1", "webpack": "^3.1.0" + }, + "optionalDependencies": { + "@ledgerhq/hw-transport-node-hid": "^4.3.0" } } diff --git a/packages/subproviders/src/globals.d.ts b/packages/subproviders/src/globals.d.ts index 238d9be78..e258ef947 100644 --- a/packages/subproviders/src/globals.d.ts +++ b/packages/subproviders/src/globals.d.ts @@ -39,6 +39,7 @@ interface LedgerTransport { declare module '@ledgerhq/hw-app-eth' { class Eth { + public transport: LedgerTransport; constructor(transport: LedgerTransport); public getAddress( path: string, @@ -48,10 +49,10 @@ declare module '@ledgerhq/hw-app-eth' { public signTransaction(path: string, rawTxHex: string): Promise; public getAppConfiguration(): Promise<{ arbitraryDataEnabled: number; version: string }>; public signPersonalMessage(path: string, messageHex: string): Promise; - transport: LedgerTransport; } export default Eth; } + declare module '@ledgerhq/hw-transport-u2f' { export default class TransportU2F { public static create(): Promise; @@ -59,6 +60,13 @@ declare module '@ledgerhq/hw-transport-u2f' { } } +declare module '@ledgerhq/hw-transport-node-hid' { + export default class TransportNodeHid { + public static create(): Promise; + public close(): Promise; + } +} + // Semaphore-async-await declarations declare module 'semaphore-async-await' { class Semaphore { diff --git a/packages/subproviders/test/integration/ledger_subprovider_test.ts b/packages/subproviders/test/integration/ledger_subprovider_test.ts index 979215bbe..86faebbd3 100644 --- a/packages/subproviders/test/integration/ledger_subprovider_test.ts +++ b/packages/subproviders/test/integration/ledger_subprovider_test.ts @@ -1,190 +1,199 @@ -// import * as chai from 'chai'; -// import promisify = require('es6-promisify'); -// import * as ethUtils from 'ethereumjs-util'; -// import * as _ from 'lodash'; -// import Web3 = require('web3'); -// import Web3ProviderEngine = require('web3-provider-engine'); -// import RpcSubprovider = require('web3-provider-engine/subproviders/rpc'); +import Eth from '@ledgerhq/hw-app-eth'; +// tslint:disable-next-line:no-implicit-dependencies +import TransportNodeHid from '@ledgerhq/hw-transport-node-hid'; +import * as chai from 'chai'; +import promisify = require('es6-promisify'); +import * as ethUtils from 'ethereumjs-util'; +import * as _ from 'lodash'; +import Web3 = require('web3'); +import Web3ProviderEngine = require('web3-provider-engine'); +import RpcSubprovider = require('web3-provider-engine/subproviders/rpc'); -// import { ledgerEthereumNodeJsClientFactoryAsync, LedgerSubprovider } from '../../src'; -// import { DoneCallback } from '../../src/types'; -// import { chaiSetup } from '../chai_setup'; -// import { reportCallbackErrors } from '../utils/report_callback_errors'; +import { LedgerSubprovider } from '../../src'; +import { DoneCallback, LedgerEthereumClient } from '../../src/types'; +import { chaiSetup } from '../chai_setup'; +import { reportCallbackErrors } from '../utils/report_callback_errors'; -// chaiSetup.configure(); -// const expect = chai.expect; +chaiSetup.configure(); +const expect = chai.expect; -// const TEST_RPC_ACCOUNT_0 = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; +async function ledgerEthereumNodeJsClientFactoryAsync(): Promise { + const ledgerConnection = await TransportNodeHid.create(); + const ledgerEthClient = new Eth(ledgerConnection); + return ledgerEthClient; +} -// describe('LedgerSubprovider', () => { -// let ledgerSubprovider: LedgerSubprovider; -// const networkId: number = 42; -// before(async () => { -// ledgerSubprovider = new LedgerSubprovider({ -// networkId, -// ledgerEthereumClientFactoryAsync: ledgerEthereumNodeJsClientFactoryAsync, -// }); -// }); -// describe('direct method calls', () => { -// it('returns default number of accounts', async () => { -// const accounts = await ledgerSubprovider.getAccountsAsync(); -// expect(accounts[0]).to.not.be.an('undefined'); -// expect(accounts.length).to.be.equal(10); -// }); -// it('returns requested number of accounts', async () => { -// const numberOfAccounts = 20; -// const accounts = await ledgerSubprovider.getAccountsAsync(numberOfAccounts); -// expect(accounts[0]).to.not.be.an('undefined'); -// expect(accounts.length).to.be.equal(numberOfAccounts); -// }); -// it('signs a personal message', async () => { -// const data = ethUtils.bufferToHex(ethUtils.toBuffer('hello world')); -// const ecSignatureHex = await ledgerSubprovider.signPersonalMessageAsync(data); -// expect(ecSignatureHex.length).to.be.equal(132); -// expect(ecSignatureHex.substr(0, 2)).to.be.equal('0x'); -// }); -// it('signs a transaction', async () => { -// const tx = { -// nonce: '0x00', -// gas: '0x2710', -// to: '0x0000000000000000000000000000000000000000', -// value: '0x00', -// chainId: 3, -// }; -// const txHex = await ledgerSubprovider.signTransactionAsync(tx); -// expect(txHex).to.be.equal( -// '0xf85f8080822710940000000000000000000000000000000000000000808077a088a95ef1378487bc82be558e82c8478baf840c545d5b887536bb1da63673a98ba0019f4a4b9a107d1e6752bf7f701e275f28c13791d6e76af895b07373462cefaa', -// ); -// }); -// }); -// describe('calls through a provider', () => { -// let defaultProvider: Web3ProviderEngine; -// let ledgerProvider: Web3ProviderEngine; -// before(() => { -// ledgerProvider = new Web3ProviderEngine(); -// ledgerProvider.addProvider(ledgerSubprovider); -// const httpProvider = new RpcSubprovider({ -// rpcUrl: 'http://localhost:8545', -// }); -// ledgerProvider.addProvider(httpProvider); -// ledgerProvider.start(); +const TEST_RPC_ACCOUNT_0 = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; -// defaultProvider = new Web3ProviderEngine(); -// defaultProvider.addProvider(httpProvider); -// defaultProvider.start(); -// }); -// it('returns a list of accounts', (done: DoneCallback) => { -// const payload = { -// jsonrpc: '2.0', -// method: 'eth_accounts', -// params: [], -// id: 1, -// }; -// const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { -// expect(err).to.be.a('null'); -// expect(response.result.length).to.be.equal(10); -// done(); -// }); -// ledgerProvider.sendAsync(payload, callback); -// }); -// it('signs a personal message with eth_sign', (done: DoneCallback) => { -// (async () => { -// const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer('hello world')); -// const accounts = await ledgerSubprovider.getAccountsAsync(); -// const signer = accounts[0]; -// const payload = { -// jsonrpc: '2.0', -// method: 'eth_sign', -// params: [signer, messageHex], -// id: 1, -// }; -// const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { -// expect(err).to.be.a('null'); -// expect(response.result.length).to.be.equal(132); -// expect(response.result.substr(0, 2)).to.be.equal('0x'); -// done(); -// }); -// ledgerProvider.sendAsync(payload, callback); -// })().catch(done); -// }); -// it('signs a personal message with personal_sign', (done: DoneCallback) => { -// (async () => { -// const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer('hello world')); -// const accounts = await ledgerSubprovider.getAccountsAsync(); -// const signer = accounts[0]; -// const payload = { -// jsonrpc: '2.0', -// method: 'personal_sign', -// params: [messageHex, signer], -// id: 1, -// }; -// const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { -// expect(err).to.be.a('null'); -// expect(response.result.length).to.be.equal(132); -// expect(response.result.substr(0, 2)).to.be.equal('0x'); -// done(); -// }); -// ledgerProvider.sendAsync(payload, callback); -// })().catch(done); -// }); -// it('signs a transaction', (done: DoneCallback) => { -// const tx = { -// to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66', -// value: '0x00', -// }; -// const payload = { -// jsonrpc: '2.0', -// method: 'eth_signTransaction', -// params: [tx], -// id: 1, -// }; -// const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { -// expect(err).to.be.a('null'); -// expect(response.result.raw.length).to.be.equal(206); -// expect(response.result.raw.substr(0, 2)).to.be.equal('0x'); -// done(); -// }); -// ledgerProvider.sendAsync(payload, callback); -// }); -// it('signs and sends a transaction', (done: DoneCallback) => { -// (async () => { -// const accounts = await ledgerSubprovider.getAccountsAsync(); +describe('LedgerSubprovider', () => { + let ledgerSubprovider: LedgerSubprovider; + const networkId: number = 42; + before(async () => { + ledgerSubprovider = new LedgerSubprovider({ + networkId, + ledgerEthereumClientFactoryAsync: ledgerEthereumNodeJsClientFactoryAsync, + }); + }); + describe('direct method calls', () => { + it('returns default number of accounts', async () => { + const accounts = await ledgerSubprovider.getAccountsAsync(); + expect(accounts[0]).to.not.be.an('undefined'); + expect(accounts.length).to.be.equal(10); + }); + it('returns requested number of accounts', async () => { + const numberOfAccounts = 20; + const accounts = await ledgerSubprovider.getAccountsAsync(numberOfAccounts); + expect(accounts[0]).to.not.be.an('undefined'); + expect(accounts.length).to.be.equal(numberOfAccounts); + }); + it('signs a personal message', async () => { + const data = ethUtils.bufferToHex(ethUtils.toBuffer('hello world')); + const ecSignatureHex = await ledgerSubprovider.signPersonalMessageAsync(data); + expect(ecSignatureHex.length).to.be.equal(132); + expect(ecSignatureHex.substr(0, 2)).to.be.equal('0x'); + }); + it('signs a transaction', async () => { + const tx = { + nonce: '0x00', + gas: '0x2710', + to: '0x0000000000000000000000000000000000000000', + value: '0x00', + chainId: 3, + }; + const txHex = await ledgerSubprovider.signTransactionAsync(tx); + expect(txHex).to.be.equal( + '0xf85f8080822710940000000000000000000000000000000000000000808077a088a95ef1378487bc82be558e82c8478baf840c545d5b887536bb1da63673a98ba0019f4a4b9a107d1e6752bf7f701e275f28c13791d6e76af895b07373462cefaa', + ); + }); + }); + describe('calls through a provider', () => { + let defaultProvider: Web3ProviderEngine; + let ledgerProvider: Web3ProviderEngine; + before(() => { + ledgerProvider = new Web3ProviderEngine(); + ledgerProvider.addProvider(ledgerSubprovider); + const httpProvider = new RpcSubprovider({ + rpcUrl: 'http://localhost:8545', + }); + ledgerProvider.addProvider(httpProvider); + ledgerProvider.start(); -// // Give first account on Ledger sufficient ETH to complete tx send -// let tx = { -// to: accounts[0], -// from: TEST_RPC_ACCOUNT_0, -// value: '0x8ac7230489e80000', // 10 ETH -// }; -// let payload = { -// jsonrpc: '2.0', -// method: 'eth_sendTransaction', -// params: [tx], -// id: 1, -// }; -// await promisify(defaultProvider.sendAsync, defaultProvider)(payload); + defaultProvider = new Web3ProviderEngine(); + defaultProvider.addProvider(httpProvider); + defaultProvider.start(); + }); + it('returns a list of accounts', (done: DoneCallback) => { + const payload = { + jsonrpc: '2.0', + method: 'eth_accounts', + params: [], + id: 1, + }; + const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { + expect(err).to.be.a('null'); + expect(response.result.length).to.be.equal(10); + done(); + }); + ledgerProvider.sendAsync(payload, callback); + }); + it('signs a personal message with eth_sign', (done: DoneCallback) => { + (async () => { + const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer('hello world')); + const accounts = await ledgerSubprovider.getAccountsAsync(); + const signer = accounts[0]; + const payload = { + jsonrpc: '2.0', + method: 'eth_sign', + params: [signer, messageHex], + id: 1, + }; + const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { + expect(err).to.be.a('null'); + expect(response.result.length).to.be.equal(132); + expect(response.result.substr(0, 2)).to.be.equal('0x'); + done(); + }); + ledgerProvider.sendAsync(payload, callback); + })().catch(done); + }); + it('signs a personal message with personal_sign', (done: DoneCallback) => { + (async () => { + const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer('hello world')); + const accounts = await ledgerSubprovider.getAccountsAsync(); + const signer = accounts[0]; + const payload = { + jsonrpc: '2.0', + method: 'personal_sign', + params: [messageHex, signer], + id: 1, + }; + const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { + expect(err).to.be.a('null'); + expect(response.result.length).to.be.equal(132); + expect(response.result.substr(0, 2)).to.be.equal('0x'); + done(); + }); + ledgerProvider.sendAsync(payload, callback); + })().catch(done); + }); + it('signs a transaction', (done: DoneCallback) => { + const tx = { + to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66', + value: '0x00', + }; + const payload = { + jsonrpc: '2.0', + method: 'eth_signTransaction', + params: [tx], + id: 1, + }; + const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { + expect(err).to.be.a('null'); + expect(response.result.raw.length).to.be.equal(206); + expect(response.result.raw.substr(0, 2)).to.be.equal('0x'); + done(); + }); + ledgerProvider.sendAsync(payload, callback); + }); + it('signs and sends a transaction', (done: DoneCallback) => { + (async () => { + const accounts = await ledgerSubprovider.getAccountsAsync(); -// // Send transaction from Ledger -// tx = { -// to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66', -// from: accounts[0], -// value: '0xde0b6b3a7640000', -// }; -// payload = { -// jsonrpc: '2.0', -// method: 'eth_sendTransaction', -// params: [tx], -// id: 1, -// }; -// const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { -// expect(err).to.be.a('null'); -// const result = response.result; -// expect(result.length).to.be.equal(66); -// expect(result.substr(0, 2)).to.be.equal('0x'); -// done(); -// }); -// ledgerProvider.sendAsync(payload, callback); -// })().catch(done); -// }); -// }); -// }); + // Give first account on Ledger sufficient ETH to complete tx send + let tx = { + to: accounts[0], + from: TEST_RPC_ACCOUNT_0, + value: '0x8ac7230489e80000', // 10 ETH + }; + let payload = { + jsonrpc: '2.0', + method: 'eth_sendTransaction', + params: [tx], + id: 1, + }; + await promisify(defaultProvider.sendAsync, defaultProvider)(payload); + + // Send transaction from Ledger + tx = { + to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66', + from: accounts[0], + value: '0xde0b6b3a7640000', + }; + payload = { + jsonrpc: '2.0', + method: 'eth_sendTransaction', + params: [tx], + id: 1, + }; + const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { + expect(err).to.be.a('null'); + const result = response.result; + expect(result.length).to.be.equal(66); + expect(result.substr(0, 2)).to.be.equal('0x'); + done(); + }); + ledgerProvider.sendAsync(payload, callback); + })().catch(done); + }); + }); +}); -- cgit v1.2.3