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

Prepare thorchain-ledger integration #1643

Closed
wants to merge 5 commits into from
Closed
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
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,13 @@
"@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-node-hid-singleton": "^6.2.0",
"@openapitools/openapi-generator-cli": "^2.3.5",
"@psf/bitcoincashjs-lib": "^4.0.2",
"@thorchain/asgardex-midgard": "^1.1.0",
"@thorchain/asgardex-theme": "^0.1.1",
"@thorchain/asgardex-util": "^0.9.1",
"@thorchain/ledger-thorchain": "^0.0.3",
"@types/electron-devtools-installer": "^2.2.0",
"@xchainjs/xchain-binance": "^5.2.4",
"@xchainjs/xchain-bitcoin": "0.15.11",
Expand Down Expand Up @@ -167,6 +169,7 @@
"ts-loader": "^8.3.0",
"ts-node": "^10.0.0",
"wait-on": "^5.3.0",
"webpack-cli": "^4.7.2"
"webpack-cli": "^4.7.2",
"webpack-node-externals": "^3.0.0"
}
}
2 changes: 2 additions & 0 deletions resources/mac/entitlements.mac.plist
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
<dict>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
5 changes: 2 additions & 3 deletions src/main/api/hdwallet.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { Chain } from '@xchainjs/xchain-util'
import { ipcRenderer } from 'electron'

import { LedgerTxInfo, Network } from '../../shared/api/types'
import { GetLedgerAddressParams, LedgerTxInfo, Network } from '../../shared/api/types'
import { ApiHDWallet } from '../../shared/api/types'
import IPCMessages from '../ipc/messages'

export const apiHDWallet: ApiHDWallet = {
getLedgerAddress: (chain: Chain, network: Network) =>
ipcRenderer.invoke(IPCMessages.GET_LEDGER_ADDRESS, chain, network),
getLedgerAddress: (args: GetLedgerAddressParams) => ipcRenderer.invoke(IPCMessages.GET_LEDGER_ADDRESS, args),
sendTxInLedger: (chain: Chain, network: Network, txInfo: LedgerTxInfo) =>
ipcRenderer.invoke(IPCMessages.SEND_LEDGER_TX, chain, network, txInfo)
}
51 changes: 22 additions & 29 deletions src/main/api/ledger/address.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,25 @@
// 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 TransportNodeHidSingleton from '@ledgerhq/hw-transport-node-hid-singleton'
import { 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 { getAddress as getBNBAddress } from './binance'
// import { getAddress as getBTCAddress } from './bitcoin'
// import { getErrorId } from './utils'
import { GetLedgerAddressParams, LedgerErrorId } from '../../../shared/api/types'
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()))
// }
export const getAddress = async ({ chain, network, index }: GetLedgerAddressParams) => {
try {
let res: E.Either<LedgerErrorId, string>
const transport = await TransportNodeHidSingleton.open()
switch (chain) {
case THORChain:
res = await getTHORAddress({ transport, network, index })
break
default:
res = E.left(LedgerErrorId.NO_APP)
}
await transport.close()
return res
} catch (error) {
return E.left(getErrorId(error.toString()))
}
}
4 changes: 4 additions & 0 deletions src/main/api/ledger/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* Derivation path by given `index` (similar to "44'/931'/0'/0/{index}")
*/
export const getPath = (index = 0) => [44, 931, 0, 0, index]
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 './transport'
32 changes: 32 additions & 0 deletions src/main/api/ledger/thorchain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Transport from '@ledgerhq/hw-transport'
import THORChainApp from '@thorchain/ledger-thorchain'
import { getPrefix } from '@xchainjs/xchain-thorchain'
import * as E from 'fp-ts/Either'

import { Network } from '../../../shared/api/types'
import { toClientNetwork } from '../../../shared/utils/utils'
import { getPath } from './common'
import { getErrorId } from './utils'

export const getAddress = async ({
transport,
network,
index
}: {
transport: Transport
network: Network
index: number
}) => {
try {
const app = new THORChainApp(transport)
const clientNetwork = toClientNetwork(network)
const prefix = getPrefix(clientNetwork)
const res = await app.getAddressAndPubKey(getPath(index), prefix)
if (!res.bech32_address || res.return_code !== 0x9000 /* ERROR_CODE.NoError */) {
return E.left(res.return_code.toString())
}
return E.right(res.bech32_address)
} catch (error) {
return E.left(getErrorId(error))
}
}
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
3 changes: 3 additions & 0 deletions src/main/api/ledger/transport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import TransportNodeHidSingleton from '@ledgerhq/hw-transport-node-hid-singleton'

export const getTransport = async () => await TransportNodeHidSingleton.open()
18 changes: 13 additions & 5 deletions src/main/electron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@ import isDev from 'electron-is-dev'
import log from 'electron-log'
import { warn } from 'electron-log'

import { LedgerTxInfo, Network, StoreFileName } from '../shared/api/types'
import { GetLedgerAddressParams, LedgerTxInfo, Network, StoreFileName } from '../shared/api/types'
import { DEFAULT_STORAGES } from '../shared/const'
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,13 @@ 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, async (_, args: GetLedgerAddressParams) => getLedgerAddress(args))
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
2 changes: 1 addition & 1 deletion src/renderer/helpers/addressHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { ethers } from 'ethers'
import * as O from 'fp-ts/lib/Option'

import { Network } from '../../shared/api/types'
import { toClientNetwork } from '../services/clients'
import { toClientNetwork } from '../../shared/utils/utils'

export const truncateAddress = (addr: Address, chain: Chain, network: Network): string => {
const first = addr.substr(0, Math.max(getAddressPrefixLength(chain, network) + 3, 6))
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
37 changes: 37 additions & 0 deletions src/renderer/hooks/useLedger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useCallback, useMemo } from 'react'

