Skip to content

Commit

Permalink
fix: localWalletType on app refresh
Browse files Browse the repository at this point in the history
  • Loading branch information
gomesalexandre committed Oct 14, 2023
1 parent 4e120d9 commit e69bacd
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 141 deletions.
64 changes: 31 additions & 33 deletions src/context/WalletProvider/Ledger/components/Connect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,43 +31,41 @@ export const LedgerConnect = ({ history }: LedgerSetupProps) => {
const pairDevice = useCallback(async () => {
setError(null)
setLoading(true)
if (state.adapters) {
const currentAdapters = state.adapters
// eslint is drunk, this isn't a hook
// eslint-disable-next-line react-hooks/rules-of-hooks
const ledgerAdapter = WebUSBLedgerAdapter.useKeyring(state.keyring)
// This is conventionally done in WalletProvider effect, but won't work here, as `requestDevice()` needs to be called from a user interaction
// So we do it in this pairDevice() method instead and set the adapters the same as we would do in WalletProvider
const currentAdapters = state.adapters ?? new Map()
// eslint is drunk, this isn't a hook
// eslint-disable-next-line react-hooks/rules-of-hooks
const ledgerAdapter = WebUSBLedgerAdapter.useKeyring(state.keyring)
// This is conventionally done in WalletProvider effect, but won't work here, as `requestDevice()` needs to be called from a user interaction
// So we do it in this pairDevice() method instead and set the adapters the same as we would do in WalletProvider
try {
const wallet = await ledgerAdapter.pairDevice()
try {
const wallet = await ledgerAdapter.pairDevice()
try {
currentAdapters.set(KeyManager.Ledger, [ledgerAdapter])
walletDispatch({ type: WalletActions.SET_ADAPTERS, payload: currentAdapters })
} catch (e) {
console.error(e)
}
currentAdapters.set(KeyManager.Ledger, [ledgerAdapter])
walletDispatch({ type: WalletActions.SET_ADAPTERS, payload: currentAdapters })
} catch (e) {
console.error(e)
}

if (!wallet) {
setErrorLoading('walletProvider.errors.walletNotFound')
throw new Error('Call to hdwallet-ledger::pairDevice returned null or undefined')
}
if (!wallet) {
setErrorLoading('walletProvider.errors.walletNotFound')
throw new Error('Call to hdwallet-ledger::pairDevice returned null or undefined')
}

const { name, icon } = LedgerConfig
// TODO(gomes): this is most likely wrong, all Ledger devices get the same device ID
const deviceId = await wallet.getDeviceID()
const { name, icon } = LedgerConfig
// TODO(gomes): this is most likely wrong, all Ledger devices get the same device ID
const deviceId = await wallet.getDeviceID()

walletDispatch({
type: WalletActions.SET_WALLET,
payload: { wallet, name, icon, deviceId, connectedType: KeyManager.Ledger },
})
walletDispatch({ type: WalletActions.SET_IS_CONNECTED, payload: true })
setLocalWalletTypeAndDeviceId(KeyManager.Ledger, deviceId)
history.push('/ledger/chains')
} catch (e: any) {
console.error(e)
setErrorLoading(e?.message || 'walletProvider.ledger.errors.unknown')
history.push('/ledger/failure')
}
walletDispatch({
type: WalletActions.SET_WALLET,
payload: { wallet, name, icon, deviceId, connectedType: KeyManager.Ledger },
})
walletDispatch({ type: WalletActions.SET_IS_CONNECTED, payload: true })
setLocalWalletTypeAndDeviceId(KeyManager.Ledger, deviceId)
history.push('/ledger/chains')
} catch (e: any) {
console.error(e)
setErrorLoading(e?.message || 'walletProvider.ledger.errors.unknown')
history.push('/ledger/failure')
}
setLoading(false)
}, [history, setErrorLoading, state.adapters, state.keyring, walletDispatch])
Expand Down
64 changes: 32 additions & 32 deletions src/context/WalletProvider/NativeWallet/components/NativeLoad.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,40 +69,40 @@ export const NativeLoad = ({ history }: RouteComponentProps) => {
}, [wallets])

