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

Commit

Permalink
Handle BCH Ledger addresses (#2133)
Browse files Browse the repository at this point in the history
- [x] Add / verify Ledger BCH address
- [x] Update CHANGELOG / README
  • Loading branch information
veado authored Mar 9, 2022
1 parent e8fa8d1 commit a0b684b
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 10 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 0.10.0 (2022-03-xx)

## Add

- [Ledger] BCH support [#2131](https://github.com/thorchain/asgardex-electron/issues/2131)

# 0.9.1 (2022-03-08)

## Add
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Symbols:
| | THOR | BNB / BEP2 | BTC | BCH | DOGE | ETH / ERC20 | LTC | TERRA |
| -------------- | ------------------ | ------------------ | ------------------ | ------------------ | ------------------ | ------------------ | ------------------ | ------------------ |
| Keystore | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :white_check_mark: |
| Ledger \* | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :heavy_check_mark: | :white_check_mark: |
| Ledger \* | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :white_check_mark: | :white_check_mark: | :heavy_check_mark: | :white_check_mark: |
| Send \*\* | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :white_check_mark: |
| Receive | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :white_check_mark: |
| Upgrade \*\*\* | :heavy_check_mark: | :heavy_check_mark: | - | - | - | :heavy_check_mark: | - | - |
Expand All @@ -33,7 +33,7 @@ Symbols:
| History | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :white_check_mark: |
| Synths | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |

(\*) Ledger `THOR` not supported on `stagenet`. Ledger `LTC` not supported on `testnet`
(\*) Ledger `THOR` not supported at `stagenet`. Ledger `LTC`/`BCH` not supported at `testnet`

(\*\*) With or without memo

Expand Down
9 changes: 8 additions & 1 deletion src/main/api/ledger/address.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import TransportNodeHidSingleton from '@ledgerhq/hw-transport-node-hid-singleton'
import { BNBChain, BTCChain, LTCChain, THORChain } from '@xchainjs/xchain-util'
import { BCHChain, BNBChain, BTCChain, LTCChain, THORChain } from '@xchainjs/xchain-util'
import * as E from 'fp-ts/Either'

import { IPCLedgerAdddressParams, LedgerError, LedgerErrorId } from '../../../shared/api/types'
import { isError } from '../../../shared/utils/guard'
import { WalletAddress } from '../../../shared/wallet/types'
import { getAddress as getBNBAddress, verifyAddress as verifyBNBAddress } from './binance/address'
import { getAddress as getBTCAddress, verifyAddress as verifyBTCAddress } from './bitcoin/address'
import { getAddress as getBCHAddress, verifyAddress as verifyBCHAddress } from './bitcoincash/address'
import { getAddress as getLTCAddress, verifyAddress as verifyLTCAddress } from './litecoin/address'
import { getAddress as getTHORAddress, verifyAddress as verifyTHORAddress } from './thorchain/address'

Expand All @@ -31,6 +32,9 @@ export const getAddress = async ({
case LTCChain:
res = await getLTCAddress(transport, network, walletIndex)
break
case BCHChain:
res = await getBCHAddress(transport, network, walletIndex)
break
default:
res = E.left({
errorId: LedgerErrorId.NOT_IMPLEMENTED,
Expand Down Expand Up @@ -62,6 +66,9 @@ export const verifyLedgerAddress = async ({ chain, network, walletIndex }: IPCLe
case LTCChain:
verifyLTCAddress(transport, network, walletIndex)
break
case BCHChain:
verifyBCHAddress(transport, network, walletIndex)
break
}
await transport.close()
}
47 changes: 47 additions & 0 deletions src/main/api/ledger/bitcoincash/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import AppBTC from '@ledgerhq/hw-app-btc'
import type Transport from '@ledgerhq/hw-transport'
import { BCHChain } from '@xchainjs/xchain-util'
import * as E from 'fp-ts/Either'

import { LedgerError, LedgerErrorId, Network } from '../../../../shared/api/types'
import { toClientNetwork } from '../../../../shared/utils/client'
import { isError } from '../../../../shared/utils/guard'
import { WalletAddress } from '../../../../shared/wallet/types'
import { getDerivationPath } from './common'

export const getAddress = async (
transport: Transport,
network: Network,
walletIndex: number
): Promise<E.Either<LedgerError, WalletAddress>> => {
try {
const app = new AppBTC(transport)
const clientNetwork = toClientNetwork(network)
const derivePath = getDerivationPath(walletIndex, clientNetwork)
const { bitcoinAddress: bchAddress } = await app.getWalletPublicKey(derivePath, {
// 'cashaddr' in case of Bitcoin Cash
// @see https://github.com/LedgerHQ/ledgerjs/blob/master/packages/hw-app-btc/README.md#parameters-2
format: 'cashaddr'
})
return E.right({ address: bchAddress, chain: BCHChain, type: 'ledger', walletIndex })
} catch (error) {
return E.left({
errorId: LedgerErrorId.GET_ADDRESS_FAILED,
msg: `Could not get address from Ledger's BCH app: ${
isError(error) ? error?.message ?? error.toString() : `${error}`
}`
})
}
}

export const verifyAddress = async (transport: Transport, network: Network, walletIndex: number): Promise<void> => {
const app = new AppBTC(transport)
const clientNetwork = toClientNetwork(network)
const derivePath = getDerivationPath(walletIndex, clientNetwork)
const _ = await app.getWalletPublicKey(derivePath, {
// 'cashaddr' in case of Bitcoin Cash
// @see https://github.com/LedgerHQ/ledgerjs/blob/master/packages/hw-app-btc/README.md#parameters-2
format: 'cashaddr',
verify: true // confirm the address on the device
})
}
23 changes: 23 additions & 0 deletions src/main/api/ledger/bitcoincash/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Network } from '@xchainjs/xchain-client'

import { LedgerErrorId } from '../../../../shared/api/types'

// TODO(@veado) Extend`xchain-bitcoincash` to get derivation path from it
// Similar to default values in `Client` of `xchain-bitcoincash`
// see https://github.com/xchainjs/xchainjs-lib/blob/56adf1e0d6ceab0bdf93f53fe808fe45bf79930f/packages/xchain-bitcoincash/src/client.ts#L65-L69
export const getDerivationPath = (walletIndex: number, network: Network): string => {
const DERIVATION_PATHES = {
[Network.Mainnet]: ["44'", "145'", "0'", 0, walletIndex],
[Network.Testnet]: ["44'", "1'", "0'", 0, walletIndex],
[Network.Stagenet]: ["44'", "145'", "0'", 0, walletIndex]
}
const path = DERIVATION_PATHES[network].join('/')
return path
}

export const fromLedgerErrorType = (error: number): LedgerErrorId => {
switch (error) {
default:
return LedgerErrorId.UNKNOWN
}
}
11 changes: 9 additions & 2 deletions src/renderer/components/wallet/settings/WalletSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,14 @@ import { RemoveWalletConfirmationModal } from '../../../components/modal/confirm
import { AssetIcon } from '../../../components/uielements/assets/assetIcon/AssetIcon'
import { QRCodeModal } from '../../../components/uielements/qrCodeModal/QRCodeModal'
import { PhraseCopyModal } from '../../../components/wallet/phrase/PhraseCopyModal'
import { getChainAsset, isBnbChain, isBtcChain, isLtcChain, isThorChain } from '../../../helpers/chainHelper'
import {
getChainAsset,
isBchChain,
isBnbChain,
isBtcChain,
isLtcChain,
isThorChain
} from '../../../helpers/chainHelper'
import { isEnabledWallet } from '../../../helpers/walletHelper'
import { ValidatePasswordHandler, WalletAccounts, WalletAddressAsync } from '../../../services/wallet/types'
import { walletTypeToI18n } from '../../../services/wallet/util'
Expand Down Expand Up @@ -136,7 +143,7 @@ export const WalletSettings: React.FC<Props> = (props): JSX.Element => {
<Styled.AddLedgerButton loading={loading} onClick={() => addLedgerAddress(chain, walletIndexMap[chain])}>
<Styled.AddLedgerIcon /> {intl.formatMessage({ id: 'ledger.add.device' })}
</Styled.AddLedgerButton>
{(isBnbChain(chain) || isThorChain(chain) || isBtcChain(chain) || isLtcChain(chain)) && (
{(isBnbChain(chain) || isThorChain(chain) || isBtcChain(chain) || isLtcChain(chain) || isBchChain(chain)) && (
<>
<Styled.IndexLabel>{intl.formatMessage({ id: 'setting.wallet.index' })}</Styled.IndexLabel>
<Styled.WalletIndexInput
Expand Down
11 changes: 9 additions & 2 deletions src/renderer/helpers/walletHelper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
LTCChain,
THORChain,
BTCChain,
BNBChain
BNBChain,
BCHChain
} from '@xchainjs/xchain-util'
import * as FP from 'fp-ts/lib/function'
import * as NEA from 'fp-ts/lib/NonEmptyArray'
Expand Down Expand Up @@ -210,7 +211,13 @@ describe('walletHelper', () => {
expect(isEnabledWallet(LTCChain, 'mainnet', 'ledger')).toBeTruthy()
expect(isEnabledWallet(LTCChain, 'stagenet', 'ledger')).toBeTruthy()
})

it('BCH ledger testnet -> false', () => {
expect(isEnabledWallet(BCHChain, 'testnet', 'ledger')).toBeFalsy()
})
it('BCH ledger mainnet/stagenet -> true', () => {
expect(isEnabledWallet(BCHChain, 'mainnet', 'ledger')).toBeTruthy()
expect(isEnabledWallet(BCHChain, 'stagenet', 'ledger')).toBeTruthy()
})
it('BTC ledger -> true', () => {
expect(isEnabledWallet(BTCChain, 'mainnet', 'ledger')).toBeTruthy()
expect(isEnabledWallet(BTCChain, 'testnet', 'ledger')).toBeTruthy()
Expand Down
4 changes: 3 additions & 1 deletion src/renderer/helpers/walletHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ZERO_ASSET_AMOUNT } from '../const'
import { WalletBalances } from '../services/clients'
import { NonEmptyWalletBalances, WalletBalance } from '../services/wallet/types'
import { isBnbAsset, isEthAsset, isLtcAsset, isRuneNativeAsset } from './assetHelper'
import { isLtcChain, isThorChain } from './chainHelper'
import { isBchChain, isLtcChain, isThorChain } from './chainHelper'
import { eqAddress, eqAsset, eqWalletType } from './fp/eq'

/**
Expand Down Expand Up @@ -157,5 +157,7 @@ export const isEnabledWallet = (chain: Chain, network: Network, walletType: Wall
// Disable LTC ledger wallets in testnet
// It seems Ledger can not derive LTC addresses on Testnet properly
if (isLtcChain(chain) && network === 'testnet' && isLedgerWallet(walletType)) return false
// Same for BCH - no Ledger support for `testnet`
if (isBchChain(chain) && network === 'testnet' && isLedgerWallet(walletType)) return false
return true
}
37 changes: 35 additions & 2 deletions src/renderer/views/wallet/WalletSettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@ import { useEthereumContext } from '../../contexts/EthereumContext'
import { useLitecoinContext } from '../../contexts/LitecoinContext'
import { useThorchainContext } from '../../contexts/ThorchainContext'
import { useWalletContext } from '../../contexts/WalletContext'
import { filterEnabledChains, isBnbChain, isBtcChain, isLtcChain, isThorChain } from '../../helpers/chainHelper'
import {
filterEnabledChains,
isBchChain,
isBnbChain,
isBtcChain,
isLtcChain,
isThorChain
} from '../../helpers/chainHelper'
import { sequenceTOptionFromArray } from '../../helpers/fpHelpers'
import { useLedger } from '../../hooks/useLedger'
import { DEFAULT_NETWORK } from '../../services/const'
Expand Down Expand Up @@ -89,11 +96,19 @@ export const WalletSettingsView: React.FC = (): JSX.Element => {
removeAddress: removeLedgerLtcAddress
} = useLedger(LTCChain)

const {
askAddress: askLedgerBchAddress,
verifyAddress: verifyLedgerBchAddress,
address: bchLedgerAddressRD,
removeAddress: removeLedgerBchAddress
} = useLedger(BCHChain)

const addLedgerAddressHandler = (chain: Chain, walletIndex: number) => {
if (isThorChain(chain)) return askLedgerThorAddress(walletIndex)
if (isBnbChain(chain)) return askLedgerBnbAddress(walletIndex)
if (isBtcChain(chain)) return askLedgerBtcAddress(walletIndex)
if (isLtcChain(chain)) return askLedgerLtcAddress(walletIndex)
if (isBchChain(chain)) return askLedgerBchAddress(walletIndex)

return FP.constVoid
}
Expand All @@ -103,6 +118,7 @@ export const WalletSettingsView: React.FC = (): JSX.Element => {
if (isBnbChain(chain)) return verifyLedgerBnbAddress(walletIndex)
if (isBtcChain(chain)) return verifyLedgerBtcAddress(walletIndex)
if (isLtcChain(chain)) return verifyLedgerLtcAddress(walletIndex)
if (isBchChain(chain)) return verifyLedgerBchAddress(walletIndex)

return FP.constVoid
}
Expand All @@ -112,6 +128,7 @@ export const WalletSettingsView: React.FC = (): JSX.Element => {
if (isBnbChain(chain)) return removeLedgerBnbAddress()
if (isBtcChain(chain)) return removeLedgerBtcAddress()
if (isLtcChain(chain)) return removeLedgerLtcAddress()
if (isBchChain(chain)) return removeLedgerBchAddress()

return FP.constVoid
}
Expand Down Expand Up @@ -203,6 +220,17 @@ export const WalletSettingsView: React.FC = (): JSX.Element => {
[intl, ltcLedgerAddressRD]
)

const bchLedgerWalletAddress: WalletAddressAsync = useMemo(
() => ({
type: 'ledger',
address: FP.pipe(
bchLedgerAddressRD,
RD.mapLeft(({ errorId, msg }) => Error(`${ledgerErrorIdToI18n(errorId, intl)} (${msg})`))
)
}),
[intl, bchLedgerAddressRD]
)

const walletAccounts$ = useMemo(() => {
const thorWalletAccount$ = walletAccount$({
addressUI$: thorAddressUI$,
Expand All @@ -220,7 +248,11 @@ export const WalletSettingsView: React.FC = (): JSX.Element => {
ledgerAddress: bnbLedgerWalletAddress,
chain: BNBChain
})
const bchWalletAccount$ = walletAccount$({ addressUI$: bchAddressUI$, chain: BCHChain })
const bchWalletAccount$ = walletAccount$({
addressUI$: bchAddressUI$,
ledgerAddress: bchLedgerWalletAddress,
chain: BCHChain
})
const ltcWalletAccount$ = walletAccount$({
addressUI$: ltcAddressUI$,
ledgerAddress: ltcLedgerWalletAddress,
Expand Down Expand Up @@ -253,6 +285,7 @@ export const WalletSettingsView: React.FC = (): JSX.Element => {
bnbAddressUI$,
bnbLedgerWalletAddress,
bchAddressUI$,
bchLedgerWalletAddress,
ltcAddressUI$,
ltcLedgerWalletAddress,
dogeAddressUI$
Expand Down

0 comments on commit a0b684b

Please sign in to comment.