import * as RD from '@devexperts/remote-data-ts'
import { Chain } from '@xchainjs/xchain-util'
import * as FP from 'fp-ts/function'
import { useObservableState } from 'observable-hooks'
import * as Rx from 'rxjs'
import * as RxOp from 'rxjs/operators'

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

export const useLedger = (chain: Chain, index: number) => {
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(() => {
FP.pipe(
Rx.from(window.apiHDWallet.getLedgerAddress({ chain, network, index })),
RxOp.map(RD.fromEither),
RxOp.startWith(RD.pending),
RxOp.catchError((error) => Rx.of(RD.failure(error)))
).subscribe(setAddress)
}, [chain, index, network, setAddress])

return {
getAddress,
address: addressRD
}
}
2 changes: 1 addition & 1 deletion src/renderer/services/app/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { startWith, mapTo, distinctUntilChanged } from 'rxjs/operators'
import * as RxOp from 'rxjs/operators'

import { Network } from '../../../shared/api/types'
import { toClientNetwork } from '../../../shared/utils/utils'
import { observableState } from '../../helpers/stateHelper'
import { SlipTolerance } from '../../types/asgardex'
import { toClientNetwork } from '../clients'
import { DEFAULT_NETWORK, DEFAULT_SLIP_TOLERANCE } from '../const'
import { Network$, SlipTolerance$, OnlineStatus } from './types'

Expand Down
4 changes: 2 additions & 2 deletions src/renderer/services/binance/ledger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import { LedgerService } from './types'

const { get$: ledgerAddress$, set: setLedgerAddressRD } = observableState<LedgerAddressRD>(RD.initial)

const retrieveLedgerAddress = (network: Network) =>
const retrieveLedgerAddress = (network: Network, index: number) =>
FP.pipe(
Rx.from(window.apiHDWallet.getLedgerAddress(BNBChain, network)),
Rx.from(window.apiHDWallet.getLedgerAddress({ chain: BNBChain, network, index })),
map(RD.fromEither),
startWith(RD.pending),
catchError((error) => Rx.of(RD.failure(error)))
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/services/binance/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export type TransactionService = C.TransactionService<SendTxParams>

export type LedgerService = {
ledgerAddress$: LedgerAddressLD
retrieveLedgerAddress: (network: Network) => void
retrieveLedgerAddress: (network: Network, index: number) => void
removeLedgerAddress: () => void
ledgerTxRD$: LedgerTxHashLD
pushLedgerTx: (network: Network, params: LedgerBNCTxInfo) => Rx.Subscription
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/services/bitcoin/ledger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import { LedgerService } from './types'

const { get$: ledgerAddress$, set: setLedgerAddressRD } = observableState<LedgerAddressRD>(RD.initial)

const retrieveLedgerAddress = (network: Network) =>
const retrieveLedgerAddress = (network: Network, index: number) =>
FP.pipe(
Rx.from(window.apiHDWallet.getLedgerAddress(BTCChain, network)),
Rx.from(window.apiHDWallet.getLedgerAddress({ chain: BTCChain, network, index })),
map(RD.fromEither),
startWith(RD.pending),
catchError((error) => Rx.of(RD.failure(error)))
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/services/bitcoin/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export type FeesService = C.FeesService & {

export type LedgerService = {
ledgerAddress$: LedgerAddressLD
retrieveLedgerAddress: (network: Network) => void
retrieveLedgerAddress: (network: Network, index: number) => void
removeLedgerAddress: () => void
ledgerTxRD$: LedgerTxHashLD
pushLedgerTx: (network: Network, params: LedgerBTCTxInfo) => Rx.Subscription
Expand Down
8 changes: 4 additions & 4 deletions src/renderer/services/chain/ledger.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { BNBChain, BTCChain, Chain } from '@xchainjs/xchain-util'

import { GetLedgerAddressParams } from '../../../shared/api/types'
import { network$ } from '../app/service'
import * as BNB from '../binance'
import * as BTC from '../bitcoin'
import { LedgerAddressParams } from './types'

const retrieveLedgerAddress = ({ chain, network }: LedgerAddressParams): void => {
const retrieveLedgerAddress = ({ chain, network, index }: GetLedgerAddressParams): void => {
switch (chain) {
case BTCChain:
return BTC.retrieveLedgerAddress(network)
return BTC.retrieveLedgerAddress(network, index)
case BNBChain:
return BNB.retrieveLedgerAddress(network)
return BNB.retrieveLedgerAddress(network, index)
default:
break
}
Expand Down
2 changes: 0 additions & 2 deletions src/renderer/services/chain/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,6 @@ export type SendPoolTxParams = SendTxParams & {
router: O.Option<Address>
}

export type LedgerAddressParams = { chain: Chain; network: Network }

/**
* State to reflect status of a swap by doing different requests
*/
Expand Down
1 change: 0 additions & 1 deletion src/renderer/services/clients/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export * from './address'
export * from './common'
export * from './types'
export * from './utils'
export * from './balances'
export * from './transaction'
export * from './fees'
2 changes: 1 addition & 1 deletion src/renderer/services/ethereum/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import * as Rx from 'rxjs'
import * as RxOp from 'rxjs/operators'

import { Network } from '../../../shared/api/types'
import { toClientNetwork } from '../../../shared/utils/utils'
import { envOrDefault } from '../../helpers/envHelper'
import { clientNetwork$ } from '../app/service'
import * as C from '../clients'
import { Address$, ExplorerUrl$ } from '../clients/types'
import { toClientNetwork } from '../clients/utils'
import { keystoreService } from '../wallet/keystore'
import { getPhrase } from '../wallet/util'
import { Client$, ClientState, ClientState$ } from './types'
Expand Down
Loading