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

Commit

Permalink
WIP: Try webusb - currently failing
Browse files Browse the repository at this point in the history
- `thorchain-ledger` needs to be linked locally (via `yarn link`) to run
/ build all code
  • Loading branch information
veado committed Jul 20, 2021
1 parent f36544e commit 79ffafc
Show file tree
Hide file tree
Showing 16 changed files with 231 additions and 52 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"@devexperts/remote-data-ts": "^2.0.5",
"@devexperts/rx-utils": "^1.0.0-alpha.14",
"@devexperts/utils": "^1.0.0-alpha.14",
"@ledgerhq/hw-transport-webusb": "^6.1.0",
"@openapitools/openapi-generator-cli": "^2.3.5",
"@psf/bitcoincashjs-lib": "^4.0.2",
"@thorchain/asgardex-midgard": "^1.1.0",
Expand Down
3 changes: 2 additions & 1 deletion src/main/api/hdwallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ export const apiHDWallet: ApiHDWallet = {
getLedgerAddress: (chain: Chain, network: Network) =>
ipcRenderer.invoke(IPCMessages.GET_LEDGER_ADDRESS, chain, network),
sendTxInLedger: (chain: Chain, network: Network, txInfo: LedgerTxInfo) =>
ipcRenderer.invoke(IPCMessages.SEND_LEDGER_TX, chain, network, txInfo)
ipcRenderer.invoke(IPCMessages.SEND_LEDGER_TX, chain, network, txInfo),
getTransport: () => ipcRenderer.invoke(IPCMessages.GET_TRANSPORT)
}
47 changes: 21 additions & 26 deletions src/main/api/ledger/address.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,27 @@
// import TransportNodeHid from '@ledgerhq/hw-transport-node-hid'
// import { BNBChain, BTCChain, Chain } from '@xchainjs/xchain-util'
import { Chain } from '@xchainjs/xchain-util'
// import * as E from 'fp-ts/Either'
import TransportWebUSB from '@ledgerhq/hw-transport-webusb'
import { Chain, THORChain } from '@xchainjs/xchain-util'
import * as E from 'fp-ts/Either'

import { Network } from '../../../shared/api/types'
// import { LedgerErrorId, Network } from '../../../shared/api/types'
import { LedgerErrorId, Network } from '../../../shared/api/types'
// import { getAddress as getBNBAddress } from './binance'
// import { getAddress as getBTCAddress } from './bitcoin'
// import { getErrorId } from './utils'
import { getAddress as getTHORAddress } from './thorchain'
import { getErrorId } from './utils'

export const getAddress = async (chain: Chain, network: Network) => {
const _disabled = { chain, network }
// try {
// const transport = await TransportNodeHid.open('')
// let res: E.Either<LedgerErrorId, string>
// switch (chain) {
// case BNBChain:
// res = await getBNBAddress(transport, network)
// break
// case BTCChain:
// res = await getBTCAddress(transport, network)
// break
// default:
// res = E.left(LedgerErrorId.NO_APP)
// }
// await transport.close()
// return res
// } catch (error) {
// return E.left(getErrorId(error.toString()))
// }
try {
const transport = await TransportWebUSB.create()
let res: E.Either<LedgerErrorId, string>
switch (chain) {
case THORChain:
res = await getTHORAddress(transport, network)
break
default:
res = E.left(LedgerErrorId.NO_APP)
}
await transport.close()
return res
} catch (error) {
return E.left(getErrorId(error.toString()))
}
}
3 changes: 3 additions & 0 deletions src/main/api/ledger/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import TransportWebUSB from '@ledgerhq/hw-transport-webusb'

export const getTransport = async () => await TransportWebUSB.create()
1 change: 1 addition & 0 deletions src/main/api/ledger/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './address'
export * from './transaction'
export * from './common'
30 changes: 30 additions & 0 deletions src/main/api/ledger/thorchain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Transport from '@ledgerhq/hw-transport'
import THORChainApp from '@thorchain/ledger-thorchain'
import * as Client from '@xchainjs/xchain-client'
import { getPrefix } from '@xchainjs/xchain-thorchain'
import * as E from 'fp-ts/Either'

import { LedgerErrorId, Network } from '../../../shared/api/types'
import { getErrorId } from './utils'

