Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ledger fixes and improvements #5444

Merged
merged 24 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7a7547c
fix: useApprovalTx pass AccountId account as from down to getFees()
gomesalexandre Oct 11, 2023
de9c709
feat: ledger csp
gomesalexandre Oct 11, 2023
80bfb0a
feat: lerna boimp
gomesalexandre Oct 11, 2023
778d606
feat: boimp
gomesalexandre Oct 11, 2023
85305f2
feat: boimp yarn.lock
gomesalexandre Oct 11, 2023
1f4754b
feat: bump yarn.lock
gomesalexandre Oct 11, 2023
b46664c
feat: boimp
gomesalexandre Oct 12, 2023
1da00ba
feat: subscribeTxs pass pubKey for EVM chains
gomesalexandre Oct 12, 2023
08f82e1
feat: subscribeTxs pass pubKey for UTXO chains
gomesalexandre Oct 12, 2023
eeb1795
feat: add close button
gomesalexandre Oct 12, 2023
6f2886e
feat: lowerCase send and receive addresses in verify
gomesalexandre Oct 12, 2023
fd2a8ef
fix: lint
gomesalexandre Oct 12, 2023
860988a
feat: verify addresses step
gomesalexandre Oct 12, 2023
3c8663e
feat: improve verify address oil
gomesalexandre Oct 12, 2023
8265e45
feat: missing pubKey for utxo swaps
gomesalexandre Oct 12, 2023
c9c621b
fix: getPublicKey pubKey or xpub in getTradeQuoteArgs
gomesalexandre Oct 12, 2023
9ed938f
feat: reorder buy and sell address
gomesalexandre Oct 12, 2023
df02f89
feat: don't check for supportsETH(wallet) in getApprovalTxData
gomesalexandre Oct 13, 2023
9b4e8b6
Merge remote-tracking branch 'origin/develop' into feat_ledger_improv…
gomesalexandre Oct 13, 2023
0ec94e7
Merge remote-tracking branch 'origin/develop' into feat_ledger_improv…
gomesalexandre Oct 16, 2023
39f02bf
Merge remote-tracking branch 'origin/develop' into feat_ledger_improv…
gomesalexandre Oct 18, 2023
7d35ef9
feat: bump
gomesalexandre Oct 18, 2023
6ec9691
feat: bump
gomesalexandre Oct 18, 2023
39c3a7a
feat: revert yarnrc
gomesalexandre Oct 18, 2023
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
28 changes: 14 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,20 +93,20 @@
"@shapeshiftoss/caip": "workspace:^",
"@shapeshiftoss/chain-adapters": "workspace:^",
"@shapeshiftoss/errors": "workspace:^",
"@shapeshiftoss/hdwallet-coinbase": "^1.52.2",
"@shapeshiftoss/hdwallet-core": "^1.52.2",
"@shapeshiftoss/hdwallet-keepkey": "^1.52.2",
"@shapeshiftoss/hdwallet-keepkey-webusb": "^1.52.2",
"@shapeshiftoss/hdwallet-keplr": "^1.52.2",
"@shapeshiftoss/hdwallet-ledger": "^1.52.2",
"@shapeshiftoss/hdwallet-ledger-webhid": "^1.52.2",
"@shapeshiftoss/hdwallet-ledger-webusb": "^1.52.2",
"@shapeshiftoss/hdwallet-metamask": "^1.52.2",
"@shapeshiftoss/hdwallet-native": "^1.52.2",
"@shapeshiftoss/hdwallet-native-vault": "^1.52.2",
"@shapeshiftoss/hdwallet-shapeshift-multichain": "^1.52.2",
"@shapeshiftoss/hdwallet-walletconnectv2": "^1.52.2",
"@shapeshiftoss/hdwallet-xdefi": "^1.52.2",
"@shapeshiftoss/hdwallet-coinbase": "1.52.5",
"@shapeshiftoss/hdwallet-core": "1.52.5",
"@shapeshiftoss/hdwallet-keepkey": "1.52.5",
"@shapeshiftoss/hdwallet-keepkey-webusb": "1.52.5",
"@shapeshiftoss/hdwallet-keplr": "1.52.5",
"@shapeshiftoss/hdwallet-ledger": "1.52.5",
"@shapeshiftoss/hdwallet-ledger-webhid": "1.52.5",
"@shapeshiftoss/hdwallet-ledger-webusb": "1.52.5",
"@shapeshiftoss/hdwallet-metamask": "1.52.5",
"@shapeshiftoss/hdwallet-native": "1.52.5",
"@shapeshiftoss/hdwallet-native-vault": "1.52.5",
"@shapeshiftoss/hdwallet-shapeshift-multichain": "1.52.5",
"@shapeshiftoss/hdwallet-walletconnectv2": "1.52.5",
"@shapeshiftoss/hdwallet-xdefi": "1.52.5",
"@shapeshiftoss/types": "workspace:^",
"@shapeshiftoss/unchained-client": "workspace:^",
"@sniptt/monads": "^0.5.10",
Expand Down
4 changes: 2 additions & 2 deletions packages/chain-adapters/src/evm/EvmBaseAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -554,9 +554,9 @@ export abstract class EvmBaseAdapter<T extends EvmChainId> implements IChainAdap
onMessage: (msg: Transaction) => void,
onError: (err: SubscribeError) => void,
): Promise<void> {
const { accountNumber, wallet } = input
const { pubKey, accountNumber, wallet } = input

const address = await this.getAddress({ accountNumber, wallet })
const address = await this.getAddress({ accountNumber, wallet, pubKey })
const bip44Params = this.getBIP44Params({ accountNumber })
const subscriptionId = toRootDerivationPath(bip44Params)

Expand Down
1 change: 1 addition & 0 deletions packages/chain-adapters/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export type SubscribeTxsInput = {
wallet: HDWallet
accountNumber: number
accountType?: UtxoAccountType
pubKey?: string
}

export type GetBIP44ParamsInput = {
Expand Down
5 changes: 3 additions & 2 deletions packages/chain-adapters/src/utxo/UtxoBaseAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -537,8 +537,9 @@ export abstract class UtxoBaseAdapter<T extends UtxoChainId> implements IChainAd
const { wallet, accountNumber, accountType = this.defaultUtxoAccountType } = input

const bip44Params = this.getBIP44Params({ accountNumber, accountType })
const { xpub } = await this.getPublicKey(wallet, accountNumber, accountType)
const account = await this.getAccount(xpub)
const account = await this.getAccount(
input.pubKey ?? (await this.getPublicKey(wallet, accountNumber, accountType)).xpub,
gomesalexandre marked this conversation as resolved.
Show resolved Hide resolved
)
const addresses = (account.chainSpecific.addresses ?? []).map(address => address.pubkey)
const subscriptionId = `${toRootDerivationPath(bip44Params)}/${accountType}`

Expand Down
10 changes: 10 additions & 0 deletions react-app-rewired/headers/csps/ledger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { Csp } from '../types'

// Ledger hw-app-eth's needs to connect to Ledger CDN for signing. This is sad, but required.
export const csp: Csp = {
// https://github.com/LedgerHQ/ledger-live/blob/e7129059b4d86378da55c0c8745219da88877e14/libs/ledgerjs/packages/hw-app-eth/src/services/ledger/index.ts#L53
'connect-src': [
'https://cdn.live.ledger.com/cryptoassets/evm/100/erc20-signatures.json',
'https://cdn.live.ledger.com/plugins/ethereum.json',
],
}
4 changes: 3 additions & 1 deletion src/components/Modals/Receive/ReceiveInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ export const ReceiveInfo = ({ asset, accountId }: ReceivePropsType) => {
accountNumber,
})

setVerified(Boolean(deviceAddress) && deviceAddress === receiveAddress)
setVerified(
Boolean(deviceAddress) && deviceAddress.toLowerCase() === receiveAddress.toLowerCase(),
)
}