const handleWalletSelect = async (item: VaultInfo) => {
const adapters = state.adapters?.get(KeyManager.Native)
const deviceId = item.id
if (adapters?.length) {
const { name, icon } = NativeConfig
try {
const wallet = await adapters[0].pairDevice(deviceId)
if (!(await wallet.isInitialized())) {
// This will trigger the password modal and the modal will set the wallet on state
// after the wallet has been decrypted. If we set it now, `getPublicKeys` calls will
// return null, and we don't have a retry mechanism
await wallet.initialize()
} else {
dispatch({
type: WalletActions.SET_WALLET,
payload: {
wallet,
name,
icon,
deviceId,
meta: { label: item.name },
connectedType: KeyManager.Native,
},
})
dispatch({ type: WalletActions.SET_IS_CONNECTED, payload: true })
// The wallet is already initialized so we can close the modal
dispatch({ type: WalletActions.SET_WALLET_MODAL, payload: false })
}

setLocalWalletTypeAndDeviceId(KeyManager.Native, deviceId)
setLocalNativeWalletName(item.name)
} catch (e) {
setError('walletProvider.shapeShift.load.error.pair')
const { name, icon } = NativeConfig
try {
const Adapter = await NativeConfig.adapters[0].loadAdapter()
// eslint is drunk, this isn't a hook
// eslint-disable-next-line react-hooks/rules-of-hooks
const adapter = Adapter.useKeyring(state.keyring)
// TODO(gomes): SET_ADAPTERS
const wallet = await adapter.pairDevice(deviceId)
if (!(await wallet.isInitialized())) {
// This will trigger the password modal and the modal will set the wallet on state
// after the wallet has been decrypted. If we set it now, `getPublicKeys` calls will
// return null, and we don't have a retry mechanism
await wallet.initialize()
} else {
dispatch({
type: WalletActions.SET_WALLET,
payload: {
wallet,
name,
icon,
deviceId,
meta: { label: item.name },
connectedType: KeyManager.Native,
},
})
dispatch({ type: WalletActions.SET_IS_CONNECTED, payload: true })
// The wallet is already initialized so we can close the modal
dispatch({ type: WalletActions.SET_WALLET_MODAL, payload: false })
}
} else {

setLocalWalletTypeAndDeviceId(KeyManager.Native, deviceId)
setLocalNativeWalletName(item.name)
} catch (e) {
setError('walletProvider.shapeShift.load.error.pair')
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/context/WalletProvider/SelectModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const WalletSelectItem = ({

export const SelectModal = () => {
const {
state: { adapters, walletInfo },
state: { walletInfo },
connect,
create,
} = useWallet()
Expand All @@ -123,7 +123,7 @@ export const SelectModal = () => {
<ModalBody>
<Text mb={6} color='text.subtle' translation={'walletProvider.selectModal.body'} />
<Grid mb={6} gridTemplateColumns={gridTemplateColumnsProp} gridGap={4}>
{adapters &&
{
// TODO: KeepKey adapter may fail due to the USB interface being in use by another tab
// So not all of the supported wallets will have an initialized adapter
wallets.map(walletType => (
Expand All @@ -133,7 +133,8 @@ export const SelectModal = () => {
walletInfo={walletInfo}
connect={connect}
/>
))}
))
}
</Grid>
<Flex direction={flexDirProp} mt={2} justifyContent='center' alignItems='center'>
<Text
Expand Down
150 changes: 77 additions & 73 deletions src/context/WalletProvider/WalletProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,25 @@ const initialState: InitialState = {
disconnectOnCloseModal: false,
}

type KeyManagerOptions = undefined | CoinbaseProviderConfig | EthereumProviderOptions
type GetKeyManagerOptions = (keyManager: KeyManager) => KeyManagerOptions
export const getKeyManagerOptions: GetKeyManagerOptions = keyManager => {
switch (keyManager) {
case KeyManager.WalletConnectV2:
return walletConnectV2ProviderConfig
case KeyManager.Coinbase:
return {
appName: 'ShapeShift',
appLogoUrl: 'https://avatars.githubusercontent.com/u/52928763?s=50&v=4',
defaultJsonRpcUrl: getConfig().REACT_APP_ETHEREUM_NODE_URL,
defaultChainId: 1,
darkMode: isDarkMode,
}
default:
return undefined
}
}

export const isKeyManagerWithProvider = (
keyManager: KeyManager | null,
): keyManager is KeyManagerWithProvider =>
Expand Down Expand Up @@ -378,21 +397,30 @@ export const WalletProvider = ({ children }: { children: React.ReactNode }): JSX
const load = useCallback(() => {
const localWalletType = getLocalWalletType()
const localWalletDeviceId = getLocalWalletDeviceId()
if (localWalletType && localWalletDeviceId && state.adapters) {
if (localWalletType && localWalletDeviceId) {
;(async () => {
if (state.adapters?.has(localWalletType)) {
const currentAdapters = state.adapters ?? new Map()
const Adapter = await SUPPORTED_WALLETS[localWalletType].adapters[0].loadAdapter()
// eslint is drunk, this isn't a hook
// eslint-disable-next-line react-hooks/rules-of-hooks
const adapterInstance = Adapter.useKeyring(state.keyring)

if (adapterInstance) {
try {
currentAdapters.set(localWalletType, [adapterInstance])
dispatch({ type: WalletActions.SET_ADAPTERS, payload: currentAdapters })
} catch (e) {
console.error(e)
}
// Fixes issue with wallet `type` being null when the wallet is loaded from state
dispatch({ type: WalletActions.SET_CONNECTOR_TYPE, payload: localWalletType })

const nativeAdapters = state.adapters.get(KeyManager.Native)

switch (localWalletType) {
case KeyManager.Mobile:
try {
const w = await getWallet(localWalletDeviceId)
if (w && w.mnemonic && w.label) {
const localMobileWallet =
await nativeAdapters?.[0]?.pairDevice(localWalletDeviceId)
const localMobileWallet = await adapterInstance.pairDevice(localWalletDeviceId)

if (localMobileWallet) {
localMobileWallet.loadDevice({ label: w.label, mnemonic: w.mnemonic })
Expand Down Expand Up @@ -424,7 +452,7 @@ export const WalletProvider = ({ children }: { children: React.ReactNode }): JSX
}
break
case KeyManager.Native:
const localNativeWallet = await nativeAdapters?.[0]?.pairDevice(localWalletDeviceId)
const localNativeWallet = await adapterInstance.pairDevice(localWalletDeviceId)
if (localNativeWallet) {
/**
* This will eventually fire an event, which the ShapeShift wallet
Expand Down Expand Up @@ -486,9 +514,7 @@ export const WalletProvider = ({ children }: { children: React.ReactNode }): JSX
dispatch({ type: WalletActions.SET_LOCAL_WALLET_LOADING, payload: false })
break
case KeyManager.MetaMask:
const localMetaMaskWallet = await state.adapters
.get(KeyManager.MetaMask)?.[0]
?.pairDevice()
const localMetaMaskWallet = await adapterInstance?.pairDevice()
if (localMetaMaskWallet) {
const { name, icon } = SUPPORTED_WALLETS[KeyManager.MetaMask]
try {
Expand All @@ -515,9 +541,7 @@ export const WalletProvider = ({ children }: { children: React.ReactNode }): JSX
dispatch({ type: WalletActions.SET_LOCAL_WALLET_LOADING, payload: false })
break
case KeyManager.Coinbase:
const localCoinbaseWallet = await state.adapters
.get(KeyManager.Coinbase)?.[0]
?.pairDevice()
const localCoinbaseWallet = await adapterInstance?.pairDevice()
if (localCoinbaseWallet) {
const { name, icon } = SUPPORTED_WALLETS[KeyManager.Coinbase]
try {
Expand All @@ -544,7 +568,7 @@ export const WalletProvider = ({ children }: { children: React.ReactNode }): JSX
dispatch({ type: WalletActions.SET_LOCAL_WALLET_LOADING, payload: false })
break
case KeyManager.XDefi:
const localXDEFIWallet = await state.adapters.get(KeyManager.XDefi)?.[0]?.pairDevice()
const localXDEFIWallet = await adapterInstance?.pairDevice()
if (localXDEFIWallet) {
const { name, icon } = SUPPORTED_WALLETS[KeyManager.XDefi]
try {
Expand All @@ -570,7 +594,7 @@ export const WalletProvider = ({ children }: { children: React.ReactNode }): JSX
dispatch({ type: WalletActions.SET_LOCAL_WALLET_LOADING, payload: false })
break
case KeyManager.Keplr:
const localKeplrWallet = await state.adapters.get(KeyManager.Keplr)?.[0]?.pairDevice()
const localKeplrWallet = await adapterInstance?.pairDevice()
if (localKeplrWallet) {
const { name, icon } = SUPPORTED_WALLETS[KeyManager.Keplr]
try {
Expand Down Expand Up @@ -598,9 +622,7 @@ export const WalletProvider = ({ children }: { children: React.ReactNode }): JSX
case KeyManager.WalletConnectV2: {
// Re-trigger the modal on refresh
await onProviderChange(KeyManager.WalletConnectV2)
const localWalletConnectWallet = await state.adapters
.get(KeyManager.WalletConnectV2)?.[0]
?.pairDevice()
const localWalletConnectWallet = await adapterInstance?.pairDevice()
if (localWalletConnectWallet) {
const { name, icon } = SUPPORTED_WALLETS[KeyManager.WalletConnectV2]
try {
Expand Down Expand Up @@ -737,61 +759,43 @@ export const WalletProvider = ({ children }: { children: React.ReactNode }): JSX
})()
}, [state.wallet, onProviderChange])

useEffect(() => {
if (state.keyring) {
;(async () => {
const adapters: Adapters = new Map()
for (const keyManager of Object.values(KeyManager)) {
try {
type KeyManagerOptions = undefined | CoinbaseProviderConfig | EthereumProviderOptions

type GetKeyManagerOptions = (keyManager: KeyManager) => KeyManagerOptions

const getKeyManagerOptions: GetKeyManagerOptions = keyManager => {
switch (keyManager) {
case KeyManager.WalletConnectV2:
return walletConnectV2ProviderConfig
case KeyManager.Coinbase:
return {
appName: 'ShapeShift',
appLogoUrl: 'https://avatars.githubusercontent.com/u/52928763?s=50&v=4',
defaultJsonRpcUrl: getConfig().REACT_APP_ETHEREUM_NODE_URL,
defaultChainId: 1,
darkMode: isDarkMode,
}
default:
return undefined
}
}

const walletAdapters = await SUPPORTED_WALLETS[keyManager]?.adapters.reduce<
Promise<GenericAdapter[]>
>(async (acc, cur) => {
const adapters = await acc
const options = getKeyManagerOptions(keyManager)
try {
const { loadAdapter } = cur
const Adapter = await loadAdapter()
// eslint is drunk, this isn't a hook
// eslint-disable-next-line react-hooks/rules-of-hooks
const adapter = Adapter.useKeyring(state.keyring, options)
adapters.push(adapter)
} catch (e) {
console.error(e)
}
return acc
}, Promise.resolve([]))

if (walletAdapters.length) adapters.set(keyManager, walletAdapters)
} catch (e) {
console.error(e)
}
}

dispatch({ type: WalletActions.SET_ADAPTERS, payload: adapters })
})()
}
}, [isDarkMode, state.keyring])
// useEffect(() => {
// if (state.keyring) {
// ;(async () => {
// const adapters: Adapters = new Map()
// for (const keyManager of Object.values(KeyManager)) {
// try {
//
//
//
// const walletAdapters = await SUPPORTED_WALLETS[keyManager]?.adapters.reduce<
// Promise<GenericAdapter[]>
// >(async (acc, cur) => {
// const adapters = await acc
// const options = getKeyManagerOptions(keyManager)
// try {
// const { loadAdapter } = cur
// const Adapter = await loadAdapter()
// eslint is drunk, this isn't a hook
// eslint-disable-next-line react-hooks/rules-of-hooks
// const adapter = Adapter.useKeyring(state.keyring, options)
// adapters.push(adapter)
// } catch (e) {
// console.error(e)
// }
// return acc
// }, Promise.resolve([]))
//
// if (walletAdapters.length) adapters.set(keyManager, walletAdapters)
// } catch (e) {
// console.error(e)
// }
// }
//
// dispatch({ type: WalletActions.SET_ADAPTERS, payload: adapters })
// })()
// }
// }, [isDarkMode, state.keyring])

const connect = useCallback((type: KeyManager) => {
dispatch({ type: WalletActions.SET_CONNECTOR_TYPE, payload: type })
Expand Down

0 comments on commit e69bacd

Please sign in to comment.