// TODO(@Veado) Move `toClientNetwork` from `renderer/services/clients` to `main/util` or so
const toClientNetwork = (network: Network): Client.Network =>
network === 'mainnet' ? Client.Network.Mainnet : Client.Network.Testnet
// TODO(@veado) Get path by using `xchain-thorchain`
const PATH = [44, 931, 0, 0, 0]

export const getAddress = async (transport: Transport, network: Network) => {
try {
const app = new THORChainApp(transport)
const clientNetwork = toClientNetwork(network)
const response = await app.getAddressAndPubKey(PATH, getPrefix(clientNetwork))
if (response.return_code !== 0x9000) {
// TODO(@Veado) get address from pubkey
return E.right('my-address')
} else {
return E.left(LedgerErrorId.UNKNOWN)
}
} catch (error) {
return E.left(getErrorId(error.toString()))
}
}
6 changes: 3 additions & 3 deletions src/main/api/ledger/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { Chain } from '@xchainjs/xchain-util'
import * as E from 'fp-ts/Either'

import { LedgerErrorId, LedgerTxInfo, Network } from '../../../shared/api/types'
import { /* LedgerErrorId, */ LedgerTxInfo, Network } from '../../../shared/api/types'
// import { LedgerBNCTxInfo, LedgerBTCTxInfo, LedgerErrorId, LedgerTxInfo, Network } from '../../../shared/api/types'
// import { sendTx as sendBNCTx } from './binance'
// import { sendTx as sendBTCTx } from './bitcoin'
Expand All @@ -15,9 +15,9 @@ export const sendTx = async (
chain: Chain,
network: Network,
txInfo: LedgerTxInfo
): Promise<E.Either<LedgerErrorId, string>> => {
): Promise<E.Either<Error /* LedgerErrorId */, string>> => {
const _disabled = { chain, network, txInfo }
return Promise.reject(Error('sendTx for Ledger is disabled temporary'))
return E.left(Error('sendTx for Ledger is disabled temporary'))
// try {
// const transport = await TransportNodeHid.open('')
// let res: E.Either<LedgerErrorId, string>
Expand Down
18 changes: 14 additions & 4 deletions src/main/electron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import { Locale } from '../shared/i18n/types'
import { registerAppCheckUpdatedHandler } from './api/appUpdate'
import { getFileStoreService } from './api/fileStore'
import { saveKeystore, removeKeystore, getKeystore, keystoreExist, exportKeystore, loadKeystore } from './api/keystore'
import { getAddress, sendTx } from './api/ledger'
import {
getAddress as getLedgerAddress,
sendTx as sendLedgerTx,
getTransport as getLedgerTransport
} from './api/ledger'
import IPCMessages from './ipc/messages'
import { setMenu } from './menu'

Expand Down Expand Up @@ -121,7 +125,9 @@ const langChangeHandler = (locale: Locale) => {
}

const initIPC = () => {
// Lang
ipcMain.on(IPCMessages.UPDATE_LANG, (_, locale: Locale) => langChangeHandler(locale))
// Keystore
ipcMain.handle(IPCMessages.SAVE_KEYSTORE, (_, keystore: Keystore) => saveKeystore(keystore))
ipcMain.handle(IPCMessages.REMOVE_KEYSTORE, () => removeKeystore())
ipcMain.handle(IPCMessages.GET_KEYSTORE, () => getKeystore())
Expand All @@ -130,11 +136,15 @@ const initIPC = () => {
exportKeystore(defaultFileName, keystore)
)
ipcMain.handle(IPCMessages.LOAD_KEYSTORE, () => loadKeystore())
ipcMain.handle(IPCMessages.GET_LEDGER_ADDRESS, (_, chain: Chain, network: Network) => getAddress(chain, network))
// Ledger
ipcMain.handle(IPCMessages.GET_LEDGER_ADDRESS, (_, chain: Chain, network: Network) =>
getLedgerAddress(chain, network)
)
ipcMain.handle(IPCMessages.SEND_LEDGER_TX, (_, chain: Chain, network: Network, txInfo: LedgerTxInfo) =>
sendTx(chain, network, txInfo)
sendLedgerTx(chain, network, txInfo)
)

ipcMain.handle(IPCMessages.GET_TRANSPORT, () => getLedgerTransport())
// Update
registerAppCheckUpdatedHandler(IS_DEV)
// Register all file-stored data services
Object.entries(DEFAULT_STORAGES).forEach(([name, defaultValue]) => {
Expand Down
1 change: 1 addition & 0 deletions src/main/ipc/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ enum IPCMessages {
LOAD_KEYSTORE = 'LOAD_KEYSTORE',
GET_LEDGER_ADDRESS = 'GET_LEDGER_ADDRESS',
SEND_LEDGER_TX = 'SEND_LEDGER_TX',
GET_TRANSPORT = 'GET_TRANSPORT',
UPDATE_AVAILABLE = 'UPDATE_AVAILABLE',
APP_CHECK_FOR_UPDATE = 'APP_CHECK_FOR_UPDATE',
/**
Expand Down
6 changes: 5 additions & 1 deletion src/renderer/hooks/useAppUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@ export const useAppUpdate = () => {
const resetAppUpdater = () => setAppUpdater(RD.initial)

const checkForUpdates = useCallback(() => {
FP.pipe(
const subscription = FP.pipe(
Rx.from(window.apiAppUpdate.checkForAppUpdates()),
RxOp.catchError((e) => Rx.of(RD.failure(new Error(e.message)))),
RxOp.startWith(RD.pending)
).subscribe(setAppUpdater)

return () => {
subscription.unsubscribe()
}
}, [setAppUpdater])

return {
Expand Down
77 changes: 77 additions & 0 deletions src/renderer/hooks/useLedger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useCallback, useMemo } from 'react'

import * as RD from '@devexperts/remote-data-ts'
import Transport from '@ledgerhq/hw-transport'
import THORChainApp from '@thorchain/ledger-thorchain'
import { getPrefix as getTHORPrefix } from '@xchainjs/xchain-thorchain'
import { Chain } from '@xchainjs/xchain-util'
import * as E from 'fp-ts/Either'
import * as FP from 'fp-ts/function'
import { useObservableState } from 'observable-hooks'
import * as Rx from 'rxjs'
import * as RxOp from 'rxjs/operators'

import { LedgerErrorId, Network } from '../../shared/api/types'
import { useAppContext } from '../contexts/AppContext'
import { observableState } from '../helpers/stateHelper'
import { toClientNetwork } from '../services/clients'
import { DEFAULT_NETWORK } from '../services/const'
import { LedgerAddressRD } from '../services/wallet/types'

// TODO(@veado) Get path by using `xchain-thorchain`
const PATH = [44, 931, 0, 0, 0]

export const getTHORAddress = async (transport: Transport, network: Network) => {
try {
const app = new THORChainApp(transport)
const clientNetwork = toClientNetwork(network)
const response = await app.getAddressAndPubKey(PATH, getTHORPrefix(clientNetwork))
if (response.return_code !== 0x9000) {
// TODO(@Veado) get address from pubkey
return E.right('my-address')
} else {
return E.left(LedgerErrorId.UNKNOWN)
}
} catch (error) {
return E.left(LedgerErrorId.WRONG_APP)
}
}

export const useLedger = () => {
const { network$ } = useAppContext()
const network = useObservableState<Network>(network$, DEFAULT_NETWORK)

const { get$: address$, set: setAddress } = useMemo(() => observableState<LedgerAddressRD>(RD.initial), [])

const addressRD = useObservableState(FP.pipe(address$, RxOp.shareReplay(1)), RD.initial)

const getAddress = useCallback(
(_chain: Chain) => {
FP.pipe(
Rx.from(window.apiHDWallet.getTransport()),
RxOp.switchMap((transport) => Rx.from(getTHORAddress(transport, network))),
RxOp.map(RD.fromEither),
RxOp.startWith(RD.pending),
RxOp.catchError((error) => Rx.of(RD.failure(error)))
).subscribe(setAddress)
},
[network, setAddress]
)

// const getAddress = useCallback(
// (chain: Chain) => {
// FP.pipe(
// Rx.from(window.apiHDWallet.getLedgerAddress(chain, network)),
// RxOp.map(RD.fromEither),
// RxOp.startWith(RD.pending),
// RxOp.catchError((error) => Rx.of(RD.failure(error)))
// ).subscribe(setAddress)
// },
// [network, setAddress]
// )

return {
getAddress,
address: addressRD
}
}
44 changes: 28 additions & 16 deletions src/renderer/views/wallet/SettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import * as Rx from 'rxjs'
import * as RxOp from 'rxjs/operators'

import { Network } from '../../../shared/api/types'
import { Button } from '../../components/uielements/button'
import { Settings } from '../../components/wallet/settings'
import { useAppContext } from '../../contexts/AppContext'
import { useBinanceContext } from '../../contexts/BinanceContext'
Expand All @@ -24,6 +25,7 @@ import { useThorchainContext } from '../../contexts/ThorchainContext'
import { useWalletContext } from '../../contexts/WalletContext'
import { filterEnabledChains } from '../../helpers/chainHelper'
import { sequenceTOptionFromArray } from '../../helpers/fpHelpers'
import { useLedger } from '../../hooks/useLedger'
import { DEFAULT_NETWORK } from '../../services/const'
import { getPhrase } from '../../services/wallet/util'
import { UserAccountType } from '../../types/wallet'
Expand Down Expand Up @@ -260,22 +262,32 @@ export const SettingsView: React.FC = (): JSX.Element => {
}
}

const { getAddress: getLedgerAddress, address: ledgerAddressRD } = useLedger()

return (
<Row>
<Col span={24}>
<Settings
selectedNetwork={network}
lockWallet={lock}
removeKeystore={removeKeystore}
exportKeystore={exportKeystore}
runeNativeAddress={runeNativeAddress}
userAccounts={userAccounts}
phrase={phrase}
clickAddressLinkHandler={clickAddressLinkHandler}
validatePassword$={validatePassword$}
ClientSettingsView={ClientSettingsView}
/>
</Col>
</Row>
<>
<Row>
<Col>
<Button onClick={() => getLedgerAddress(THORChain)}>Get Ledger THOR address</Button>
</Col>
<Col>ledgerAddressRD: {JSON.stringify(ledgerAddressRD)}</Col>
</Row>
<Row>
<Col span={24}>
<Settings
selectedNetwork={network}
lockWallet={lock}
removeKeystore={removeKeystore}
exportKeystore={exportKeystore}
runeNativeAddress={runeNativeAddress}
userAccounts={userAccounts}
phrase={phrase}
clickAddressLinkHandler={clickAddressLinkHandler}
validatePassword$={validatePassword$}
ClientSettingsView={ClientSettingsView}
/>
</Col>
</Row>
</>
)
}
2 changes: 2 additions & 0 deletions src/shared/api/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as RD from '@devexperts/remote-data-ts'
import Transport from '@ledgerhq/hw-transport'
import { Address, FeeRate, TxParams } from '@xchainjs/xchain-client'
import { Keystore } from '@xchainjs/xchain-crypto'
import { Chain } from '@xchainjs/xchain-util'
Expand Down Expand Up @@ -91,6 +92,7 @@ export type LedgerTxInfo = LedgerBTCTxInfo | LedgerBNCTxInfo
export type ApiHDWallet = {
getLedgerAddress: (chain: Chain, network: Network) => Promise<Either<LedgerErrorId, Address>>
sendTxInLedger: (chain: Chain, network: Network, txInfo: LedgerTxInfo) => Promise<Either<LedgerErrorId, string>>
getTransport: () => Promise<Transport>
}

declare global {
Expand Down
4 changes: 3 additions & 1 deletion src/shared/mock/api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Transport from '@ledgerhq/hw-transport'
import { Keystore } from '@xchainjs/xchain-crypto'
import * as E from 'fp-ts/Either'

Expand Down Expand Up @@ -77,5 +78,6 @@ export const apiUrl: ApiUrl = {
// Mock `apiHDWallet`
export const apiHDWallet: ApiHDWallet = {
getLedgerAddress: () => Promise.resolve(E.right('ledger_address')),
sendTxInLedger: () => Promise.resolve(E.right('tx_hash'))
sendTxInLedger: () => Promise.resolve(E.right('tx_hash')),
getTransport: () => Promise.resolve({} as Transport)
}
1 change: 1 addition & 0 deletions src/shared/thorchain-ledger.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module '@thorchain/ledger-thorchain'
Loading

0 comments on commit 79ffafc

Please sign in to comment.