Skip to content
This repository has been archived by the owner on Mar 28, 2023. It is now read-only.

Heath Ledger: Add Ledger wallet support #205

Merged
merged 56 commits into from Aug 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
18d65bd
Implement a Ledger wallet connector
liamzebedee Apr 1, 2020
0c615d5
Improve doc for LedgerConnector
liamzebedee Apr 1, 2020
afaec4e
Begin documenting the process of testing the hardware wallets
liamzebedee Apr 2, 2020
0b9cdac
Update Trezor links to their documentation
liamzebedee Apr 6, 2020
e2302ae
Clicking Web3Status when "Connected" displays dialog
liamzebedee Apr 6, 2020
739304a
Rewrite LedgerConnector to use 0x LedgerSubprovider
liamzebedee Apr 6, 2020
d26756e
fix: U2F DEVICE_INELIGIBLE with Ledger TransportU2F
liamzebedee Apr 6, 2020
50d39d7
Add close button to dialog
liamzebedee Apr 6, 2020
ce2e747
Show connected network in dialog details
liamzebedee Apr 6, 2020
621773c
Add @0x/subproviders, upgrade @ledgerhq/hw-app-eth
liamzebedee Apr 6, 2020
6fd969f
Correct title in ConnectedView
liamzebedee Apr 6, 2020
c73798a
Add ErrorConnecting view with "Try Again" button
liamzebedee Apr 6, 2020
e25fc67
Add Ledger-specific connection instructions
liamzebedee Apr 6, 2020
847449a
Consistent capitalisation
liamzebedee Apr 6, 2020
eed6c41
Fix Ledger signing issues when chainId > 255
liamzebedee Apr 15, 2020
060c590
Refactor chooseWallet code
liamzebedee Apr 15, 2020
7858457
Remove SUPPORTED_CHAIN_IDS
liamzebedee Apr 15, 2020
f7c1541
Pass around chosenWallet object instead of name/connector combo.
liamzebedee Apr 15, 2020
c304689
Clean up unused <div> boilerplate
liamzebedee Apr 15, 2020
2c101b2
Remove CHAINS array for now, and inject chainId/rpcUrl
liamzebedee Apr 15, 2020
21c23d1
Better document Ledger's exchangeTimeout, and slighly increase the value
liamzebedee Apr 15, 2020
963a196
More documentation of Ledger code
liamzebedee Apr 15, 2020
b034861
Merge branch 'master' of github.com:keep-network/tbtc-dapp into heath…
liamzebedee Apr 15, 2020
8721d37
Refactor getBufferFromHex helper into hexToPaddedBuffer
liamzebedee Apr 15, 2020
cc21880
Revert ledgerEthereumClientFactoryAsync rename
liamzebedee Apr 15, 2020
96f7836
Update package-lock
liamzebedee Apr 15, 2020
5c53568
Update doc
liamzebedee Apr 16, 2020
b29b682
Use stronger comparison operator
liamzebedee Apr 16, 2020
0d36816
Add Trezor wallet connector
liamzebedee Apr 16, 2020
b38594d
Add custom Trezor subprovider
liamzebedee Apr 17, 2020
bd18333
Add missing trezor-connect package
liamzebedee Apr 17, 2020
9f81151
Import hexToPaddedBuffer from utils
liamzebedee Apr 17, 2020
af5d9cd
Rename injected ETH chain parameters to match our Kube configs
liamzebedee Apr 17, 2020
eb9e46d
WIP: Inject ETH RPC URL and chain ID in tbtc-dapp Deployment
liamzebedee Apr 17, 2020
a3c78da
Remove ts types in the trezor.js file
r-czajkowski Jun 26, 2020
9ae4508
Add a defaultAccount field to the TrezorConnector
r-czajkowski Jun 26, 2020
f7743bd
Add ChooseAccount component
r-czajkowski Jun 26, 2020
be8ef0c
Fix the `activateProvider` fn
r-czajkowski Jun 26, 2020
b061f3f
Add commons styles
r-czajkowski Jun 26, 2020
81438cb
Extract components
r-czajkowski Jun 26, 2020
24a229c
Remove unused console.log and variable in web3.js
r-czajkowski Jun 29, 2020
da79c34
Pass a number of accounts to the getAccounts fn
r-czajkowski Jun 29, 2020
a5f77d6
Catch error while aborting the wallet connection
r-czajkowski Jun 29, 2020
92d57e8
Change hardware wallets icons
r-czajkowski Jun 29, 2020
d6e2cfe
Add support for ledger live/legacy
r-czajkowski Jun 29, 2020
fb516a1
Cleanup in the ledger_subprovider.js
r-czajkowski Jun 29, 2020
d7928e7
Merge branch 'heath-ledger' into tresor-317-integration
ironng Jul 3, 2020
0ea3d33
Update ledger name check
ironng Jul 3, 2020
f5d9a04
Address nits
ironng Jul 3, 2020
6753685
Merge pull request #206 from keep-network/tresor-317-integration
ironng Jul 3, 2020
1d84561
Merge branch 'master' into heath-ledger
ironng Jul 3, 2020
4e3946d
Merge branch 'master' into heath-ledger
ironng Jul 8, 2020
fc20c9d
Merge branch 'master' into heath-ledger
ironng Jul 24, 2020
4cc34fb
Get network ID and wsURL from artifacts
r-czajkowski Jul 24, 2020
00042dd
Merge branch 'master' into heath-ledger
ironng Jul 24, 2020
a06cb50
Merge branch 'master' into heath-ledger
Shadowfiend Aug 6, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/testing-with-hardware-wallets.md
@@ -0,0 +1,21 @@
# Testing with hardware wallets.

