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

Add Trezor wallet connector #206

Merged
merged 19 commits into from
Jul 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"react-scripts": "3.0.1",
"redux": "^4.0.4",
"redux-saga": "^1.0.5",
"trezor-connect": "^8.1.1",
"web3": "^1.2.6",
"web3-provider-engine": "^15.0.6"
},
Expand Down
24 changes: 20 additions & 4 deletions public/images/ledger.svg
Loading
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
221 changes: 147 additions & 74 deletions src/components/lib/ConnectWalletDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,124 +3,197 @@ import Check from '../svgs/Check'
import { useWeb3React } from '@web3-react/core'
import { InjectedConnector } from '@web3-react/injected-connector'
import { LedgerConnector } from '../../connectors/ledger'
import { TrezorConnector } from '../../connectors/trezor'

const ETH_CHAIN_ID = process.env.ETH_CHAIN_ID || 1337
const ETH_WS_URL = process.env.ETH_WS_URL || 'ws://localhost:8545'
const CHAIN_ID = process.env.CHAIN_ID || 1101
const ETH_RPC_URL = process.env.ETH_RPC_URL || 'ws://localhost:8546'

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

const ledgerConnector = new LedgerConnector({
chainId: ETH_CHAIN_ID,
url: ETH_WS_URL
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: '[email protected]',
manifestAppUrl: 'https://localhost'
})

// Wallets.
const WALLETS = [
{
name: "Metamask",
icon: "/images/metamask-fox.svg",
showName: true,
connector: injectedConnector
},
{
name: "Ledger",
name: "Ledger Legacy",
icon: "/images/ledger.svg",
connector: ledgerConnector
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, onClose }) => {
const { active, account, activate, chainId, connector } = useWeb3React()
const { active, account, activate } = useWeb3React()

let [chosenWallet, setChosenWallet] = useState(null)
let [chosenWallet, setChosenWallet] = useState({})
let [error, setError] = useState(null)
let state = {
chosenWallet,
error
}
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 {
if(wallet.isHardwareWallet) {
wallet.connector.setDefaultAccount(selectedAccount)
}
await activate(wallet.connector, undefined, true)
onConnected()
} catch(ex) {
setError(ex.toString())
throw ex
}
}

const ChooseWalletStep = () => {
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 className='wallet-option' onClick={() => chooseWallet(wallet)}>
<img src={wallet.icon} />
{wallet.showName && wallet.name}
</li>
})
}
</ul>
</>
const reconnectWallet = async () => {
setError(null)
await chooseWallet(chosenWallet)
}

const ConnectToWalletStep = () => {
if(error) {
return <ErrorConnecting/>
}
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}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spacing and indentation is odd here but we can fix it when we do a pass with prettier

/>}
{(chosenWallet.name && active) && <ConnectedView wallet={chosenWallet} account={account} />}
<ChooseAccount
wallet={chosenWallet}
availableAccounts={availableAccounts}
active={active}
onAccountSelect={activateProvider}
/>
</div>
</div>
}

if(chosenWallet.name == 'Ledger') {
return <>
<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 ChooseWalletStep = ({ onChooseWallet }) => {
return <>
<div className="title">Connect to a wallet</div>
<p>This wallet will be used to sign transactions on Ethereum.</p>

return <>
<div className="title">Connect to a wallet</div>
<p>Connecting to {chosenWallet.name} wallet...</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} />
}

const ErrorConnecting = () => {
if(wallet.name.includes('Ledger')) {
return <>
<div className="title">Connect to a wallet</div>
<p>Error connecting to {chosenWallet.name} wallet...</p>
<a onClick={async () => {
setError(null)
await chooseWallet(chosenWallet)
}}>
Try Again
</a>
{ 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'>
<div className="title">Wallet connected</div>
<div className='details'>
<p>{chosenWallet.name}</p>
<p>Account: {account}</p>
</div>
</div>
}
return <>
<div className="title">Connect to a wallet</div>
<p>Connecting to {wallet.name} wallet...</p>
</>
}

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 && <ChooseWalletStep />}
{(chosenWallet && !active) && <ConnectToWalletStep />}
{(chosenWallet && active) && <ConnectedView />}
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> }
</>
}
17 changes: 11 additions & 6 deletions src/connectors/ledger.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import TransportU2F from "@ledgerhq/hw-transport-u2f"
import AppEth from '@ledgerhq/hw-app-eth'
import { ConnectorUpdate } from '@web3-react/types'
import { AbstractConnector } from '@web3-react/abstract-connector'
import Web3ProviderEngine from 'web3-provider-engine'
import { LedgerSubprovider } from './ledger_subprovider'
import CacheSubprovider from 'web3-provider-engine/subproviders/cache.js'
import { RPCSubprovider } from '@0x/subproviders/lib/src/subproviders/rpc_subprovider' // https://github.com/0xProject/0x-monorepo/issues/1400
import WebsocketSubprovider from 'web3-provider-engine/subproviders/websocket'

/**
Expand All @@ -22,6 +20,8 @@ import WebsocketSubprovider from 'web3-provider-engine/subproviders/websocket'
* support out-of-the-box. Assuming a Websocket provider is simpler for our case.
*/
export class LedgerConnector extends AbstractConnector {
defaultAccount = ""

constructor({
chainId,
url,
Expand Down Expand Up @@ -91,11 +91,16 @@ export class LedgerConnector extends AbstractConnector {
return this.chainId
}

/**
* @return {Promise<null>}
*/
async getAccount() {
return this.provider._providers[0].getAccountsAsync(1).then((accounts: string[]): string => accounts[0])
return this.defaultAccount
}

async getAccounts(numberOfAccounts = 15) {
return await this.provider._providers[0].getAccountsAsync(numberOfAccounts)
}

setDefaultAccount(account) {
this.defaultAccount = account
}

deactivate() {
Expand Down
Loading