const translate = useTranslate()
Expand Down
11 changes: 10 additions & 1 deletion src/components/MultiHopTrade/MultiHopTrade.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,15 @@ import { useAppDispatch, useAppSelector } from 'state/store'
import { Approval } from './components/Approval/Approval'
import { TradeConfirm } from './components/TradeConfirm/TradeConfirm'
import { TradeInput } from './components/TradeInput/TradeInput'
import { VerifyAddresses } from './components/VerifyAddresses/VerifyAddresses'
import { TradeRoutePaths } from './types'

const MultiHopEntries = [TradeRoutePaths.Input, TradeRoutePaths.Approval, TradeRoutePaths.Confirm]
const MultiHopEntries = [
TradeRoutePaths.Input,
TradeRoutePaths.Approval,
TradeRoutePaths.Confirm,
TradeRoutePaths.VerifyAddresses,
]

export type TradeCardProps = {
defaultBuyAssetId?: AssetId
Expand Down Expand Up @@ -94,6 +100,9 @@ const MultiHopRoutes = memo(() => {
<Route key={TradeRoutePaths.Approval} path={TradeRoutePaths.Approval}>
<Approval />
</Route>
<Route key={TradeRoutePaths.VerifyAddresses} path={TradeRoutePaths.VerifyAddresses}>
<VerifyAddresses />
</Route>
</Switch>
</AnimatePresence>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
Tooltip,
useToken,
} from '@chakra-ui/react'
import { isLedger } from '@shapeshiftoss/hdwallet-ledger'
import { AnimatePresence } from 'framer-motion'
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
import type { ColorFormat } from 'react-countdown-circle-timer'
Expand Down Expand Up @@ -211,6 +212,11 @@ export const TradeInput = memo(() => {

const isApprovalNeeded = await checkApprovalNeeded(tradeQuoteStep, wallet)

if (isLedger(wallet)) {
history.push({ pathname: TradeRoutePaths.VerifyAddresses })
return
}

if (isApprovalNeeded) {
history.push({ pathname: TradeRoutePaths.Approval })
return
Expand Down
gomesalexandre marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import {
Box,
Button,
CardBody,
CardFooter,
CardHeader,
Flex,
Text as CText,
} from '@chakra-ui/react'
import { CHAIN_NAMESPACE, fromAccountId, fromChainId } from '@shapeshiftoss/caip'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useHistory } from 'react-router'
import { useAccountIds } from 'components/MultiHopTrade/hooks/useAccountIds'
import { checkApprovalNeeded } from 'components/MultiHopTrade/hooks/useAllowanceApproval/helpers'
import { getReceiveAddress } from 'components/MultiHopTrade/hooks/useReceiveAddress'
import { TradeRoutePaths } from 'components/MultiHopTrade/types'
import { Row } from 'components/Row/Row'
import { SlideTransition } from 'components/SlideTransition'
import { Text } from 'components/Text'
import { getChainAdapterManager } from 'context/PluginProvider/chainAdapterSingleton'
import { useWallet } from 'hooks/useWallet/useWallet'
import {
selectBuyAsset,
selectPortfolioAccountMetadataByAccountId,
selectSellAsset,
} from 'state/slices/selectors'
import { selectFirstHop } from 'state/slices/tradeQuoteSlice/selectors'
import { useAppSelector } from 'state/store'

export const VerifyAddresses = () => {
const wallet = useWallet().state.wallet
const history = useHistory()

const [sellAddress, setSellAddress] = useState<string | undefined>()
const [buyAddress, setBuyAddress] = useState<string | undefined>()
const [isSellVerifying, setIsSellVerifying] = useState(false)
const [isBuyVerifying, setIsBuyVerifying] = useState(false)

const [sellVerified, setSellVerified] = useState(false)
const [buyVerified, setBuyVerified] = useState(false)

const buyAsset = useAppSelector(selectBuyAsset)
const sellAsset = useAppSelector(selectSellAsset)
const tradeQuoteStep = useAppSelector(selectFirstHop)

const { sellAssetAccountId, buyAssetAccountId } = useAccountIds()

const sellAccountFilter = useMemo(
() => ({ accountId: sellAssetAccountId ?? '' }),
[sellAssetAccountId],
)
const sellAccountMetadata = useAppSelector(state =>
selectPortfolioAccountMetadataByAccountId(state, sellAccountFilter),
)
const buyAccountFilter = useMemo(
() => ({ accountId: buyAssetAccountId ?? '' }),
[buyAssetAccountId],
)
const buyAccountMetadata = useAppSelector(state =>
selectPortfolioAccountMetadataByAccountId(state, buyAccountFilter),
)

const handleContinue = useCallback(async () => {
if (!tradeQuoteStep) throw Error('missing tradeQuoteStep')
if (!wallet) throw Error('missing wallet')

const isApprovalNeeded = await checkApprovalNeeded(tradeQuoteStep, wallet)
if (isApprovalNeeded) {
history.push({ pathname: TradeRoutePaths.Approval })
return
}

history.push({ pathname: TradeRoutePaths.Confirm })
}, [history, tradeQuoteStep, wallet])

const fetchAddresses = useCallback(async () => {
if (
!wallet ||
!sellAssetAccountId ||
!buyAssetAccountId ||
!sellAccountMetadata ||
!buyAccountMetadata
)
return

const deviceId = await wallet.getDeviceID()

const fetchedSellAddress = await getReceiveAddress({
asset: sellAsset,
wallet,
deviceId,
accountMetadata: sellAccountMetadata,
pubKey: fromAccountId(sellAssetAccountId).account,
})
const fetchedBuyAddress = await getReceiveAddress({
asset: buyAsset,
wallet,
deviceId,
accountMetadata: buyAccountMetadata,
pubKey: fromAccountId(buyAssetAccountId).account,
})

setSellAddress(fetchedSellAddress)
setBuyAddress(fetchedBuyAddress)
}, [
wallet,
sellAssetAccountId,
buyAssetAccountId,
sellAccountMetadata,
buyAccountMetadata,
sellAsset,
buyAsset,
])

useEffect(() => {
fetchAddresses()
}, [fetchAddresses])

const handleVerify = useCallback(
async (type: 'sell' | 'buy') => {
if (type === 'sell') {
setIsSellVerifying(true)
} else if (type === 'buy') {
setIsBuyVerifying(true)
}

try {
const asset = type === 'sell' ? sellAsset : buyAsset
const accountMetadata = type === 'sell' ? sellAccountMetadata : buyAccountMetadata
const _address = type === 'sell' ? sellAddress : buyAddress

if (!asset || !accountMetadata || !_address || !wallet) return

const { chainId } = asset
const adapter = getChainAdapterManager().get(chainId)

if (!adapter) return

const { bip44Params } = accountMetadata

const { chainNamespace } = fromChainId(asset.chainId)
if (CHAIN_NAMESPACE.Utxo === chainNamespace && !accountMetadata.accountType) return

const deviceAddress = await adapter.getAddress({
wallet,
showOnDevice: true,
accountType: accountMetadata.accountType,
accountNumber: bip44Params.accountNumber,
})

if (deviceAddress && deviceAddress.toLowerCase() === _address.toLowerCase()) {
if (type === 'sell') {
setSellVerified(true)
} else if (type === 'buy') {
setBuyVerified(true)
}
}
} catch (e) {
console.error(e)
} finally {
if (type === 'sell') {
setIsSellVerifying(false)
} else if (type === 'buy') {
setIsBuyVerifying(false)
}
}
},
[sellAsset, buyAsset, sellAccountMetadata, buyAccountMetadata, sellAddress, buyAddress, wallet],
)

return (
<SlideTransition>
<Box>
<CardHeader>
<CText fontSize='xl' fontWeight='bold' mb={4}>
Verify Addresses
</CText>
</CardHeader>
<CardBody>
<Row my={3}>
<Row.Label fontWeight='medium'>
<CText>Buy Address:</CText>
</Row.Label>
<Row.Value flex={1} mr={4} fontWeight='light' wordBreak='break-all'>
{buyAddress || 'Fetching...'}
</Row.Value>
<Button
colorScheme={buyVerified ? 'green' : 'blue'}
onClick={() => handleVerify('buy')}
gomesalexandre marked this conversation as resolved.
Show resolved Hide resolved
isDisabled={buyVerified}
isLoading={isBuyVerifying}
>
{buyVerified ? 'Verified' : 'Verify'}
</Button>
</Row>
<Row my={3}>
<Row.Label fontWeight='medium'>
<CText>Sell Address:</CText>
</Row.Label>
<Row.Value flex={1} mr={4} fontWeight='light' wordBreak='break-all'>
{sellAddress || 'Fetching...'}
</Row.Value>
<Button
colorScheme={sellVerified ? 'green' : 'blue'}
onClick={() => handleVerify('sell')}
gomesalexandre marked this conversation as resolved.
Show resolved Hide resolved
isDisabled={sellVerified}
isLoading={isSellVerifying}
>
{sellVerified ? 'Verified' : 'Verify'}
</Button>
</Row>
</CardBody>
<CardFooter mt={4}>
<Flex direction='column' alignItems='center' justifyContent='space-between' width='full'>
<CText color='red.500' textAlign='center' mb={4}>
Ensure your addresses are correct before proceeding.
</CText>

<Button
onClick={handleContinue}
size='lg'
isDisabled={!(sellVerified && buyVerified)}
width='full'
>
<Text translation='common.continue' />
</Button>
</Flex>
</CardFooter>
</Box>
</SlideTransition>
)
}
Loading