## Running the dApp on HTTPS

To test with hardware wallets, the dApp must be served over HTTPS. Run `HTTPS=true npm start` and access it at https://localhost:3000.

### Hardware wallets

## Trezor

To run the trezor hardware wallet emulator, follow these steps:

1. [Download and run the trezor bridge](https://github.com/trezor/trezord-go).
2. [Clone the Trezor repo](https://github.com/trezor/trezor-firmware/blob/master/docs/core/build/index.md)
3. [Setup and run the emulator](https://github.com/trezor/trezor-firmware/blob/master/docs/core/emulator/index.md)

Connect to the Trezor in Metamask, and deposit some ether for testing.

## Ledger

We suggest testing with a physical Ledger wallet.
13 changes: 13 additions & 0 deletions infrastructure/kube/keep-dev/tbtc-dapp-deployment.yaml
Expand Up @@ -25,3 +25,16 @@ spec:
image: gcr.io/keep-dev-fe24/tbtc-dapp
ports:
- containerPort: 80
env:
# TODO: ETH_WS_URL needs to be using wss:// instead of ws://, otherwise
# insecure connection will be blocked.
- name: ETH_WS_URL
valueFrom:
configMapKeyRef:
name: eth-network-internal
key: ws-url
- name: ETH_CHAIN_ID
valueFrom:
configMapKeyRef:
name: eth-network-internal
key: network-id
11 changes: 11 additions & 0 deletions infrastructure/kube/keep-test/tbtc-dapp-deployment.yaml
Expand Up @@ -25,3 +25,14 @@ spec:
image: gcr.io/keep-test-f3e0/tbtc-dapp
ports:
- containerPort: 80
env:
- name: ETH_WS_URL
valueFrom:
configMapKeyRef:
name: eth-network-ropsten
key: ws-url
- name: ETH_CHAIN_ID
valueFrom:
configMapKeyRef:
name: eth-network-ropsten
key: network-id
16,399 changes: 16,270 additions & 129 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions package.json
Expand Up @@ -3,11 +3,17 @@
"version": "0.16.4-rc",
"dependencies": {
"@keep-network/tbtc.js": ">0.18.0-rc <0.18.0",
"@0x/subproviders": "^6.0.8",
"@ledgerhq/hw-app-eth": "^5.12.2",
"@ledgerhq/hw-transport-u2f": "^5.11.0",
"@ledgerhq/web3-subprovider": "^5.11.0",
"@web3-react/core": "^6.0.7",
"@web3-react/injected-connector": "^6.0.7",
"bignumber.js": "^9.0.0",
"classnames": "^2.2.6",
"console.history": "^1.5.1",
"ethereumjs-common": "^1.5.0",
"ethereumjs-tx": "^2.1.2",
"history": "^4.9.0",
"node-sass": "^4.13.1",
"npm-run-all": "^4.1.2",
Expand All @@ -21,7 +27,9 @@
"redux": "^4.0.4",
"redux-devtools-extension": "^2.13.8",
"redux-saga": "^1.0.5",
"trezor-connect": "^8.1.1",
"web3": "^1.2.6",
"web3-provider-engine": "^15.0.6",
"web3-utils": "^1.2.8"
},
"scripts": {
Expand Down
20 changes: 20 additions & 0 deletions public/images/ledger.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions public/images/trezor.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
226 changes: 160 additions & 66 deletions src/components/lib/ConnectWalletDialog.js
@@ -1,105 +1,199 @@
import React, { useState } from 'react'
import { useWeb3React } from '@web3-react/core'
import { InjectedConnector } from '@web3-react/injected-connector'
import { LedgerConnector } from '../../connectors/ledger'
import { TrezorConnector } from '../../connectors/trezor'
import { getChainId, getWsUrl } from '../../connectors/utils'

const SUPPORTED_CHAIN_IDS = [
// Mainnet
1,
// Ropsten
3,
// Rinkeby
4,
// Dev chains (Ganache, Geth)
123, // Low chainId to workaround ledgerjs signing issues.
1337,
// Keep testnet
1101
]
const CHAIN_ID = getChainId()
const ETH_RPC_URL = getWsUrl()

// Connectors.
const injectedConnector = new InjectedConnector({
supportedChainIds: SUPPORTED_CHAIN_IDS
const injectedConnector = new InjectedConnector({})

const ledgerLiveConnector = new LedgerConnector({
chainId: CHAIN_ID,
url: ETH_RPC_URL,
baseDerivationPath: "44'/60'/0'/0",
})

const ledgerLegacyConnector = new LedgerConnector({
chainId: CHAIN_ID,
url: ETH_RPC_URL,
baseDerivationPath: "44'/60'/0'",
})

const trezorConnector = new TrezorConnector({
chainId: CHAIN_ID,
pollingInterval: 1000,
requestTimeoutMs: 1000,
config: {
chainId: CHAIN_ID,
},
url: ETH_RPC_URL,
manifestEmail: 'contact@keep.network',
manifestAppUrl: 'https://localhost'
})

// Wallets.
const WALLETS = [
{
name: "Metamask",
icon: "/images/metamask-fox.svg",
showName: true
connector: injectedConnector
},
{
name: "Ledger Legacy",
icon: "/images/ledger.svg",
connector: ledgerLegacyConnector,
isHardwareWallet: true,
},
{
name: "Ledger Live",
icon: "/images/ledger.svg",
connector: ledgerLiveConnector,
isHardwareWallet: true,
},
{
name: "Trezor",
icon: "/images/trezor.svg",
connector: trezorConnector,
isHardwareWallet: true,
}
]

export const ConnectWalletDialog = ({ shown, onConnected }) => {

export const ConnectWalletDialog = ({ shown, onConnected, onClose }) => {
const { active, account, activate } = useWeb3React()

let [chosenWallet, setChosenWallet] = useState(null)
let [chosenWallet, setChosenWallet] = useState({})
let [error, setError] = useState(null)
const [availableAccounts, setAvailableAccounts] = useState([])

async function chooseWallet(wallet) {
setChosenWallet(wallet)
try {
setChosenWallet(wallet)
if(wallet.isHardwareWallet) {
await wallet.connector.activate()
setAvailableAccounts(await wallet.connector.getAccounts())
} else {
await activateProvider(null, wallet)
}
} catch(error) {
setError(error.toString())
}
}

const activateProvider = async (selectedAccount, wallet = chosenWallet) => {
try {
await activate(injectedConnector, undefined, true)
if(wallet.isHardwareWallet) {
wallet.connector.setDefaultAccount(selectedAccount)
}
await activate(wallet.connector, undefined, true)
onConnected()
} catch(ex) {
setError(ex.toString())
throw ex
}
}

const ChooseWalletStep = () => {
return <>
<header>
<div className="title">Connect To A Wallet</div>
</header>
<p>This wallet will be used to sign transactions on Ethereum.</p>

<ul className='wallets'>
{
WALLETS.map(({ name, icon, showName }) => {
return <li key={name} className='wallet-option' onClick={() => chooseWallet(name)}>
<img src={icon} alt={`${name} icon`} />
{showName && name}
</li>
})
}
</ul>
</>
const reconnectWallet = async () => {
setError(null)
await chooseWallet(chosenWallet)
}

const ConnectToWalletStep = () => {
return <div className={`modal connect-wallet ${shown ? 'open' : 'closed'}`}>
<div className="modal-body">
<div className="close">
<div className="x" onClick={onClose}>&#9587;</div>
</div>
{!chosenWallet.name && <ChooseWalletStep onChooseWallet={chooseWallet} />}
{(chosenWallet.name && !active) &&
<ConnectToWalletStep
wallet={chosenWallet}
error={error}onTryAgainClick={reconnectWallet}
/>}
{(chosenWallet.name && active) && <ConnectedView wallet={chosenWallet} account={account} />}
<ChooseAccount
wallet={chosenWallet}
availableAccounts={availableAccounts}
active={active}
onAccountSelect={activateProvider}
/>
</div>
</div>
}

const ChooseWalletStep = ({ onChooseWallet }) => {
return <>
<div className="title">Connect to a wallet</div>
<p>This wallet will be used to sign transactions on Ethereum.</p>

<ul className='wallets'>
{
WALLETS.map(wallet => {
return <li key={wallet.name} className='wallet-option' onClick={() => onChooseWallet(wallet)}>
<img alt="wallet-icon" src={wallet.icon} />
{wallet.name}
</li>
})
}
</ul>
</>
}

const ConnectToWalletStep = ({ error, wallet, onTryAgainClick }) => {
if(error) {
return <ErrorConnecting error={error} wallet={wallet} onTryAgainClick={onTryAgainClick} />
}

if(wallet.name.includes('Ledger')) {
return <>
<header>
<div className="title">Connect To A Wallet</div>
</header>
<p>Connecting to {chosenWallet} wallet...</p>
{ error && <p>{error}</p> }
<div className="title">Plug In Ledger & Enter Pin</div>
<p>Open Ethereum application and make sure Contract Data and Browser Support are enabled.</p>
<p>Connecting...</p>
</>
}

const ConnectedView = () => {
return <div className='connected-view'>
<header>
<div className="title">Connect To A Wallet</div>
</header>

<div className='details'>
<p>{chosenWallet}</p>
<p>
{account}
</p>
</div>
</div>
}
return <>
<div className="title">Connect to a wallet</div>
<p>Connecting to {wallet.name} wallet...</p>
</>
}

return <div>
<div className={`modal connect-wallet ${shown ? 'open' : 'closed'}`}>
<div className="modal-body">
{!chosenWallet && <ChooseWalletStep />}
{(chosenWallet && !active) && <ConnectToWalletStep />}
{(chosenWallet && active) && <ConnectedView />}
</div>
const ChooseAccount = ({ wallet, availableAccounts, active, onAccountSelect }) => {
if(wallet.isHardwareWallet && availableAccounts.length !== 0 && !active) {
return (
<>
<div className="title mb-2">Select account</div>
{availableAccounts.map(account => (
<div key={account} className="cursor-pointer mb-1" onClick={() => onAccountSelect(account)}>
{account}
</div>
))}
</>
)
}

return null
}

const ConnectedView = ({ wallet, account }) => {
return <div className='connected-view'>
<div className="title">Wallet connected</div>
<div className='details'>
<p>{wallet.name}</p>
<p>Account: {account}</p>
</div>
</div>
}

const ErrorConnecting = ({ wallet, error, onTryAgainClick }) => {
return <>
<div className="title">Connect to a wallet</div>
<p>Error connecting to {wallet.name} wallet...</p>
<span onClick={onTryAgainClick}>
Try Again
</span>
{ error && <p>{error}</p> }
</>
}