diff --git a/package.json b/package.json
index 62164b15780..81b9053eabe 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"@shapeshiftoss/hdwallet-native": "^1.21.2",
"@shapeshiftoss/hdwallet-native-vault": "^1.21.2",
"@shapeshiftoss/hdwallet-portis": "^1.21.2",
+ "@shapeshiftoss/hdwallet-xdefi": "^1.21.2",
"@shapeshiftoss/investor-foxy": "^3.0.0",
"@shapeshiftoss/investor-yearn": "^2.0.0",
"@shapeshiftoss/logger": "^1.1.2",
diff --git a/src/assets/translations/en/main.json b/src/assets/translations/en/main.json
index 6e2c8c0ea5f..d6d0caf7bba 100644
--- a/src/assets/translations/en/main.json
+++ b/src/assets/translations/en/main.json
@@ -781,6 +781,28 @@
"body": "Unable to connect Portis wallet"
}
},
+ "xdefi": {
+ "errors": {
+ "unknown": "An unexpected error occurred communicating with XDEFI",
+ "connectFailure": "Unable to connect XDEFI wallet",
+ "network": "XDEFI isn't set to Ethereum Mainnet",
+ "multipleWallets": "Detected Ethereum provider is not XDEFI. Do you have multiple wallets installed?"
+ },
+ "connect": {
+ "header": "Pair XDEFI",
+ "body": "Click Pair and login to XDEFI from the popup window",
+ "button": "Pair"
+ },
+ "failure": {
+ "header": "Error",
+ "body": "Unable to connect XDEFI wallet"
+ },
+ "redirect": {
+ "header": "Open in XDEFI App",
+ "body": "Click to open ShapeShift dashboard in XDEFI",
+ "button": "Open"
+ }
+ },
"shapeShift": {
"load": {
"error": {
diff --git a/src/components/Icons/XDEFIIcon.tsx b/src/components/Icons/XDEFIIcon.tsx
new file mode 100644
index 00000000000..526b433c08f
--- /dev/null
+++ b/src/components/Icons/XDEFIIcon.tsx
@@ -0,0 +1,32 @@
+import { createIcon } from '@chakra-ui/react'
+
+export const XDEFIIcon = createIcon({
+ displayName: 'XDeFiIcon',
+ path: (
+
+ ),
+ viewBox: '0 0 318.6 318.6',
+})
diff --git a/src/context/WalletProvider/KeyManager.ts b/src/context/WalletProvider/KeyManager.ts
index 306a7844df8..2292db9a513 100644
--- a/src/context/WalletProvider/KeyManager.ts
+++ b/src/context/WalletProvider/KeyManager.ts
@@ -4,4 +4,5 @@ export enum KeyManager {
MetaMask = 'metamask',
Portis = 'portis',
Demo = 'demo',
+ XDefi = 'xdefi',
}
diff --git a/src/context/WalletProvider/WalletProvider.tsx b/src/context/WalletProvider/WalletProvider.tsx
index eb963f2130d..4d67169f964 100644
--- a/src/context/WalletProvider/WalletProvider.tsx
+++ b/src/context/WalletProvider/WalletProvider.tsx
@@ -381,6 +381,31 @@ 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)?.pairDevice()
+ if (localXDEFIWallet) {
+ const { name, icon } = SUPPORTED_WALLETS[KeyManager.XDefi]
+ try {
+ await localXDEFIWallet.initialize()
+ const deviceId = await localXDEFIWallet.getDeviceID()
+ dispatch({
+ type: WalletActions.SET_WALLET,
+ payload: {
+ wallet: localXDEFIWallet,
+ name,
+ icon,
+ deviceId,
+ },
+ })
+ dispatch({ type: WalletActions.SET_IS_CONNECTED, payload: true })
+ } catch (e) {
+ disconnect()
+ }
+ } else {
+ disconnect()
+ }
+ dispatch({ type: WalletActions.SET_LOCAL_WALLET_LOADING, payload: false })
+ break
default:
/**
* The fall-through case also handles clearing
diff --git a/src/context/WalletProvider/XDEFI/components/Connect.tsx b/src/context/WalletProvider/XDEFI/components/Connect.tsx
new file mode 100644
index 00000000000..8d3cf55dc77
--- /dev/null
+++ b/src/context/WalletProvider/XDEFI/components/Connect.tsx
@@ -0,0 +1,113 @@
+import { XDEFIHDWallet } from '@shapeshiftoss/hdwallet-xdefi'
+import React, { useState } from 'react'
+import { RouteComponentProps } from 'react-router-dom'
+import { ActionTypes, WalletActions } from 'context/WalletProvider/actions'
+import { KeyManager } from 'context/WalletProvider/KeyManager'
+import { setLocalWalletTypeAndDeviceId } from 'context/WalletProvider/local-wallet'
+import { useWallet } from 'hooks/useWallet/useWallet'
+
+import { ConnectModal } from '../../components/ConnectModal'
+import { LocationState } from '../../NativeWallet/types'
+import { XDEFIConfig } from '../config'
+
+export interface XDEFISetupProps
+ extends RouteComponentProps<
+ {},
+ any, // history
+ LocationState
+ > {
+ dispatch: React.Dispatch
+}
+
+export const XDEFIConnect = ({ history }: XDEFISetupProps) => {
+ const { dispatch, state } = useWallet()
+ const [loading, setLoading] = useState(false)
+ const [error, setError] = useState(null)
+ let provider: any
+
+ // eslint-disable-next-line no-sequences
+ const setErrorLoading = (e: string | null) => (setError(e), setLoading(false))
+
+ const pairDevice = async () => {
+ setError(null)
+ setLoading(true)
+
+ try {
+ provider = (globalThis as any).xfi && (globalThis as any).xfi.ethereum
+ } catch (error) {
+ throw new Error('walletProvider.xdefi.errors.connectFailure')
+ }
+
+ if (state.adapters && state.adapters?.has(KeyManager.XDefi)) {
+ try {
+ const wallet = (await state.adapters.get(KeyManager.XDefi)?.pairDevice()) as
+ | XDEFIHDWallet
+ | undefined
+ if (!wallet) {
+ setErrorLoading('walletProvider.errors.walletNotFound')
+ throw new Error('Call to hdwallet-xdefi::pairDevice returned null or undefined')
+ }
+
+ const { name, icon } = XDEFIConfig
+
+ const deviceId = await wallet.getDeviceID()
+
+ if (provider !== (globalThis as any).xfi.ethereum) {
+ throw new Error('walletProvider.xdefi.errors.multipleWallets')
+ }
+
+ if (provider?.chainId !== 1) {
+ throw new Error('walletProvider.xdefi.errors.network')
+ }
+
+ // Hack to handle XDEFI account changes
+ //TODO: handle this properly
+ const resetState = () => dispatch({ type: WalletActions.RESET_STATE })
+ provider?.on?.('accountsChanged', resetState)
+ provider?.on?.('chainChanged', resetState)
+
+ const oldDisconnect = wallet.disconnect.bind(wallet)
+ wallet.disconnect = () => {
+ provider?.removeListener?.('accountsChanged', resetState)
+ provider?.removeListener?.('chainChanged', resetState)
+ return oldDisconnect()
+ }
+
+ await wallet.initialize()
+
+ dispatch({
+ type: WalletActions.SET_WALLET,
+ payload: { wallet, name, icon, deviceId },
+ })
+ dispatch({ type: WalletActions.SET_IS_CONNECTED, payload: true })
+ setLocalWalletTypeAndDeviceId(KeyManager.XDefi, deviceId)
+ dispatch({ type: WalletActions.SET_WALLET_MODAL, payload: false })
+ } catch (e: any) {
+ if (e?.message?.startsWith('walletProvider.')) {
+ console.error('XDEFI Connect: There was an error initializing the wallet', e)
+ setErrorLoading(e?.message)
+ } else {
+ setErrorLoading('walletProvider.xdefi.errors.unknown')
+ history.push('/xdefi/failure')
+ // Safely navigate user to website if XDEFI is not found
+ if (e?.message === 'XDEFI provider not found') {
+ const newWindow = window.open('https://xdefi.io', '_blank', 'noopener noreferrer')
+ if (newWindow) newWindow.opener = null
+ }
+ }
+ }
+ }
+ setLoading(false)
+ }
+
+ return (
+
+ )
+}
diff --git a/src/context/WalletProvider/XDEFI/components/Failure.tsx b/src/context/WalletProvider/XDEFI/components/Failure.tsx
new file mode 100644
index 00000000000..1d36059b6e2
--- /dev/null
+++ b/src/context/WalletProvider/XDEFI/components/Failure.tsx
@@ -0,0 +1,10 @@
+import { FailureModal } from 'context/WalletProvider/components/FailureModal'
+
+export const XDEFIFailure = () => {
+ return (
+
+ )
+}
diff --git a/src/context/WalletProvider/XDEFI/config.ts b/src/context/WalletProvider/XDEFI/config.ts
new file mode 100644
index 00000000000..0bc1f84659e
--- /dev/null
+++ b/src/context/WalletProvider/XDEFI/config.ts
@@ -0,0 +1,8 @@
+import { XDEFIAdapter } from '@shapeshiftoss/hdwallet-xdefi'
+import { XDEFIIcon } from 'components/Icons/XDEFIIcon'
+
+export const XDEFIConfig = {
+ adapter: XDEFIAdapter,
+ icon: XDEFIIcon,
+ name: 'XDEFI',
+}
diff --git a/src/context/WalletProvider/config.ts b/src/context/WalletProvider/config.ts
index f7d8f965a3e..ff4d1fe0a37 100644
--- a/src/context/WalletProvider/config.ts
+++ b/src/context/WalletProvider/config.ts
@@ -34,6 +34,9 @@ import { NativeConfig } from './NativeWallet/config'
import { PortisConnect } from './Portis/components/Connect'
import { PortisFailure } from './Portis/components/Failure'
import { PortisConfig } from './Portis/config'
+import { XDEFIConnect } from './XDEFI/components/Connect'
+import { XDEFIFailure } from './XDEFI/components/Failure'
+import { XDEFIConfig } from './XDEFI/config'
export interface SupportedWalletInfo {
adapter: any
@@ -89,6 +92,13 @@ export const SUPPORTED_WALLETS: Record = {
{ path: '/portis/failure', component: PortisFailure },
],
},
+ [KeyManager.XDefi]: {
+ ...XDEFIConfig,
+ routes: [
+ { path: '/xdefi/connect', component: XDEFIConnect },
+ { path: '/xdefi/failure', component: XDEFIFailure },
+ ],
+ },
[KeyManager.Demo]: {
...DemoConfig,
routes: [],
diff --git a/src/hooks/useBalanceChartData/useBalanceChartData.ts b/src/hooks/useBalanceChartData/useBalanceChartData.ts
index ad43b55c1d6..9e5eb5b5227 100644
--- a/src/hooks/useBalanceChartData/useBalanceChartData.ts
+++ b/src/hooks/useBalanceChartData/useBalanceChartData.ts
@@ -407,7 +407,6 @@ export const useBalanceChartData: UseBalanceChartData = args => {
const hasNoDeviceId = isNil(walletInfo?.deviceId)
const hasNoAssetIds = !assetIds.length
const hasNoPriceHistoryData = isEmpty(cryptoPriceHistoryData) || !fiatPriceHistoryData?.length
-
if (hasNoDeviceId || hasNoAssetIds || hasNoPriceHistoryData) {
return setBalanceChartDataLoading(true)
}
diff --git a/yarn.lock b/yarn.lock
index 4731cd7b4b9..9e53264160c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4004,6 +4004,14 @@
p-lazy "^3.1.0"
web3 "^1.5.1"
+"@shapeshiftoss/hdwallet-xdefi@^1.21.2":
+ version "1.22.0"
+ resolved "https://registry.yarnpkg.com/@shapeshiftoss/hdwallet-xdefi/-/hdwallet-xdefi-1.22.0.tgz#74b39347bb39d165cdc4cc0a90b786b2bef7a6d2"
+ integrity sha512-XsczkfSteFLhNNgWEKiOqJO6ZPWTwJB9jevwxPks+yOzk4E/tEoGRRyybI3BJgnmnqCghb+7OwbIAhEnMgf4dA==
+ dependencies:
+ "@shapeshiftoss/hdwallet-core" "1.22.0"
+ lodash "^4.17.21"
+
"@shapeshiftoss/investor-foxy@^3.0.0":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@shapeshiftoss/investor-foxy/-/investor-foxy-3.0.1.tgz#64ae368f117ad98c5933b7bc2baff5798e880641"